Skip to content

Escape untrusted values rendered into issue/PR bodies - #9

Open
schenksj wants to merge 4 commits into
mainfrom
vulnfix/output-injection
Open

Escape untrusted values rendered into issue/PR bodies#9
schenksj wants to merge 4 commits into
mainfrom
vulnfix/output-injection

Conversation

@schenksj

Copy link
Copy Markdown
Contributor

Escape untrusted values rendered into issue/PR bodies

Neutralizes two spots where finding-derived text is rendered into GitHub issue/PR markdown without escaping, plus the same small config.py prerequisite build fix. From the cross-model /vulnhunt evaluation (eval_rep/); developed test-first with the existing suites kept green.

Prerequisite — config.py currently doesn't parse

Commit aaad2ea. vulnhunter-agent/agent/config.py has an unclosed paren — the token_path_prefixes=tuple(...) generator is missing its ), before max_comment_pages=int( — so import agent.config raises SyntaxError and the agent package can't be imported. Adds the missing ), and a small regression test (tests/test_config.py).

This one-line prerequisite also appears on vulnfix/agentic-sdk-hardening and vulnfix/verify-mode-scoping; whichever merges first applies it.

CANON-21 — Constrain marker values in issue-body HTML comments (CWE-79)

Commit 2615c32. vulnhunter-agent/agent/issues_render.py substitutes VULN_ID (and RESULTS_DIR_NAME) verbatim into HTML-comment markers like <!-- vulnhunt-finding-id: {VULN_ID} -->. These are intentionally left un-escaped so the markers stay machine-parseable, but a value containing --> would close the comment early.

  • Change: add _sanitize_marker_value() restricting marker values to [A-Za-z0-9._-] — exactly the charset the downstream parsers already accept (VULN-\d{3}, ..._VULNHUNT_RESULTS_[0-9A-Za-z._-]+) — applied to VULN_ID, RESULTS_DIR_NAME, and (defensively) IDEMPOTENCY_KEY. This preserves marker parsing rather than html.escape, which would break the regexes.
  • Test: vulnhunter-agent/tests/test_issues_render.py::TestMarkerCommentBreakout. Fails on current code, passes after.

CANON-44 — Escape residual-risk entries in the delivery body (CWE-79)

Commit 4949b3a. vulnhunter-fix/vulnhunter_fix/delivery.py renders each residual_vectors entry verbatim into the ## Residual Risk bullets. The existing guards reject vague/empty phrasing but don't escape markup.

  • Change: add _escape_residual_entry() (html.escape(quote=False) plus backslash-escaping of ` [ ], matching the codebase's existing _sanitize_for_issue_body convention) and apply it in the bullet join. Ordinary prose renders unchanged.
  • Test: vulnhunter-fix/tests/test_hand_wave_guard.py — a markup/link payload is escaped in the output. Fails on current code, passes after.

Verification

  • vulnhunter-agent: 1113 passed, 1 skipped
  • vulnhunter-fix: 548 passed, 9 skipped
  • Discrimination evidence recorded for both findings.

🤖 Generated with Claude Code

schenksj added 3 commits July 18, 2026 09:41
…nfig.py

Prerequisite: agent package did not parse, blocking all tests.

Co-Authored-By: Claude Code (VulnFix)
f.id and results_dir_name are stamped verbatim into the
<!-- vulnhunt-finding-id: ... --> / <!-- vulnhunt-results-dir: ... -->
footer markers. Both are attacker-influenced LLM/scan-README data, so a
value containing '-->' closed the comment and injected arbitrary
markdown/HTML into the rendered GitHub issue (CWE-79/CWE-116).

Restrict marker values to a strict identifier charset [A-Za-z0-9._-] --
the same class the downstream parsers accept -- so a breakout is
impossible while legitimate ids/dir-names survive intact.

Co-Authored-By: Claude Code (VulnFix)
residual_vectors entries are LLM/finding-derived and were interpolated
verbatim into the '## Residual Risk' markdown appended to the PR/issue
body. The hand-wave / consistency guards reject vague or empty entries
but never escape, so an entry could inject raw HTML (<script>) or a
markdown link ([x](javascript:...)) into the rendered body
(CWE-79/CWE-116).

Neutralize each entry via html.escape + backslash-escaping the markdown
link/code metacharacters so metacharacters render as literal text while
benign prose survives readably.

Co-Authored-By: Claude Code (VulnFix)
@schenksj
schenksj requested a review from a team as a code owner July 18, 2026 14:56
…down injection

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@schenksj

Copy link
Copy Markdown
Contributor Author

Adversarial review + fix applied (pushed 49b29e2).

CANON-21 (marker breakout) reviewed solid. CANON-44 review found a gap: _escape_residual_entry escaped angle brackets/backticks/brackets but not newlines, so a multi-line residual_vectors entry broke out of the - {entry} bullet and injected block-level markdown (a forged ## Verification Complete heading + --- rule).

Fix: collapse [\r\n]+ → space in _escape_residual_entry (in addition to existing escaping), so an entry can't span multiple markdown lines. New test_residual_entry_newlines_cannot_inject_block_markdown (RED before, GREEN after). vulnhunter-fix suite: 549 passed, 9 skipped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant