Skip to content

feat(examples): add skills code review agent - #209

Open
k0mondor wants to merge 11 commits into
trpc-group:mainfrom
k0mondor:feat/skills-code-review-agent-clean
Open

feat(examples): add skills code review agent#209
k0mondor wants to merge 11 commits into
trpc-group:mainfrom
k0mondor:feat/skills-code-review-agent-clean

Conversation

@k0mondor

Copy link
Copy Markdown

Summary

This PR adds a runnable code review agent prototype under examples/skills_code_review_agent/ and a formal code-review skill under skills/code-review/.

The example supports diff / repo / fixture input, structured diff parsing, deterministic review rules, filter governance, controlled script execution, SQLite persistence, and JSON/Markdown report generation.

Included

  • examples/skills_code_review_agent/
  • skills/code-review/
  • CLI for --diff-file, --repo-path, and --fixture
  • deterministic rules for security, async, resource leak, missing tests, secret leakage, and DB lifecycle issues
  • dedupe and confidence-based routing
  • filter decisions before execution
  • timeout and output truncation for controlled script runs
  • redaction before report/database writes
  • SQLite schema + repository
  • sample outputs and design note

Testing

pytest examples/skills_code_review_agent/tests -q

Covered scenarios:

  • clean diff
  • security issue
  • async/resource leak
  • database lifecycle issue
  • missing tests
  • duplicate finding
  • sandbox failure
  • secret redaction

@CongkeChen

Copy link
Copy Markdown
Contributor

AI Code Review

我已经了解了所有上下文,现在来写审查报告。

发现的问题

🚨 Critical

  • examples/skills_code_review_agent/agent/tools.py:112-175:sandbox 执行未按 runtime 做真正隔离,filter 又默认放行 container 运行时,构成与安全声明背离的治理缺口
    • execute_skill_script 忽略 runtime 参数,对所有运行时(含 container/e2b/cube)都用 subprocess.run 直接在宿主进程执行脚本;而 evaluate_invocations 仅在 runtime == "local" 时降级为 NEEDS_HUMAN_REVIEWcontainer 等被默认 ALLOW。结果是被审查 diff 中的 skill 脚本会在宿主机直接执行,但报告/文档把 container 标记为生产安全沙箱。建议:对非 local 运行时走真实容器/远程执行路径,或在 filter 中对未真正隔离的执行一律标为 NEEDS_HUMAN_REVIEW 并在报告中显式注明“未隔离执行”。

⚠️ Warning

  • examples/skills_code_review_agent/agent/tools.py:107build_skill_run_payload 用 f-string 把 diff_file 直接拼进 shell 命令串,路径含空格或 shell 元字符时会破坏命令甚至注入

    • 当前 "command": f"python scripts/{script_name} --diff-file {diff_file} > {output_file}" 未对 diff_file 加引号或转义。该函数现仅被测试调用、属预留契约,但一旦被 skill_run 真实执行,diff_file(来自用户 --diff-file/--fixture/临时物化路径,可能含空格)会触发命令解析错误或注入。建议对路径加单引号转义或改用 argv 形式。
  • examples/skills_code_review_agent/sample_outputs/review_report.json:61-64examples/skills_code_review_agent/README.md:120-175:提交的样例产物与文档中硬编码了开发者本机绝对路径

    • sample_outputs/review_report.jsoncommand/stdout 字段含 C:\Users\32349\...\python.exe 及仓库绝对路径;README/DESIGN 等用 file:///c:/Users/32349/trpc-agent-python-fork/... 链接引用文件。这些路径在其他机器上无效,链接不可跳转,样例产物也不可复现。建议改为仓库相对路径或重新生成可移植样例。
  • examples/skills_code_review_agent/agent/agent.py:135-149:sandbox 脚本输出未被解析回结构化 findings,与脚本契约脱节

    • execute_skill_script 捕获了 stdout/stderr,但循环里只调用 _sandbox_run_findings,而它仅在 failed/timed_out 时产出“执行失败”类 finding;脚本正常输出的 lint/test JSON(契约里定义的字段)被丢弃,未参与审查结论。这意味着 run_linters.py 检出的 eval/shell=True/verify=False 不会进入最终 findings。建议解析脚本 stdout JSON 并合并为 findings 或至少写入报告。

