From d8fa70e17b3ddf129e4c05bafe29555f7b36fded Mon Sep 17 00:00:00 2001 From: mistic96 <112645335+mistic96@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:46:19 +0000 Subject: [PATCH] Harden HTML output against U+2028 XSS + two crash-on-adversarial-input fixes (#1357) Harden HTML export against U+2028/U+2029 script-breakout XSS + two crash-on-adversarial-input fixes (non-dict LLM JSON, _extract_parallel IndexError). Validated: full suite 2107 passed, HTML export smoke clean. Thanks @mistic96. --- graphify/extract.py | 7 ++++--- graphify/llm.py | 12 ++++++++++-- graphify/tree_html.py | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/graphify/extract.py b/graphify/extract.py index fb7741d6..f40fa928 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -12164,16 +12164,17 @@ def _extract_parallel( try: with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as pool: futures = { - pool.submit(_extract_single_file, item): item[0] for item in work_items + pool.submit(_extract_single_file, item): pos + for pos, item in enumerate(work_items) } for future in concurrent.futures.as_completed(futures): try: idx, result = future.result() per_file[idx] = result except Exception as exc: - idx = futures[future] + pos = futures[future] print( - f" warning: worker failed for {work_items[idx][1]}: {exc}", + f" warning: worker failed for {work_items[pos][1]}: {exc}", file=sys.stderr, flush=True, ) done_count += 1 diff --git a/graphify/llm.py b/graphify/llm.py index 335e1b3f..c0d294a5 100644 --- a/graphify/llm.py +++ b/graphify/llm.py @@ -721,7 +721,12 @@ def _parse_llm_json(raw: str) -> dict: else: stripped = after_fence.strip() try: - return json.loads(stripped) + parsed = json.loads(stripped) + if isinstance(parsed, dict): + return parsed + # Top-level array/scalar (common LLM output) is not a usable graph + # fragment; fall through to the next strategy rather than returning a + # non-dict that callers will try to subscript (e.g. result["input_tokens"]). except json.JSONDecodeError: pass # Strategy 2: extract the first balanced JSON object found anywhere in @@ -751,7 +756,10 @@ def _parse_llm_json(raw: str) -> dict: depth -= 1 if depth == 0: try: - return json.loads(stripped[start : i + 1]) + parsed = json.loads(stripped[start : i + 1]) + if isinstance(parsed, dict): + return parsed + break except json.JSONDecodeError: break print( diff --git a/graphify/tree_html.py b/graphify/tree_html.py index 3d825add..94b10df4 100644 --- a/graphify/tree_html.py +++ b/graphify/tree_html.py @@ -549,7 +549,7 @@ def emit_html( ) -> str: # Escape sequences so embedded JSON cannot break out of the #