Skip to content

fix(recon,executor): harden JSON parsing, close contamination gaps, fix status_code misparse - #9

Open
iceWhispers wants to merge 4 commits into
cdxiaodong:mainfrom
iceWhispers:main
Open

fix(recon,executor): harden JSON parsing, close contamination gaps, fix status_code misparse#9
iceWhispers wants to merge 4 commits into
cdxiaodong:mainfrom
iceWhispers:main

Conversation

@iceWhispers

@iceWhispers iceWhispers commented Sep 4, 2026

Copy link
Copy Markdown

Summary

  • Recon-to-test JSON parsing: _extract_json (handlers.py, validator.py) now scans for every top-level, bracket-balanced JSON span and picks the final span matching the expected schema, instead of naive first-{/last-} slicing. Fixes recon_invalid/json_parse_failed false positives when the model emits prose, markdown fences, a malformed draft, or multiple JSON objects before the valid final one.
  • Recon handler now filters extracted endpoints through workspace scope before writing endpoints.json, and writes a structured recon/status.json gate that the test stage checks before running.
  • Test handler checks findings for scope membership and provenance against recon-derived endpoints; out-of-scope or non-recon-derived findings are marked CONTAMINATED/false_positive.
  • complete_tool_call() no longer misreads stray 3-digit numbers (e.g. the 443 in a Cloudflare Alt-Svc: h3=":443" header) as an HTTP status code — status_code is now only set from a genuine HTTP/x.y NNN status line or output that is entirely a bare status code. Root-caused via a live finding (test-d880dadd) whose provenance recorded status_code=443 for a response that actually returned 200.
  • _provenance_for now records the connection port as its own field (separate from status_code), with matching validation in has_confirmable_provenance.
  • SkillLoader hardening: rejects duplicate YAML keys in a skill's frontmatter, validates description/severity_focus types and values (not just presence), and fixes a real cross-phase false-positive where duplicate-name detection could silently exclude a legitimate skill in the requested phase because of an unrelated same-named skill in a different phase. SkillLoader.issues (previously write-only, read nowhere) is now surfaced into the recon/test stages' existing caveats/summary output.
  • Filled in real, vulnerability-specific L1/L2/L3 methodology and evidence-bar content for 5 skills (command-injection, deserialization, file-upload, info-disclosure, path-traversal) whose recently-added ## 三层测试模型 / ## 证据要求 sections were empty headers with no body.
  • Added English-language recon/test/validation prompts alongside the existing Chinese ones.
  • .gitignore: ignore engagements/ so real per-target recon/finding artifacts don't end up in git history.

Test plan

  • pytest tests/ — 1142 passed, 3 skipped
  • No target contacted; all new tests use fake/canned executor output

🤖 Generated with Claude Code

https://claude.ai/code/session_01VtrXzdKuRt5psr7Xd8VrFw

icewhispers and others added 4 commits September 4, 2026 14:58
- _extract_json (handlers.py + validator.py) now scans for every top-level,
  bracket-balanced JSON span instead of naive first-`{`/last-`}` slicing, and
  picks the final span matching the expected schema (endpoints/findings/
  result). Fixes recon_invalid/json_parse_failed false positives when the
  model emits prose, markdown fences, a malformed draft, or multiple JSON
  objects before the valid final one.
- recon handler: filters extracted endpoints through workspace scope before
  writing endpoints.json, and now writes a structured recon/status.json gate
  (valid/recon_invalid + structural_errors) that the test stage checks before
  running.
- test handler: findings are checked for scope membership and provenance
  against recon-derived endpoints; out-of-scope or non-recon-derived findings
  are marked CONTAMINATED/false_positive instead of being trusted as-is.
- Add English-language recon/test/validation prompts alongside the existing
  Chinese ones.
- Add regression tests for the JSON-parsing fix (clean/fenced/prose-prefixed/
  malformed-then-corrected/multiple-candidates/invalid) and for the WAR-RO
  contamination scenario (test-60889214).
- .gitignore: ignore engagements/ so real per-target recon/finding artifacts
  don't end up in git history.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtrXzdKuRt5psr7Xd8VrFw
complete_tool_call()'s status-code regex scanned the *entire* tool output for
any bare 3-digit number in [100,599] and took the last match, regardless of
whether a real "HTTP/x.y NNN" status line already matched earlier. On
Cloudflare-fronted targets this reliably misfires: Cloudflare responses
commonly carry an `Alt-Svc: h3=":443"` header, and "443" sorts after the real
status line, silently overwriting it.

Root-caused via finding test-d880dadd (workspace/findings.json, local/
gitignored — not touched here), whose provenance recorded status_code=443
for a war-robr.com.br GET that actually returned 200.

status_code is now only ever set from an unambiguous signal: a genuine
HTTP/x.y NNN status line (last one, so redirect chains still resolve to the
final status), or output that consists of nothing but a 3-digit code (the
`curl -w '%{http_code}'` idiom). Anything else — a port, a byte count, a
header value — is left None rather than guessed.

Adds 6 regression tests in tests/test_executor.py, including one that
reproduces the exact Alt-Svc/443 misfire.

No target was contacted while investigating or fixing this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtrXzdKuRt5psr7Xd8VrFw
_provenance_for() now captures the connection port (explicit from the URL,
defaulting to 443/80 by scheme) as its own "port" field, instead of leaving
port information implicitly conflated with status_code — which is exactly
the confusion the executor.py Alt-Svc/443 regex bug produced. findings.py's
has_confirmable_provenance() validates the new field the same way it already
validates status_code (must be an int in range, not a bool), treating it as
optional so existing provenance records without a "port" field still pass.

Adds regression coverage: a provenance record correctly separates a real
HTTP status from a port number even when both "443" and a status code are
present in the same tool output, and a provenance record with no resolvable
HTTP status is correctly treated as incomplete rather than guessed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtrXzdKuRt5psr7Xd8VrFw
…gaps

Reviewed the 5 recently frontmatter-migrated skills (command-injection,
deserialization, file-upload, info-disclosure, path-traversal) and the
SkillLoader that gates them:

- SkillLoader.load() now rejects duplicate YAML keys in a skill's frontmatter
  (a custom yaml.SafeLoader subclass that raises instead of PyYAML's default
  of silently keeping the last value), validates description/severity_focus
  types and values (severity_focus must be one of the real Severity enum
  values), in addition to the existing name/phase checks.
- Fixed a real cross-phase false-positive: duplicate-name detection used to
  register every scanned file's name globally *before* filtering by phase,
  so a same-named skill belonging to an unrelated phase could silently
  exclude the skill actually being loaded for the requested phase. Duplicate
  detection is now scoped to the phase being loaded.
- SkillLoader.issues was write-only — populated but never read anywhere in
  the codebase, so a skill silently failing to load left zero trace for a
  human running `cain-agent run`. The recon and test handlers now surface
  any issues raised while rendering their phase's skills into the existing
  StageResult caveats/summary mechanism.
- All 5 migrated skills had two added section headers (`## 三层测试模型`,
  `## 证据要求`) with no body content — verified by diffing against the
  frontmatter migration and confirmed empty in the files on disk. Filled in
  real, vulnerability-class-specific L1/L2/L3 methodology and evidence-bar
  content for each, mirroring the depth of the existing sqli skill. The
  original restrictions (renamed 注意事项 -> 禁止事项, content unchanged)
  were already intact and are preserved as-is.

Adds regression tests: duplicate YAML key rejection, same name reused across
different phases is correctly *not* a conflict, invalid severity_focus /
non-string description are rejected, and a recon run with a broken skill
file surfaces "技能加载问题" in its caveats and summary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtrXzdKuRt5psr7Xd8VrFw
cdxiaodong added a commit that referenced this pull request Sep 5, 2026
- 新增 _jsonspans.iter_json_spans:字符串/转义感知的顶层括号平衡 span
  扫描,混合嵌套({a:[1]})正确闭合,未闭合起点放弃