💡 Suggestion

  • examples/skills_code_review_agent/src/redactor.py:17-22sk-[A-Za-z0-9]{8,} 因字符类不含 -,无法匹配 sk-test-... 这类带连字符的 key;当前靠前一条 api_key 模式兜底。若要独立覆盖纯 sk- 场景,建议放宽字符类或与 rule_engine 的 sk-(?:proj-)?[A-Za-z0-9]{12,} 对齐。

总结

整体新增的是一个结构清晰、测试覆盖较全的示例 agent,主要风险集中在治理声明与实际执行不符(Critical)以及若干可移植性/契约缺陷(Warning)。其中 sandbox 未真正隔离却被标记为安全沙箱是需要修复的核心问题;其余为建议性改进。

测试建议

  • 补充一个用例:当 runtime="container" 且 diff 命中 eval/shell=True 时,断言 skill 脚本输出(run_linters.py 的 warnings)确实进入了最终 findings/结论,覆盖“脚本输出→findings”这一目前未验证的路径。
  • 补充一个用例:build_skill_run_payloaddiff_file 含空格时生成的 command 能被正确分词/转义(或验证当前实现已加引号),避免该契约被采用时引入回归。

}


def execute_skill_script(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sandbox 未按 runtime 真正隔离,container 运行时默认放行

execute_skill_script 忽略 runtime 参数,对所有运行时(含 container/e2b/cube)都用 subprocess.run 直接在宿主执行;而 evaluate_invocations 仅在 runtime=="local" 时降级为 NEEDS_HUMAN_REVIEW,container 等被默认 ALLOW。建议对非 local 运行时走真实容器/远程执行,或在 filter 中一律标为 NEEDS_HUMAN_REVIEW 并在报告中显式注明未隔离执行。

@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@f2a34ff). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff             @@
##             main        #209   +/-   ##
==========================================
  Coverage        ?   88.43489%           
==========================================
  Files           ?         491           
  Lines           ?       46035           
  Branches        ?           0           
==========================================
  Hits            ?       40711           
  Misses          ?        5324           
  Partials        ?           0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

):
task.add_filter_decision(decision)
if decision.decision.value == "allow":
sandbox_run = execute_skill_script(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对于skill,框架会自动在沙箱中运行,这里是否有必要主动调用?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢您提醒,上一版确实由 Agent 在 Filter 判断后直接管理 workspace 和脚本执行,造成了 Agent 与 Skill 框架职责重复,已调整为:Agent 只构造结构化的 skill_run 请求;在 container 模式下,由框架的 SkillToolSet/skill_run 负责 Skill staging、workspace、输入映射、沙箱执行和超时控制,Agent 不再自行创建 runtime 或直接运行容器命令。
dry-run/fake-model 模式因为没有模型生成 tool call,所以确定性测试流程仍会调用同一个公开的 skill_run 接口,但这里只模拟 tool dispatch,不再自行实现 sandbox,请帮忙review这样的职责划分是否符合框架预期

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已完整审阅 pr.diff(约 7400 行,新增一个 skills_code_review_agent 示例及仓库级 skills/code-review skill)。下面是落在 diff 变更内、可定位验证的问题。

发现的问题

🚨 Critical

未发现 Critical 级别问题。

⚠️ Warning

  • examples/skills_code_review_agent/src/input_loader.py:47-81load_git_workspace_diff 的两次 subprocess.rungit diff HEAD 与回退分支)均未设置 timeout

    • 整条评审链路对脚本执行严格限超时(SCRIPT_TIMEOUT_SECONDS)和输出截断,但拉取 diff 的 git 子进程无超时;当仓库巨大、git 等待输入或被锁时,整个 review 会无限期阻塞。建议为两次调用补 timeout= 并在 TimeoutExpired 时转换为 RuntimeError
  • examples/skills_code_review_agent/src/input_loader.py:42-81load_git_workspace_diff 使用 --binary 产出二进制补丁,但 diff_parser.parse_unified_diff 不识别 GIT binary patch/delta/literal 块。

    • 二进制块的 base64 行会被当作普通 +/- 行解析,污染 added_lines_count 并可能触发误报;对纯代码评审也无必要。建议去掉 --binary,仅用文本 diff。

💡 Suggestion

  • examples/skills_code_review_agent/agent/agent.py:208-211report_jsonreport_markdown 已计算,紧接着 write_report_files(report, ...) 内部又各调用一次 build_report_payload/render_markdown_report,重复劳动且存在两处来源不一致的风险。建议让 write_report_files 接收已渲染产物,或直接复用已计算的变量写入磁盘与落库。

  • examples/skills_code_review_agent/agent/agent.py:223sandbox_run.status != sandbox_run.status.SUCCEEDED 依赖 enum 成员访问同名成员的隐式行为,虽正确但极易被后续维护者误判为 bug 而改坏。建议改为显式的 SandboxRunStatus.SUCCEEDED 比较。

