mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-30 10:16:45 +00:00
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.
This commit is contained in:
+4
-3
@@ -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
|
||||
|
||||
+10
-2
@@ -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(
|
||||
|
||||
@@ -549,7 +549,7 @@ def emit_html(
|
||||
) -> str:
|
||||
# Escape </script> sequences so embedded JSON cannot break out of the
|
||||
# <script> tag, and HTML-escape values that land in <title>/<h1>.
|
||||
data_json = json.dumps(tree, ensure_ascii=False, separators=(",", ":")).replace("</", "<\\/")
|
||||
data_json = json.dumps(tree, ensure_ascii=True, separators=(",", ":")).replace("</", "<\\/")
|
||||
return _HTML_TEMPLATE.format(
|
||||
title=_html.escape(title),
|
||||
header=_html.escape(header),
|
||||
|
||||
Reference in New Issue
Block a user