- handlers/validator/orchestration 三处 _extract_json:整体解析优先,
  失败后取最后一个可解析 span(模型终稿在末尾)——修复多对象输出
  (草稿+终稿)/markdown 围栏/写坏草稿导致的 json_parse_failed 假阳性
- validator/orchestration dict 版:末尾非 dict 数组不顶替前面的合法 dict
- SkillLoader.issues 激活:降级原因(目录缺失/零命中)注入 recon/test
  阶段 caveats 与 summary,不再 write-only
- 测试 +15 例(span 扫描 5 + 三版提取 8 + caveats 端到端 2),1100 绿

issue #9 其余项(status_code 误解析/provenance 字段/status.json 门控/
CONTAMINATED 标记/技能内容填充)属于 fork 侧新功能面,main 无对应代码,
已留评论邀请 PR;本 commit 只采纳 main 上可验证的正确性修复
@cdxiaodong

Copy link
Copy Markdown
Owner

感谢这份极其扎实的审查报告 — 逐项核对后部分已在 main 落地,其余大部分内容我们非常希望以 PR 形式接收

已在 main 修复(eb4dff3,本条 issue 保持 open)

  1. JSON 解析硬化(属实,已修):三处 _extract_json(handlers / validator / multi_agent.orchestration)原实现确实是 naive 首-{/末-} 切片。已新增 _jsonspans.iter_json_spans(字符串/转义感知的顶层括号平衡扫描),多对象输出取末尾终稿、markdown 围栏与写坏草稿不再产生 json_parse_failed 假阳性;dict 版末尾数组不顶替前面合法 dict。+15 例测试。
  2. SkillLoader.issues write-only(属实,已激活):目录缺失/零命中的降级原因现注入 recon/test 阶段 caveats 与 summary。

属实但未在本轮处理

  • 五个技能(command-injection / deserialization / file-upload / info-disclosure / path-traversal)的三层测试模型与证据要求章节为空 — 确认属实,已排入任务队列补齐。

需要澄清/邀请 PR 的部分

报告中的 complete_tool_call()_provenance_forhas_confirmable_provenancerecon/status.json 门控、CONTAMINATED 标记、severity_focus 校验、英文 prompt 等,当前 main 上不存在这些代码面 — 它们看起来是您 fork 上的完整实现(测试计划显示 1142 passed,远超 main 的 1100)。

其中 Alt-Svc h3=":443" 被误读为 status_code=443 的 root-cause 分析非常有价值,但该解析逻辑本身不在上游代码里。强烈建议把整套实现(尤其是 status_code 误解析修复、端口独立字段、scope 过滤门控、CONTAMINATED 污染检测)以 PR 形式发过来 — 您已有完整实现与测试,直接移植远比重写可靠。PR 指引见 docs/community.md

(本条 issue 按维护策略暂不关闭,等 PR 与技能补齐后一并收口)

@cdxiaodong

Copy link
Copy Markdown
Owner

更正:上一条评论误将本 PR 当作 issue 处理 — 已确认 #9 是带完整实现的 PR(1782+/109-,25 文件,4 commits)。

状态更新:

  • 上一条中的「JSON 解析硬化」与「SkillLoader.issues 激活」两项,已由维护者在 main 侧独立落地(eb4dff3,+15 测试,1100 绿)——与您 PR 中对应部分思路一致,review 时会以您的实现为准对齐差异
  • 其余(executor 的 status_code 误解析修复、scope 过滤门控、CONTAMINATED 污染检测、五技能内容填充、bench fixture)将基于您的 diff 逐文件深度 review
  • 本 PR 保持 open,合并决定由仓库所有者拍板

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.

2 participants