总结

整体链路(解析、规则、Filter、沙箱、脱敏、落库、报告)设计完整且自测覆盖较好,未发现安全漏洞或核心逻辑错误。主要风险集中在 diff 加载子进程缺少超时与 --binary 解析不健壮两个稳定性问题,建议修复后再合入。

测试建议

  • 补充一个用例:构造一个会触发 git 命令长时间挂起的场景(或 mock subprocess.run 注入 TimeoutExpired),验证 load_git_workspace_diff 能在超时后抛出而非无限阻塞。
  • 补充一个含二进制文件变更的 --repo-path diff 用例,验证 parser 不会把二进制块计入 added_lines_count 或产生误报。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

示例输出证实了具体化的 diff 文件位于 sample_outputs/skill_inputs/ 下。对于 security_issue 样本,它包含了 eval(shell=True(没有实际密钥),但对于 secret_redaction 固定测试用例(fixture),该文件将包含 sk-test-1234567890abcdef。这是一个真正的密钥持久化泄露。

我已经完成了分析,现在开始撰写审查报告。

发现的问题

🚨 Critical

  • examples/skills_code_review_agent/agent/agent.py:198-204:物化的 diff 输入以明文形式持久化了密钥,且从未被清理。
    • _materialize_diff_input 在执行沙箱脚本前将 task.review_input.diff_text(未脱敏)原样写入 output_dir/skill_inputs/{task_id}.diff,且整个流程没有任何删除或脱敏处理。对于 secret_redaction.diff 这类输入,sk-test-... / Bearer ... 等真实凭证会以明文残留在磁盘上,直接违背 RULES.md 中“Redact secrets before persistence and reporting”的安全契约(报告与 DB 已脱敏,唯独该中间文件未脱敏)。建议在写入前对 diff 文本执行 redact_text,或在沙箱脚本执行完成后删除该文件(脚本通过 --diff-file 读取,仅需在执行后清理)。
    diff_file = inputs_dir / f"{task.task_id}.diff"
    diff_file.write_text(task.review_input.diff_text, encoding="utf-8")
    return diff_file

⚠️ Warning

  • examples/skills_code_review_agent/src/redactor.py:87-94redact_task 未脱敏 review_input.diff_text,返回的 task 仍携带明文密钥。

    • 函数名为 redact_task 且只替换了 findings/sandbox_runs/error_message,但 ReviewInput.diff_text 仍保留原始密钥。run_review_task 把脱敏后的 task 返回给调用方(run_agent.pymain 也返回该 task),任何下游消费 task.review_input 的代码都会拿到明文凭证,与“task 已脱敏”的语义不符。建议对 review_input 也返回一份 diff_text=redact_text(...) 的副本(repo_path 等非敏感字段可保留)。
  • examples/skills_code_review_agent/skills/code-review/scripts/run_linters.py:33-39(同 skills/code-review/scripts/run_linters.py:33-39):lint 检测覆盖整段 diff 而非仅新增行,可能导致定位降级。

    • 脚本对整个 diff_text 检测 eval(/shell=True/verify=False,包含上下文与删除行;而 agent._linter_warning_to_finding 通过 _find_added_line_evidence 在新增行中反向查找 needle。若命中只出现在删除/上下文行中的 token,将找不到对应新增行,最终退化为 file=changed_paths[0], line=None 的低精度 finding。建议脚本只扫描 + 起始的新增行,或在 evidence 中带出原始行以供精确定位。

💡 Suggestion

  • examples/skills_code_review_agent/agent/tools.py:283-299_sanitize_display_value 对每个字符串都构造 Path 并调用 _resolve_project_root()(含 resolve() 磁盘IO),在 _sanitize_json_value 递归遍历较大 sandbox stdout JSON 时存在不必要的性能开销。可缓存 project root 并先用 "/" not in value and "\\" not in value 等廉价判断短路,避免对普通文本逐个做路径解析。

总结

整体实现结构清晰、脱敏与沙箱治理链路基本闭合,但存在一个必须修复的 Critical:物化的 diff 中间文件以明文持久化密钥且无清理,违背自身安全契约;另有 redact_task 漏脱敏 review_input 与 lint 脚本定位精度两处建议修复项。

测试建议

  • 补充断言:secret_redaction 场景下 output_dir/skill_inputs/<task_id>.diff 不含明文密钥(或运行后被删除),且 run_review_task 返回的 task.review_input.diff_text 已被脱敏。

def _count_by_disposition(findings: list, disposition: FindingDisposition) -> int:
"""Count findings by presentation bucket."""

return sum(1 for finding in findings if finding.disposition == disposition)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

物化的 diff 中间文件以明文持久化密钥且无清理

_materialize_diff_input 将未脱敏的 task.review_input.diff_text 直接写入 skill_inputs/{task_id}.diff,全流程无删除或脱敏,真实凭证会以明文残留磁盘,违背报告/DB 已脱敏的安全契约。建议写入前对 diff 文本执行 redact_text,或在沙箱脚本执行完成后删除该文件。

@k0mondor k0mondor Jul 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已将原始 diff 从 output_dir/skill_inputs/ 移到短生命周期的 TemporaryDirectory Skill 脚本执行完成后会自动清理,发生异常时也会通过 context manager 清理,不再把未脱敏 diff 作为输出产物保留
同时,返回的 task.review_inputparsed_diff 也会进行深度脱敏,报告和数据库继续只保存脱敏内容,原始输入仅保留 SHA256 用于审计,并补充了回归测试,验证临时文件会被删除,并且 task、报告和数据库中均不包含明文密钥

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经掌握了所有发现的问题。让我来写最终的审查结论。

发现的问题

⚠️ Warning

💡 Suggestion

  • examples/skills_code_review_agent/src/redactor.py:17-40:脱敏正则对非 Bearer/无 key 名前缀的裸密钥值覆盖不足
    • AUTH_HEADER = "super-secret-token-value"(无 Bearer 前缀)不会被任何规则脱敏;当前依赖 rule engine 不生成对应 finding 才未泄露。若未来 evidence/summary 出现此类裸值,存在泄露风险,可补一个"赋值号右侧长字符串"的兜底脱敏规则。

总结

整体未发现安全或逻辑层面的阻塞性问题:子进程均使用参数列表无 shell=True、不可信 diff 不参与命令构造、输出有截断与脱敏、SQLite 用参数化查询、容器/非已实现 runtime 走过滤策略拦截。主要风险集中在 --dry-run/--fake-model 开关语义与实现不符的描述性误导,以及 run_tests.py 对异常 diff 头的健壮性不足,二者均非必须修复。

测试建议

暂无额外测试建议;现有测试已覆盖密钥脱敏、过滤决策、容器失败回退等关键路径。可考虑补一条含 diff --git a/tests/x c/tests/y(无 b/)的用例以验证 run_tests.py 的健壮性。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

I now have enough verified context to produce the review.

发现的问题

🚨 Critical

  • examples/skills_code_review_agent/agent/agent.py:60-187:主流程无异常兜底,失败任务不会被落库或标记 FAILED
    • run_review_taskload_review_input / parse_unified_diff / run_rule_engine / 沙箱执行任何一步抛异常时,异常直接上抛,task.status 永远不会置为 ReviewStatus.FAILEDerror_message 不会被设置,SQLite 也不会写入任何记录。ReviewStatus.FAILEDtask.error_message、telemetry 的 exception_distribution(仅依赖 task.error_message)实际上是不可达的死逻辑,违背“失败不崩、可审计”的验收目标。建议在 try/except 中捕获异常,设置 status=FAILEDerror_message,仍尝试写出报告并落库(或至少落一条失败记录)。

⚠️ Warning

  • examples/skills_code_review_agent/agent/tools.py:38-40,219-223:local 回退环境白名单包含 PYTHONPATH,削弱了宣称的最小化环境

    • 白名单将宿主 PYTHONPATH 一并透传给子进程,可被环境劫持标准库/第三方模块导入路径,与 README/文档强调的“environment-variable allowlist”“最小化”目标相悖。PATH 同样透传也带来一定的非预期搜索路径风险。建议 local 回退环境显式剔除 PYTHONPATH(或仅保留空值),并尽量收紧 PATH
  • examples/skills_code_review_agent/src/filter_policy.py:68-81,104-108:dangerous/network 拦截分支针对固定命令恒不可达,治理声明与实现不符

    • _contains_token 只检查 invocation.command,而该命令始终是硬编码的 ["python","scripts/*.py","--diff-file","work/inputs/review.diff"],永远不会包含 rm -rf/curl/requests.get( 等 token,dangerous_commandnetwork_not_allowed 两条 DENY 分支事实上永不触发;Filter 也未对 diff 内容或脚本行为做任何网络/危险命令治理。ACCEPTANCE_CHECKLIST.md 标准 7 声称“高风险脚本必须先经过 Filter 决策”,但实际治理仅命中 forbidden path。建议要么删除无效分支并修正文档声明,要么真正对 diff/脚本内容扫描危险与网络 token。
  • examples/skills_code_review_agent/src/redactor.py:33-39:opaque-value 脱敏规则过度激进,可能误伤正常代码与报告可读性

    • 模式 4 会把任意 ≥24 字符、含字母与数字的引号字符串替换为 [REDACTED],不区分上下文,正常常量(如长 UUID、文件指纹、base64 示例)会被脱敏,导致报告/数据库内容失真。建议增加键名上下文限定(如要求键名含 secret/token/credential/password 等)或提高长度/字符熵门槛,降低误报。

💡 Suggestion

  • examples/skills_code_review_agent/src/diff_parser.py:147-184_parse_hunk_line 对空串、未知 marker 一律 return None 静默跳过,遇到畸形 hunk 时不会报错也无法察觉;建议对非 \\ No newline 的未知行至少记录或抛出,避免解析静默丢失行,影响后续规则命中行号准确性。

总结

整体链路设计清晰、确定性规则与脱敏/落库闭环基本可用;但主流程缺少异常兜底,使得失败任务无法落库、FAILED 状态与 exception_distribution 成为死逻辑,属于必须修复的 Critical。其余为 local 环境白名单、Filter 死分支与脱敏误伤等 Warning 级问题,建议一并修正以兑现文档中的安全/治理承诺。

测试建议

  • 建议补充:当 load_review_input/parse_unified_diff/run_rule_engine 抛异常时,任务状态为 failederror_message 非空、exception_distribution 计入 pipeline_error 且 SQLite 有失败记录的用例。
  • 建议补充:含 PYTHONPATH 的宿主环境下 local 回退不应继承该变量的回归断言,以及含 curl/rm -rf token 的 diff 是否被 Filter 正确处置的断言(用以暴露当前死分支)。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经审查了完整的 diff 和相关的仓库上下文,以下是我的审查结果。

发现的问题

🚨 Critical

  • examples/skills_code_review_agent/agent/tools.py:627-632:容器运行时未绑定 inputs 目录,host:// diff 输入无法在容器内访问
    • _create_workspace_runtimehost_config 只 bind 了 skills 目录,没有绑定 inputs。build_skill_run_payloadsrc=host://<主机绝对路径>dst=work/inputs/review.diff 注入 diff。在容器运行时下,ContainerWorkspaceFS._stage_host_input 只有当 inputs_host_base 非空(即 DEFAULT_INPUTS_CONTAINER 被 bind)时才走 bind-mount 解析,否则回退到 _put_directory,而 _put_directory 对单文件会 tar.add 整个目录并把 review.diff 当目录处理,输入不会落到 work/inputs/review.diff,导致脚本读不到文件、run_linters 无 warning。对照 examples/skills_with_container/agent/tools.py:43-45 正确绑定了 inputs_host,本例缺这一绑定。建议参照该示例把 diff 输入放到一个 inputs_host 目录并加入 Binds,或改用 workspace:///artifact 输入。
    • ...
      host_config = {"Binds": [f"{skill_root}:{DEFAULT_SKILLS_CONTAINER}:ro"]}
      kwargs["host_config"] = host_config
      kwargs["auto_inputs"] = True
      return create_container_workspace_runtime(**kwargs)
      ...

⚠️ Warning

  • examples/skills_code_review_agent/src/input_loader.py:67-78git diff HEAD -- <pathspec> 在 pathspec 非法时可能误判为"无 HEAD"而吞掉真实错误

    • 代码以 bad revision 'HEAD' / unknown revision 字符串作为是否回退到无 HEAD 分支的判据。当仓库有 HEAD 但传入非法 changed_paths 时,git diff HEAD -- <badpath> 的 stderr 可能不含这两个串而被当作不可恢复错误抛出(正确),但反过来如果某些 git 版本对 pathspec 错误的 stderr 措辞恰好命中回退条件,就会静默丢弃 pathspec 走 git diff -- 全量比较,得到与请求不符的 diff。建议显式区分返回码与 stderr 关键字,或对 pathspec 做基本校验,避免依赖脆弱的字符串匹配。
  • examples/skills_code_review_agent/src/filter_policy.py:11-19,90-94:网络令牌对受信任的内置脚本产生误拦风险,且字符串匹配易绕过

    • _NETWORK_TOKENShttp://https://requests.get(,并对内置 skill 脚本文本做子串匹配。任何内置脚本注释/字符串里出现 http://requests.get( 都会被 network_not_allowed 拒绝。虽然当前三脚本未触发,但作为默认策略对后续扩展的内置脚本很脆弱(注释中的 URL 即可触发)。同时 diff 中真正不可信的命令部分未单独校验(command 列表固定可信)。建议对受信任的仓库内脚本豁免网络令牌检查,或仅对 invocation.command 做校验。
  • examples/skills_code_review_agent/agent/agent.py:396-405,372-414_linter_warning_to_findingverify=False 未做测试/mock 上下文降级,与规则引擎不一致

    • 规则引擎 rule_engine.py:196 在测试/mock 上下文下会把 verify=False 的 confidence 降到 0.58(路由到 human review);但 skill-script 通道 agent.py:404 固定 0.89(最终 finding)。同一问题经两条路径得到不同 disposition,去重时由于 confidence 不同会互相覆盖,行为不一致。建议在 skill-script 通道也参考路径/上下文做同样的降级,或在去重时统一处置。
  • examples/skills_code_review_agent/agent/agent.py:357-366_sandbox_output_findings 仅解析 run_linters 的 stdout,其它脚本诊断被丢弃

    • parse_diffrun_tests 的 stdout JSON(含 has_security_keywordstest_update_present 等信号)从未被提升为 finding,仅作为审计记录存在;脚本输出与规则引擎之间没有实际联动。若意图是让 skill 脚本补充发现,目前只有 linter 通道有效。建议明确文档说明仅 linter 输出会被提升,或补齐其它脚本输出的处理逻辑。
  • examples/skills_code_review_agent/agent/tools.py:224-237:local 回退用固定 cwd=project_root 运行受信任脚本,但仍把 diff 文件以绝对路径透传,输出可能含主机绝对路径

    • local 模式以 _resolve_project_root(project_root) 为 cwd 执行 invocation.script_path,脚本内部 --diff-file 是主机绝对路径。脚本 stdout 里 diff_file 字段是绝对路径(见 parse_diff.py:31),虽有 _sanitize_output_text/_sanitize_display_value 在记录层做相对化,但去重 key、evidence 等仍可能在写出前携带主机路径信息。建议在脚本输出层即用相对/稳定路径,避免依赖下游清洗覆盖所有出口。
  • examples/skills_code_review_agent/src/storage/repository.py:76-148:子表删除-插入非事务化且无并发保护,重入会丢数据

    • save_reviewfilter_decisions/sandbox_runs/findingsDELETEexecutemany,整个过程在一个连接内但未显式开事务;若 executemany 中途失败,已删除的旧记录不会回滚(SQLite 默认 autocommit 下每条语句各自提交),留下半写入状态。同一 db_path 被并发 run_review_task 调用时(同一 task_id 概率低但不同 task 共享子表无冲突,问题主要在重入)也会互相覆盖。建议用 with connection: 包裹整个保存逻辑保证原子性。
  • examples/skills_code_review_agent/src/storage/repository.py:263-271get_review_bundlef"SELECT * FROM {table_name}" 拼接表名

    • 虽然 table_name 来自内部常量、当前无注入风险,但属于 SQL 拼接模式,后续若把表名来源扩展到外部输入即成漏洞。建议用白名单校验或固定常量映射,避免长期维护隐患。
  • examples/skills_code_review_agent/tests/test_storage_and_report.py:283-344test_container_runtime_dispatches_through_skill_run 仅用 Mock 验证调度,未覆盖真实容器输入解析

    • 该测试 mock 了 run_tool.run_async 的返回,无法发现 Critical 中"容器内 diff 输入无法访问"的问题;test_skill_repository_indexes_root_code_review_skill 等也只校验本地仓库索引。目前没有任何测试真正跑通容器运行时的输入 staging。建议补充一个不依赖 Docker 的容器 FS staging 单测(mock container client)覆盖 host:// 输入解析路径,否则容器路径的输入处理长期无回归保护。

💡 Suggestion

  • examples/skills_code_review_agent/agent/agent.py:470-487rule_engine.py 中 security 模式映射存在重复定义(eval/shell=True/verify=False 的 severity、confidence、title、recommendation 在两处各维护一份且已出现 verify=False 不一致)。建议抽取共享的 warning→finding 映射,避免双源真相 drift。

总结

整体为一个结构清晰、测试覆盖较全的示例工程;最关键问题是容器运行时未绑定 inputs 目录,导致 host:// diff 输入在真实容器中无法被脚本读取,run_linters 等会静默无输出,属必须修复。其余多为边界条件、误拦与一致性隐患。

测试建议

  • 补充容器运行时下 host:// 输入 staging 的单测(mock container client,断言 work/inputs/review.diff 能被解析/拷贝到位),覆盖当前完全未覆盖的容器输入路径。
  • 补充 git diff HEAD 在 pathspec 非法与无 HEAD 两种情况下的区分测试,避免回退逻辑误吞错误。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

已确认:severity_distribution 过滤了 FINDING(第 22 行),而 category_distribution 计算了所有(第 24 行)。这确实不一致。然而,这是一个细微的展示层问题,并非会导致功能故障的正确性错误。子代理将其标记为严重警告(Warning)过于激进——这是仪表盘展示中一个轻微的、外观上的不一致。我会保留该问题,但降低其优先级。

现在我来检查一下 run_tests.py 第 41 行关于路径前缀剥离(path-prefix stripping)的说法。子代理提到了 normalized_path[1] == "/"。重新阅读代码:

for raw_path in parts[2:4]:
    normalized_path = raw_path.replace("\\", "/")
    if len(normalized_path) > 2 and normalized_path[1] == "/":
        normalized_path = normalized_path[2:]

这里检查 normalized_path[1] == "/"。对于 a/tests/x.py,索引 1 是 /。剥离前 2 个字符 → tests/x.py。没问题。对于 tests/x.py(无前缀),索引 1 是 e 而非 /,因此不会进行剥离。没问题。子代理的担忧是合理的,但优先级较低。实际上这里有一个更具体的问题:它只处理单字符前缀 a/b/。这是一个小问题。

现在我来验证一下最关键的声明,即关于多行正则表达式(regex)模式 3 和脱敏器(redactor)实际行为的说法。我无法通过实验进行测试。让我仔细重读一下 redactor.py 的模式:

模式 1(第 17-21 行):(?i)\b(api[_-]?key|token|password|secret)\b(\s*[:=]\s*['\"]?)([^'\"\s]{4,})(['\"]?) —— 值组是 [^'\"\s]{4,},它会拒绝空格。因此 password = "my secret" 不会匹配(在 my 之后、secret 之前有空格;值组停在第一个空格处,得到 my,长度为 2 < 4)。已确认绕过。

规则引擎模式(第 68 行):(?i)\b(api[_-]?key|token|password|secret)\b\s*[:=]\s*['\"][^'\"]{6,}['\"] —— 值是 [^'\"]{6,},允许空格。已确认不对称性。

我现在非常有信心。两个关键(Critical)发现都已核实。让我再检查一下子代理没有深入审查的一个担忧:在 tools.py 中,build_skill_run_payload 通过拼接 f"python scripts/{script_name} --diff-file {WORKSPACE_DIFF_PATH}" 构建 command 字符串。script_name 已经过验证(第 145 行:script_path.name != script_name or script_path.suffix != ".py"),因此没有注入风险。很好——这不是一个问题。

让我检查最后一个潜在问题:tools.py 第 152 行 —— command 是一个 字符串 "python scripts/{script_name} --diff-file {WORKSPACE_DIFF_PATH}",而不是列表。这会被传递给框架的 skill_run 工具。如果框架通过 shell 执行此命令,这可能会是一个注入向量,但 script_name 已经验证为单纯的 .py 文件名,且 WORKSPACE_DIFF_PATH 是一个常量 ("work/inputs/review.diff")。所以是安全的。

我已核实完毕。让我最终确定评审报告。我将报告 2 个 Critical 问题和 1 个 Warning 项(并将 telemetry 的不一致问题合并为优先级较低的 Warning 或 Suggestion)。关于 deduper 的说法站不住脚(该函数确实被用于指纹识别),所以我将其删除。我将把 GitHub token 问题(#1)和多词值问题(#2)合并为一个 Critical 问题,因为它们有相同的根本原因(“脱敏器的模式集是规则引擎模式集的真子集,导致了脱敏缺口”),并有不同的修复方案——但说明中要求除非修复方案有显著不同,否则合并。它们是同一个文件中相邻的模式,根本原因相同。我考虑一下:根本原因是“脱敏器没有覆盖规则引擎检测到的所有凭据形状”。多词值问题和 GitHub token 问题都是这一根本原因的表现。根据指南 #4#9,我应该将它们合并。但修复方法不同(GitHub 使用新模式,多词使用值类变更)。指南 #9 指出,除非修复方法显著不同,否则合并。它们确实不同。我会将它们保留为一条 Critical 项,并在同一个条目中包含两个子修复方案。

发现的问题

🚨 Critical

  • examples/skills_code_review_agent/src/redactor.py:17-47:Redactor 的脱敏模式集是 Rule Engine 检测模式的子集,导致已检测到的密钥泄露到报告与数据库

    • Rule Engine (rule_engine.py:66-109) 会检测 GitHub token(gh[pousr]_...,行 82)以及含空格的引号密钥值([^'\"]{6,} 允许空格,行 68),但 redactor.py 既无 GitHub token 形状脱敏模式,其关键字赋值模式的值组又用 [^'\"\s]{4,}(行 19)拒绝空格。因此当 diff 出现如 GH_TOKEN = "ghp_0123...(40位)"_TOKEN 前无词边界,关键字模式不命中)或 password = "my secret value"(值含空格,{4,} 在第一个空格处中断且首段不足 4 字符)时,Rule Engine 仍会生成 finding 并把该行原文作为 evidence,而 redact_task/redact_report/redact_sandbox_run 全部仅依赖 redact_text,密钥原样写入 review_report.jsonreview_report.md 与 SQLite 库。security_issue.diff 类 fixture 即可触发。建议:① 增补 re.compile(r"\bgh[pousr]_[A-Za-z0-9]{36,}\b") 等所有 Rule Engine 中的形状模式;② 将行 19 的值组改为 [^'\"]{6,} 以与 Rule Engine 一致。
    # redactor.py:17-21 现状(值组拒绝空格)
    re.compile(r"(?i)\b(api[_-]?key|token|password|secret)\b(\s*[:=]\s*['\"]?)([^'\"\s]{4,})(['\"]?)"),
    # rule_engine.py:68 对应检测(值组允许空格)
    re.compile(r"(?i)\b(api[_-]?key|token|password|secret)\b\s*[:=]\s*['\"][^'\"]{6,}['\"]"),

⚠️ Warning

  • examples/skills_code_review_agent/src/telemetry.py:19-24:监控摘要中 severity 与 category 分布口径不一致

    • severity_distribution(行 19-23)仅统计 disposition == FINDING 的发现,而 category_distribution(行 24)统计全部发现。下游仪表盘若联立两者会得到不可比的计数,造成误判。建议两者采用同一 disposition 过滤口径(或显式注释差异)。
    severity_distribution = Counter(finding.severity.value for finding in findings
                                    if finding.disposition == FindingDisposition.FINDING)
    category_distribution = Counter(finding.category.value for finding in findings)  # 未过滤
  • examples/skills_code_review_agent/skills/code-review/scripts/run_tests.py:39-42skills/code-review/scripts/run_tests.py:39-42 同):路径前缀剥离逻辑过于特化

    • normalized_path[1] == "/" 仅能剥离 a/b/ 这类单字符前缀,对非常规前缀的 diff --git 头不会剥离;与 diff_parser.py:145startswith("a/") or startswith("b/") 实现不一致,维护时易产生分歧。建议复用 _normalize_diff_path 的判定方式以保持一致。

总结

整体为新增的示例/技能包,代码结构清晰、SQL 参数化、subprocess 未使用 shell、本地环境变量有白名单、临时输入只读挂载,主体安全性良好。存在 1 处必须修复的 Critical:Redactor 脱敏模式集未能覆盖 Rule Engine 检测到的全部密钥形状(GitHub token 与含空格密钥值),会导致已识别的密钥原样泄露至报告与持久化存储;另有 2 处建议修复的口径一致性/健壮性问题。

测试建议

  • 补充 Redactor 回归测试:断言 redact_text('GH_TOKEN = "ghp_' + 'A'*36 + '"')redact_text('password = "my secret value here"') 的输出不再包含原密钥,并断言其经 run_review_task 后不出现在 review_report.jsonreview_report.md 及 SQLite bundle 中。
  • telemetry.build_monitoring_summary 增加断言:当存在 NEEDS_HUMAN_REVIEW/WARNING disposition 的发现时,severity_distributioncategory_distribution 计数口径一致(或符合文档化预期)。

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.

4 participants