From 5d6451f1b4abfce758d3b2ba4a95334b6b3296cf Mon Sep 17 00:00:00 2001 From: Brigs Date: Thu, 6 Aug 2026 15:46:22 -0400 Subject: [PATCH] Remove every remote destination from the HTML report Port of the iLEAPP change, keeping html_safe.py byte-identical across the cores that carry it. A remote href or src in a report is a disclosure channel: following one, or in the case merely opening the report, tells whoever controls that host that the account is under examination, when, and from which IP address. - safe_url() no longer builds an anchor. It returns the URL as escaped text, so the evidence stays readable and copyable while the destination goes. The scheme allowlist it used to gate anchors on goes with them. - safe_local_link() is added for the destinations that stay: report-relative paths. It refuses a scheme, a protocol-relative //host, an absolute path, and any .. segment, so a report link cannot leave the report folder. No artifact in this core builds an anchor by hand, so the helper is the whole change here. Validated: py_compile clean; safe_url returns no anchor for a remote URL, safe_local_link anchors a report-relative path and refuses a remote one; PluginLoader loads every plugin. Co-Authored-By: Claude Opus 5 --- scripts/html_safe.py | 61 ++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/scripts/html_safe.py b/scripts/html_safe.py index 4563256..40768a0 100644 --- a/scripts/html_safe.py +++ b/scripts/html_safe.py @@ -8,17 +8,27 @@ the examiner's report (stored XSS, CWE-79). Route every evidence-derived value in an ``html_columns`` cell through these helpers so -the dynamic parts are escaped while the tool's own structural markup (````, ``
``, +the dynamic parts are escaped while the tool's own structural markup (``
``, ````) is preserved. + +**A report links to nothing outside its own folder.** A remote destination in a +report is a disclosure channel: an ```` is fetched the moment +the report is opened, with no click, which tells whoever controls that host that the +account is under examination, when, and from which IP address. An ```` needs a +click but reaches the same place. Reports are read on analyst workstations and mailed +to counsel, so no ``href`` or ``src`` may leave the report folder -- not ``http``, +``https``, ``ftp``, and not ``mailto``/``tel``, which hand the subject's own address or +number to a mail client or dialer. + +Evidence URLs are still *evidence*, so nothing is dropped: they are rendered as escaped +text the examiner can read and copy. Only the anchor goes away. Report-relative +destinations -- ``media/`` thumbnails, files an artifact writes beside the report +-- stay clickable through ``safe_local_link()``; they resolve offline and reach nothing. """ import html from urllib.parse import urlparse -# Schemes we are willing to turn into a live . Anything else (notably -# javascript: and data:) is rendered as escaped text, never as a clickable link. -ALLOWED_URL_SCHEMES = ('http', 'https', 'mailto', 'tel', 'ftp', 'ftps') - def esc(value): """HTML-escape a single evidence value for safe use in text or an attribute. @@ -31,25 +41,42 @@ def esc(value): return html.escape(str(value), quote=True) -def safe_url(url, text=None, target='_blank'): - """Build an ```` anchor with an escaped, scheme-checked href. +def safe_url(url, text=None, target=None): # pylint: disable=unused-argument + """Render an evidence URL as escaped text. **Never returns a link.** + + A URL read out of an extraction names a host chosen by whoever wrote the data, so + it is exactly the destination a report must not reach; see the module docstring. + The URL itself is evidence and is preserved verbatim as escaped text, so an + examiner can read it, copy it, and open it deliberately elsewhere. - Returns the escaped visible text with **no** anchor when the URL is empty or its - scheme is not allowlisted, so ``javascript:``/``data:`` URLs never become live - links. ``text`` (the visible label) defaults to the URL itself. + ``text`` overrides the visible string when the caller has a better label than the + raw URL. ``target`` is accepted and ignored: it exists so the call sites that + passed it before this became text-only keep working. """ url = '' if url is None else str(url).strip() - label = esc(text if text is not None else url) - if not url: + return esc(text if text is not None else url) + + +def safe_local_link(path, text=None): + """Build an ```` to a file inside the report folder. + + This is the only helper that still emits an anchor. The destination must be + report-relative -- no scheme, no protocol-relative ``//host``, no absolute path, + and no ``..`` escaping the folder -- so following it can never leave the report. + Anything else is returned as escaped text with no anchor. + """ + path = '' if path is None else str(path).strip() + label = esc(text if text is not None else path) + if not path: + return label + if path.startswith(('/', '\\', '//')) or '..' in path.replace('\\', '/').split('/'): return label try: - scheme = urlparse(url).scheme.lower() + if urlparse(path).scheme: + return label except ValueError: return label - if scheme not in ALLOWED_URL_SCHEMES: - return label - rel = ' rel="noopener noreferrer"' if target == '_blank' else '' - return f'{label}' + return f'{label}' def safe_join(values, sep='
'):