Skip to content

tool/safety: add tool execution safety guard#2276

Open
shiyudesu wants to merge 3 commits into
trpc-group:mainfrom
shiyudesu:feat/tool-safety-guard
Open

tool/safety: add tool execution safety guard#2276
shiyudesu wants to merge 3 commits into
trpc-group:mainfrom
shiyudesu:feat/tool-safety-guard

Conversation

@shiyudesu

Copy link
Copy Markdown

What changed

Command- and code-execution tools now pass through a pre-execution safety guard that returns an allow, deny, or ask decision before the tool runs. Denied calls are blocked by the framework before tool execution, and an after-tool callback redacts secrets, caps result size, tracks host session lifecycle, and appends correlated preflight and post-execute audit events as JSONL. Behavior is controlled by YAML/JSON policy files (allowed and denied commands, denied paths and globs, network domain allowlists, timeouts, output limits, environment whitelists, per-rule actions, and audit behavior) without code changes, and decisions are exported as tool.safety.* OpenTelemetry span attributes.

Why

Agents that execute shell commands or code can be steered by untrusted input into destructive commands, credential reads, unexpected network egress, dependency installation, or resource abuse. There was no framework-level interception point with a shared, policy-driven decision before tool execution.

The guard parses each command once through internal/shellsafe into a shared analysis result so all rule families evaluate the same parse, and implements the existing tool.PermissionPolicy hook so enforcement happens before CallableTool.Call rather than inside each tool.

Testing

  • go test -race ./tool/safety — includes corpus quality gates over the shared 47-case corpus (testdata/tool_safety_corpus.json): ≥90% high-risk recall, ≤10% safe false positives, 100% detection of credential reads, dangerous deletion, and non-whitelisted egress, and a 500-sample scan under one second
  • go test ./internal/flow/processor -run Safety -count=1
  • cd examples && go vet ./tool_safety_guard
  • Manual run from examples/: go run ./tool_safety_guard scans the 19 built-in samples (5 allow / 12 deny / 2 ask) and regenerates tool_safety_report.json and tool_safety_audit.jsonl with output identical to the committed files except timestamps, scan IDs, and durations

Notes for reviewers

  • New public API: the entire tool/safety package plus telemetry/semconv/trace span attribute constants. Labeled type/api-change accordingly.
  • Default policy semantics (allow vs. ask vs. deny when no policy file is loaded) deserve focused review, as they define the fail-open/fail-close posture.
  • The audit JSONL schema is a persistence contract; field changes after merge would be a compatibility concern.
  • The guard is defense-in-depth and does not replace sandbox isolation; the example README documents its relationship to sandboxing, PermissionPolicy, telemetry, and code executors.

Fixes #2002

Add a pre-execution safety guard for command- and code-execution
tools. The guard parses each shell command once through
internal/shellsafe into a shared analysis result, evaluates
deterministic rule families covering dangerous commands, denied
paths, network egress, shell bypass, host execution sessions,
dependency installation, resource abuse, secret leakage, and
environment and working-directory hygiene, and returns an allow,
deny, or ask decision before the tool runs.

The guard implements tool.PermissionPolicy so the framework blocks
denied calls before CallableTool.Call executes, and attaches an
after-tool callback that redacts secrets, caps result size, tracks
host session lifecycle, and appends correlated preflight and
post_execute audit events as JSONL. Policy files in YAML or JSON
control allowed and denied commands, denied paths and globs,
network domain allowlists, timeouts, output limits, environment
whitelists, per-rule actions, and audit behavior without code
changes. OpenTelemetry span attributes under tool.safety.* are
projected onto the active execute-tool span and mirrored in
telemetry/semconv/trace.

An example under examples/tool_safety_guard scans a shared corpus
of 47 samples and writes tool_safety_report.json and
tool_safety_audit.jsonl. Executable quality gates assert at least
90 percent high-risk recall, at most 10 percent safe false
positives, 100 percent detection of credential reads, dangerous
deletion, and non-whitelisted egress, and a 500-sample scan under
one second.

Fixes trpc-group#2002
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c34c2676-7b93-4ad9-b3f8-397e0359492e

📥 Commits

Reviewing files that changed from the base of the PR and between d6735fc and 20aa784.

📒 Files selected for processing (1)
  • tool/safety/cover_core_guard_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • tool/safety/cover_core_guard_test.go

📝 Walkthrough

English

Overview

Adds a configurable Tool Execution Safety Guard in tool/safety that scans tool requests before execution and returns allow / deny / ask decisions. Denied calls are blocked before execution via integration with tool.PermissionPolicy and the tool-call framework.

Key capabilities:

  • Pre-execution policy scanning over tool command/script inputs, shell parsing + conservative fail-closed behavior, code blocks, path operations, network targets/domains (and dangerous download/network flags), env/CWD rules, dependency-install detection, resource abuse (timeout/sleep/output/unbounded loops), host/session risks (PTY/background/privilege/session lifecycle), and secret leakage detection.
  • YAML/JSON policy configuration (commands/paths/network domains, timeouts, output limits, env whitelist, per-rule enabled+action, and audit policy) with strict decoding/validation (unknown fields rejected, schema/version constraints, domain pattern validation, decision threshold invariants).
  • Deterministic reporting: ordered findings, aggregated risk/decision, and structured evidence/recommendations.

Post-execution enforcement and observability

  • Framework callbacks perform secret redaction and result truncation (byte-budgeted, deterministic markers).
  • Adds MIME-aware artifact redaction and refuses binary artifacts that contain secrets.
  • Tracks host/workspace session lifecycle and correlates preflight ↔ post-execute outcomes.
  • Emits correlated JSONL audit events for both phases and projects safety attributes to OpenTelemetry using tool.safety.* semantic keys.
  • Supports optional concurrency limiting and required-audit mode (audit failures can force deny when configured).

Includes a runnable example (examples/tool_safety_guard) that generates a sample policy/report/audit and demonstrates allow/deny/ask, after-tool redaction, and telemetry mapping.

Public API and compatibility

This is additive but it changes system behavior only when integrators wire the guard in.

New/affected exported Go surface (main symbols):

  • Guard + enforcement
    • type Guard
    • func NewGuard(opts ...Option) (*Guard, error)
    • func (g *Guard) CheckToolPermission(ctx context.Context, req *tool.PermissionRequest) (tool.PermissionDecision, error)
    • func (g *Guard) Scan(ctx context.Context, in ScanInput) (ScanReport, error)
    • func (g *Guard) ScanBatch(ctx context.Context, inputs []ScanInput) (BatchReport, error)
    • func (g *Guard) Policy() Policy
    • func (g *Guard) Callbacks() *tool.Callbacks, func (g *Guard) AttachCallbacks(cbs *tool.Callbacks)
    • func (g *Guard) Close() error
    • Redaction/limiting/artifacts (guard-scoped helpers):
      • func (g *Guard) RedactString(string) (string, bool)
      • func (g *Guard) RedactValue(any) (any, bool, error)
      • func (g *Guard) LimitResult(any) (any, bool, int64)
      • func (g *Guard) RedactArtifact(*artifact.Artifact) (*artifact.Artifact, bool, error)
      • func (g *Guard) WrapArtifactService(artifact.Service) artifact.Service
    • Options:
      • type Option func(*guardOptions) error
      • WithPolicy, WithPolicyFile, WithAuditPath, WithAuditWriter
      • WithTelemetry, WithRedaction, WithToolProfile
      • WithRequiredAudit, WithConcurrencyPolicy
  • Policies/scanning models
    • type Policy, func DefaultPolicy() Policy, func LoadPolicy(path string) (Policy, error), func LoadPolicyFromBytes(data []byte) (Policy, error), func (p Policy) Validate() error
    • type Scanner, func NewScanner(policy Policy, opts ...ScannerOption) *Scanner, func (s *Scanner) Scan(...), func (s *Scanner) ScanBatch(...)
    • type ScanInput, type ScanReport, type BatchReport, type BatchSummary, type Finding
    • Decisions/risk/backends:
      • type Decision (DecisionAllow, DecisionDeny, DecisionAsk, DecisionNeedsHumanReview)
      • type RiskLevel (RiskLow, RiskMedium, RiskHigh, RiskCritical)
      • type Backend (BackendUnknown, BackendWorkspaceExec, BackendHostExec, BackendCodeExec, BackendMCP)
    • Tool decoding profiles and concurrency:
      • type ToolProfile, func DefaultToolProfiles() []ToolProfile
      • type ConcurrencyPolicy
  • Audit + telemetry
    • type AuditPhase with AuditPhasePreflight, AuditPhasePostExecute
    • type AuditEvent, type AuditWriter
    • func NewAuditWriter(path string, required, redactSecrets bool) (*AuditWriter, error)
    • func NewAuditWriterFrom(w io.Writer, required, redactSecrets bool) *AuditWriter
    • func (w *AuditWriter) Append(event AuditEvent) error, func (w *AuditWriter) Close() error
    • type SpanAttribute
    • OpenTelemetry semantic keys:
      • telemetry/semconv/trace/tool_safety.go (adds tool.safety.* attribute key constants)
    • Session/correlation:
      • type ScanEvent (exported) used for correlation across phases.
  • Integrations helpers
    • func CommandPolicyLists(p Policy) (allow []string, deny []string)

Compatibility implications to consider

  • Integrators must correctly wire:
    1. WithToolPermissionPolicyFunc(guard.CheckToolPermission) (or equivalent wrapper),
    2. guard callbacks (for redaction/truncation/session lifecycle + post-exec audit).
  • Policy authors must understand DecisionAsk vs DecisionNeedsHumanReview aliasing and threshold semantics.
  • Policy load is strict: unknown fields or invalid thresholds/domains will fail.

Review questions (export necessity/overlap/semantics)

  • Are all currently exported helper methods on Guard (redaction/limit/artifact wrapping) intended to be stable public API, or should some be internal to reduce surface area?
  • Should ScanEvent be exported (for external correlation use-cases), or kept internal?
  • Ownership/duplication concern: there are both tool/safety telemetry helpers and telemetry/semconv/trace constants—verify tool.safety.* keys are single-canonical and documented.
  • Naming semantics: confirm and document the intended policy author experience for DecisionNeedsHumanReview aliasing to DecisionAsk.

Behavioral and operational risks

  • Fail-closed parsing can increase ask/deny rates: if inputs are unsafe-to-parse, shell/code parsing may deny or escalate rather than allow.
  • False positives vs bypass risk: regex + static preflight can be conservative; attackers may try to evade detectors, so rule coverage and allowlist correctness are critical.
  • Observability vs safety tradeoff: redaction/truncation reduces sensitive exposure but can also remove debugging context; verify redaction markers are sufficient for triage.
  • required_audit operational coupling: when audit is required, audit write failures can force deny (blocking execution due to observability dependency).
  • Callback/lifecycle coupling: correctness depends on tool framework invoking after-tool callbacks; mis-wiring can lead to missing correlation or not releasing concurrency/session state.
  • Policy hot changes: strict validation prevents silent misconfig, but policy updates can still materially change enforcement.

Recommended validation

  • Unit/integration: run go test ./... (including race).
  • Guard enforcement:
    • verify deny prevents tool invocation in the framework layer,
    • verify allow calls through, and ask requires application-side approval.
  • Policy validation:
    • YAML/JSON equivalence,
    • unknown-field rejection,
    • domain pattern validation,
    • needs_human_review normalization,
    • duration/int parsing and threshold invariants.
  • Security/behavior checks:
    • ensure secrets never appear in:
      • reports,
      • JSONL audit,
      • OTel safety attributes,
      • redacted error payloads,
    • verify truncation markers and byte-budget behavior,
    • verify binary secret-bearing artifacts are refused.
  • Operational checks:
    • verify audit JSONL formatting (one event per line), permissions/0600 behavior, and required-vs-non-required error handling,
    • verify correlation fields across preflight and post_execute,
    • verify concurrency limit balancing and session lifecycle tracking.
  • Example outputs: regenerate and confirm tool_safety_report.json and tool_safety_audit.jsonl match the expected decisioning/correlation semantics.

中文

概览

tool/safety 新增可配置的 工具执行安全守卫(Tool Execution Safety Guard):对工具请求做执行前扫描,返回 allow / deny / ask 三态,并且当决策为 deny 时会在框架层 在执行前阻断

主要能力:

  • 执行前策略扫描:基于策略对命令/脚本输入、保守的 shell 解析(fail-closed)代码块路径操作网络目标/域名(含危险下载/网络标志)、环境变量/CWD依赖安装资源滥用(timeout/sleep/输出/无界循环)、主机会话风险(PTY/background/权限提升/会话生命周期)、以及密钥泄漏进行检测与风险评估。
  • YAML/JSON 策略配置:支持命令/路径/网络域名、超时与输出限制、环境变量白名单、各规则的启用+动作、以及审计策略;并提供严格校验(未知字段拒绝、schema/version 与阈值/域名模式校验)。
  • 确定性结构化报告:稳定排序的发现项(findings)、聚合 risk/decision、证据与建议。

执行后处理与可观测性

  • 框架回调里完成密钥脱敏结果截断(全局字节预算、确定性标记)。
  • 引入 MIME 感知 Artifact 脱敏,并对“包含密钥的二进制 Artifact”进行拒绝
  • 追踪 host/workspace 会话生命周期,并实现 preflight ↔ post_execute 的关联
  • 输出关联的 JSONL 审计事件(两个阶段)并将安全属性投递到 OpenTelemetry(使用 tool.safety.* 语义键)。
  • 支持可选的并发限制,并提供 required_audit:审计写入失败时可按配置触发 deny

同时加入可运行示例 examples/tool_safety_guard(生成示例 policy/report/audit,展示 allow/deny/ask、after-tool 脱敏与 telemetry 映射)以及大量扫描/审计/脱敏/语料库/性能测试。

公共 API 与兼容性

这是增量式新增,但只有在集成方把守卫正确接入工具权限链路与回调后才会改变运行行为。

新增/关键导出符号(公共 API 表面):

  • 守卫与强制决策
    • type Guard
    • func NewGuard(...) (*Guard, error)
    • func (g *Guard) CheckToolPermission(...) (tool.PermissionDecision, error)
    • func (g *Guard) Scan(...) / ScanBatch(...) / Policy() Policy
    • func (g *Guard) Callbacks() / AttachCallbacks(...) / Close()
    • 脱敏/截断/Artifact(guard 级辅助):
      • RedactString / RedactValue / LimitResult
      • RedactArtifact / WrapArtifactService
    • 选项:
      • type Option func(*guardOptions) error
      • WithPolicyWithPolicyFileWithAuditPathWithAuditWriter
      • WithTelemetryWithRedactionWithToolProfile
      • WithRequiredAuditWithConcurrencyPolicy
  • 策略与扫描模型
    • type PolicyDefaultPolicyLoadPolicyLoadPolicyFromBytesPolicy.Validate()
    • type ScannerNewScannerScanScanBatch
    • ScanInput / ScanReport / BatchReport / BatchSummary / Finding
    • Decision / RiskLevel / Backend 及其常量(含 DecisionNeedsHumanReviewDecisionAsk 别名)
    • type ToolProfileDefaultToolProfiles
    • type ConcurrencyPolicy
  • 审计 + 遥测
    • type AuditPhaseAuditPhasePreflightAuditPhasePostExecute
    • AuditEventAuditWriterNewAuditWriter/NewAuditWriterFromAppendClose
    • type SpanAttribute、以及 telemetry/semconv/trace/tool_safety.go 新增的 tool.safety.* 语义键
    • type ScanEvent(用于 preflight/post_execute 关联)
  • 集成辅助函数
    • func CommandPolicyLists(p Policy) (allow []string, deny []string)

兼容性/集成要点

  • 接入方需要同时完成:
    1. CheckToolPermission 接到 tool.PermissionPolicy(或等价 wrapper)
    2. 绑定守卫回调(负责脱敏/截断/会话与审计的 post-exec 行为)
  • 策略加载是严格的:未知字段或非法阈值/域名会直接失败。

需要重点评审的问题

  • 是否所有 Guard 上的脱敏/截断/Artifact 方法都应作为长期稳定的公共 API(还是应降低导出面)?
  • ScanEvent 是否必须对外导出(外部是否真的需要复用其结构)?
  • tool/safety 的遥测与 telemetry/semconv/trace 的常量是否存在“两个所有者”风险:建议确认 tool.safety.* 的单一 canonical 来源与文档化。
  • DecisionNeedsHumanReview 别名到 DecisionAsk 的语义是否需要在策略文档中明确约束。

行为与运行风险

  • 保守 fail-closed 解析会提高 ask/deny:无法安全解析的输入倾向于拒绝/升级,而不是放行。
  • 静态预检查与绕过风险的平衡:规则覆盖不足会带来绕过可能;因此允许列表/规则配置正确性很关键。
  • 脱敏/截断与排障之间的权衡:脱敏会隐藏敏感内容,但也可能降低调试可用信息;需确认脱敏标记与截断标记足够支撑排障。
  • required_audit 带来的运维耦合:审计写入失败时可能触发 deny,导致“观测失败阻断执行”。
  • 回调与生命周期耦合:依赖工具框架正确调用 after-tool 回调;接入错误可能导致关联缺失或并发/会话状态释放失败。
  • 策略更新风险:严格校验避免静默错误,但保守策略配置仍可能造成广泛阻断。

建议验证

  • 运行 go test ./...(包含竞态)。
  • 验证强制阻断端到端:
    • deny 不应触发后端工具执行,
    • allow 应放行,
    • ask 应按策略触发应用侧人工审批流程。
  • 验证策略系统:
    • YAML/JSON 等价性,
    • 未知字段拒绝,
    • 域名模式校验、
    • needs_human_review 归一,
    • duration/int 解析与阈值不变量校验。
  • 验证执行后安全动作:
    • 审计 JSONL/报告/OTel 属性中不应出现原始密钥,
    • 截断标记与字节预算行为符合预期,
    • 含密钥的二进制 Artifact 应被拒绝。
  • 验证审计与关联:
    • JSONL 格式(每行一个事件)、权限/0600 行为、required 与 non-required 错误处理,
    • preflight 与 post_execute 关联字段正确性,
    • 并发限制计数平衡与会话生命周期跟踪正确性。
  • 对照示例产物:重新生成并核对 tool_safety_report.jsontool_safety_audit.jsonl 的决策与关联字段语义。

Walkthrough

This PR adds a policy-driven tool/safety package for scanning commands and code, enforcing allow/deny/ask decisions, redacting outputs and artifacts, auditing execution, projecting telemetry, and limiting concurrency. It also adds framework tests and a runnable example with policy, reports, and audit records.

Changes

Tool Execution Safety Guard

Layer / File(s) Summary
Safety contracts and request decoding
tool/safety/types.go, tool/safety/policy.go, tool/safety/profile.go, tool/safety/decode.go
Defines scan/report models, policy validation and loading, tool profiles, and JSON argument decoding.
Analysis and rule evaluation
tool/safety/analysis.go, tool/safety/rules_*.go, tool/safety/helpers.go, tool/safety/url.go
Analyzes shell commands and code blocks and evaluates command, path, network, host, resource, dependency, metadata, environment, shell, and secret findings.
Guard services
tool/safety/guard.go, tool/safety/scanner.go, tool/safety/audit.go, tool/safety/redactor.go, tool/safety/artifact.go, tool/safety/concurrency.go
Adds permission enforcement, deterministic reports, callbacks, audit events, redaction, artifact wrapping, session correlation, telemetry, and concurrency limits.
Validation and integration
tool/safety/*_test.go, tool/safety/testdata/*, internal/flow/processor/functioncall_safety_test.go
Adds unit, corpus, quality, concurrency, performance, and framework integration coverage.
Runnable example
examples/tool_safety_guard/*, telemetry/semconv/trace/tool_safety.go
Adds the example program, policy, documentation, generated report and audit records, and canonical telemetry keys.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: winechord

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a tool execution safety guard.
Description check ✅ Passed The description is directly related to the changeset and explains the guard, policy, audit, and telemetry work.
Linked Issues check ✅ Passed The summaries show the guard, policy loading, pre-execution blocking, audit/OTel, tests, and docs required by #2002 are implemented.
Out of Scope Changes check ✅ Passed The added examples, tests, reports, and docs all support the safety-guard feature set and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@shiyudesu

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.64548% with 70 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.96415%. Comparing base (0c77741) to head (d6735fc).

Files with missing lines Patch % Lines
tool/safety/guard.go 94.32624% 22 Missing and 2 partials ⚠️
tool/safety/redactor.go 93.78238% 9 Missing and 3 partials ⚠️
tool/safety/audit.go 94.02985% 6 Missing and 2 partials ⚠️
tool/safety/analysis.go 98.26840% 2 Missing and 2 partials ⚠️
tool/safety/rules_host.go 96.11650% 2 Missing and 2 partials ⚠️
tool/safety/rules_network.go 94.36620% 2 Missing and 2 partials ⚠️
tool/safety/rules_secret.go 96.29630% 2 Missing and 2 partials ⚠️
tool/safety/scanner.go 96.58120% 2 Missing and 2 partials ⚠️
tool/safety/rules_command.go 98.64865% 1 Missing and 1 partial ⚠️
tool/safety/rules_env_cwd.go 97.77778% 1 Missing and 1 partial ⚠️
... and 1 more
Additional details and impacted files
@@                 Coverage Diff                 @@
##                main       #2276         +/-   ##
===================================================
+ Coverage   89.85876%   89.96415%   +0.10538%     
===================================================
  Files           1150        1178         +28     
  Lines         203417      206390       +2973     
===================================================
+ Hits          182788      185677       +2889     
- Misses         12914       12976         +62     
- Partials        7715        7737         +22     
Flag Coverage Δ
unittests 89.96415% <97.64548%> (+0.10538%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (8)
tool/safety/otel.go (1)

11-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unused trace import kept alive only via a dummy variable.

go.opentelemetry.io/otel/trace (Line 13) is imported here only to satisfy the compiler via var _ = trace.SpanFromContext (Lines 41-42); nothing in this file otherwise uses trace.Span/trace.SpanFromContext. Go imports are file-scoped, so this doesn't "cover" telemetry.go's own (separate) use of trace — that file already imports trace itself. Removing both the import and the dummy var is safe.

🔧 Proposed fix
 import (
 	"go.opentelemetry.io/otel/attribute"
-	"go.opentelemetry.io/otel/trace"
 )
 ...
-
-// Ensure trace.Span is referenced for the imports used by telemetry.go.
-var _ = trace.SpanFromContext
中文

未使用的 trace 导入仅靠一个占位变量来“使用”。

第13行导入的 go.opentelemetry.io/otel/trace 仅通过第41-42行的 var _ = trace.SpanFromContext 来避免编译器报“未使用导入”错误,本文件中实际没有任何函数使用 trace.Span/trace.SpanFromContext。Go 的导入是按文件生效的,这个占位变量并不能为 telemetry.go 中独立的 trace 使用“背书”(该文件本身已自行导入)。可以安全地移除该导入和占位变量(见上方diff)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/otel.go` around lines 11 - 14, Remove the unused
go.opentelemetry.io/otel/trace import and delete the dummy var _ =
trace.SpanFromContext declaration in safety-related code; retain the attribute
import and all functional behavior unchanged.
tool/safety/telemetry.go (1)

87-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate semconv constants across packages, with no test actually enforcing the "canonical" claim.

tool/safety redeclares KeyToolSafety* string constants that the PR layer description says also exist as canonical semconv constants in telemetry/semconv/trace/tool_safety.go. The one test meant to guard this relationship only compares against re-hardcoded literals rather than the actual semconv package, so drift between the two declarations would go undetected.

  • tool/safety/telemetry.go#L87-L105: reference telemetry/semconv/trace's exported constants directly (if no import cycle) instead of re-declaring the same string values, making one package the single source of truth.
  • tool/safety/telemetry_test.go#L48-L58: once the constants reference (or are imported from) telemetry/semconv/trace, update this test to assert equality against that package's actual exported symbols, not hardcoded string literals, so it truly fails on drift.
中文

跨包重复声明 semconv 常量,且测试并未真正强制“权威一致性”。

tool/safety 重复声明了 KeyToolSafety* 字符串常量,而根据PR分层描述,telemetry/semconv/trace/tool_safety.go 中也存在等价的权威 semconv 常量。目前唯一用于校验这种关系的测试只是与再次硬编码的字符串比较,而非对照实际的 semconv 包,因此两者之间的漂移无法被发现。

  • tool/safety/telemetry.go#L87-L105:建议直接引用 telemetry/semconv/trace 的导出常量(若无循环依赖问题),而非重复声明相同字符串值,使其中一个包成为唯一权威来源。
  • tool/safety/telemetry_test.go#L48-L58:在常量引用/来源统一后,更新该测试以对照该权威包的实际导出符号进行断言,而不是硬编码字符串,从而真正能在出现漂移时失败。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/telemetry.go` around lines 87 - 105, Use the exported constants
from telemetry/semconv/trace as the single source of truth instead of
redeclaring the KeyToolSafety* constants in tool/safety/telemetry.go, provided
this does not introduce an import cycle; update tool/safety/telemetry_test.go to
compare against the actual telemetry/semconv/trace symbols rather than hardcoded
literals, so drift is detected.

Source: Path instructions

tool/safety/artifact_test.go (1)

21-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for the documented Name/URL credential-redaction contract.

redactArtifact's doc comment (artifact.go Lines 23-25) states Name and URL are "always redacted regardless of MIME type because they can carry credentials (e.g. https://user:pass@host/...)". None of the tests here exercise that path — all fixtures only put secrets in Data. Since this is explicitly called out as a distinct code path (Lines 38-45 in artifact.go), add a case with a credential-bearing URL/Name and a non-secret Data to protect this contract from regression.

func TestRedactArtifact_URLCredentialsRedactedRegardlessOfMIME(t *testing.T) {
	in := &artifact.Artifact{
		MimeType: "application/octet-stream",
		URL:      "https://user:supersecretpass123@host.example/path",
		Data:     []byte{0x00, 0x01, 0x02},
	}
	out, changed, err := redactArtifact(in)
	require.NoError(t, err)
	require.True(t, changed)
	require.NotContains(t, out.URL, "supersecretpass123")
}

As per path instructions for **/*_test.go, tests should "protect a project contract" and prioritize "externally observable behavior" — the URL/Name credential-redaction path is a stated contract without direct coverage.

中文

缺少对已文档化的 Name/URL 凭据脱敏契约的测试覆盖。

redactArtifact 的文档注释(artifact.go 第23-25行)明确说明 Name 和 URL 无论 MIME 类型如何都会被脱敏,因为它们可能携带凭据(如 https://user:pass@host/)。当前测试全部只在 Data 中放置秘密,未覆盖该独立代码路径(artifact.go 第38-45行)。建议补充一个针对含凭据 URL/Name 的测试用例以保护该契约(示例见上)。

根据路径规则,测试应保护项目契约、关注外部可观察行为;而 URL/Name 凭据脱敏路径目前缺乏直接覆盖。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/artifact_test.go` around lines 21 - 51, Add a test alongside
TestRedactArtifact_* that creates an artifact with credential-bearing URL or
Name, non-secret binary Data, and a MIME type such as application/octet-stream.
Call redactArtifact and assert no error, changed is true, and the credential
value is absent from the redacted URL or Name, covering redaction regardless of
MIME type.

Source: Path instructions

tool/safety/rules_test.go (1)

433-435: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

scannerWithCustom is dead. It is constructed here but never used — the custom-profile case at Lines 440-447 builds its own scanner, and Line 480 only exists to suppress the unused warning. Remove both to keep the test intent clear.

中文

scannerWithCustom 是无用代码。 此处创建后从未使用——自定义 profile 用例(第 440-447 行)会自建 scanner,而第 480 行仅用于消除“未使用”告警。建议一并删除,以保持测试意图清晰。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/rules_test.go` around lines 433 - 435, Remove the unused
scannerWithCustom construction near the scanner setup and delete the separate
suppression reference associated with it; leave the custom-profile test’s own
scanner creation and all other test behavior unchanged.

Source: Path instructions

tool/safety/decode.go (1)

223-228: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Dead guard in rawInt float64 case. Both branches return int(n), true, so the if n == float64(int(n)) check has no effect. If the intent was to reject non-integer JSON numbers (e.g. a timeout of 1.5), this silently truncates instead. Either drop the redundant conditional or reject fractional values explicitly.

中文

rawInt 的 float64 分支存在无效判断。 两个分支都返回 int(n), true,因此 if n == float64(int(n)) 判断毫无作用。如果本意是拒绝非整数 JSON 数字(例如 timeout1.5),当前实现会静默截断。建议删除冗余条件,或显式拒绝小数值。

♻️ Proposed simplification
 	case float64:
-		if n == float64(int(n)) {
-			return int(n), true
-		}
-		return int(n), true
+		return int(n), true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/decode.go` around lines 223 - 228, Update the float64 case in
rawInt to reject fractional values instead of silently truncating them: retain
the integer-value conversion path, but return an unsuccessful result when n
differs from its integer representation. Remove the redundant branch that
currently returns int(n), true in both cases.

Source: Path instructions

tool/safety/analysis.go (1)

545-556: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant if/else in extractNetworkTarget. Both the localhost and the else branch set t.Malformed = true, so the conditional is dead. Collapse it (keep the comment) for clarity, or differentiate the behavior if localhost was meant to be handled distinctly.

中文

extractNetworkTarget 中存在冗余的 if/else localhost 分支与 else 分支都设置 t.Malformed = true,因此该条件判断是无效的。建议合并(保留注释)以提升可读性;若原意是对 localhost 做区别处理,则应实现差异化逻辑。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/analysis.go` around lines 545 - 556, In extractNetworkTarget,
remove the redundant host == "localhost" conditional because both branches only
set t.Malformed = true. Preserve the surrounding explanation as a single comment
and retain the same behavior for all hosts without a dot.
tool/safety/rules_path.go (1)

205-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unused evaluatePathOp helper.
rulePath only uses evaluatePathOpWithCwd, so this copy never runs and only adds drift risk.

中文

删除未使用的 evaluatePathOp 辅助函数。
rulePath 只调用 evaluatePathOpWithCwd,因此这份复制逻辑不会执行,只会增加漂移风险。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/rules_path.go` around lines 205 - 257, Remove the unused
evaluatePathOp helper and its entire duplicated finding logic from the path
rules implementation. Leave evaluatePathOpWithCwd and the rulePath call flow
unchanged, since they provide the active path evaluation behavior.
tool/safety/scanner.go (1)

107-119: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

ctx is accepted but never checked for cancellation, especially in ScanBatch.

Neither Scan nor ScanBatch reads ctx at all — none of the 14 rule evaluators receive it, and ScanBatch's per-input loop never calls ctx.Err(). For a single scan this is low-risk, but ScanBatch is explicitly designed for large batches (the perf tests exercise 500 items); a caller that cancels or times out the context has no way to stop an in-flight batch early, wasting CPU on already-abandoned requests.

As per path instructions, "Require context.Context to be the first parameter when needed. Check cancellation propagation."

♻️ Suggested cancellation check in ScanBatch
 func (s *Scanner) ScanBatch(ctx context.Context, inputs []ScanInput) (BatchReport, error) {
 	batch := BatchReport{
 		SchemaVersion: "1",
 		GeneratedAt:   s.clock(),
 		Reports:       make([]ScanReport, 0, len(inputs)),
 	}
 	for _, in := range inputs {
+		if err := ctx.Err(); err != nil {
+			return batch, err
+		}
 		report, err := s.Scan(ctx, in)

Also applies to: 192-215

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tool/safety/scanner.go` around lines 107 - 119, Update ScanBatch to check
ctx.Err() before processing each input and stop promptly when the context is
canceled or times out, returning the context error. Ensure Scan propagates the
context to any rule-evaluation work it performs, and preserve existing scan
behavior when ctx remains active.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/tool_safety_guard/README.md`:
- Around line 18-23: Update the expected output block in the README to match
main.go’s CheckToolPermission formatting: show all five case-name lines with
only their decision actions, followed by the existing AfterTool, scan summary,
and report-writing lines. Mark the fenced block as text to satisfy markdownlint
MD040.

In `@tool/safety/analysis.go`:
- Around line 178-190: Ensure the policy’s ConfiguredNetworkCommands reaches
token classification inside analyzeShell before classifyToken runs: either pass
the configured commands into analyzeShell or seed its inner analysis from
buildAnalysis. Preserve the existing mergeAnalysis behavior while making
bare-host arguments for configured downloaders recognized on the shell path.

In `@tool/safety/artifact.go`:
- Around line 101-108: Update the Godoc comment above newArtifactServiceWrapper
to reference the function’s actual unexported name, keeping the constructor
unexported unless external use is explicitly required.

In `@tool/safety/concurrency.go`:
- Around line 85-112: Gate both global counter decrements in the acquire
rollback and release closure on the same MaxActiveCalls > 0 condition that
guards the increment. Update the relevant logic in acquire and its release
callback while preserving the per-tool counter behavior.

In `@tool/safety/guard.go`:
- Around line 403-406: Update the decision handling around stashScanEvent so
scan events are stored only when the request is allowed and will reach the
after-tool callback. Do not call stashScanEvent for denied or ask decisions,
while preserving the existing fromReport data for the allow path.

In `@tool/safety/helpers.go`:
- Around line 209-226: Update parseDecimalInt to use strconv.ParseInt with base
10 and int64 bit size after trimming and validating the empty input, propagating
its overflow or invalid-number error instead of manually accumulating n.
Preserve the existing output assignment and consumed-length behavior for valid
non-negative decimal integers.

In `@tool/safety/redactor.go`:
- Around line 304-332: Update isSecretFieldName so its substring checks also
recognize “token” and “accesskey”, matching the existing exact-match cases and
covering embedded names such as id_token, session_token, and my_access_key.
Preserve the current case-insensitive matching and other secret-name checks.
- Around line 203-270: Update the map[string]any branch in limitWithBudget to
collect and lexicographically sort the map keys before processing entries.
Iterate over the sorted keys while preserving the existing budget checks,
truncation behavior, and value handling so identical inputs produce
deterministic results.

In `@tool/safety/rules_code.go`:
- Around line 267-277: Update the rec.networkCall handling to honor the
extracted URL allowlist before appending the code.network_call Finding, reusing
the existing NetworkTargets/ruleNetwork allowlist logic where applicable.
Suppress the finding when URLs are present and all are allowlisted, while
preserving the current finding behavior for unknown or unavailable targets;
otherwise remove the misleading comment.

In `@tool/safety/rules_command.go`:
- Around line 97-109: Update hasDangerousDelete so rawSourceHasDangerousDelete
is only called when a.Pipeline is nil, indicating parsing failed. After scanning
all successfully parsed pipeline segments without finding danger, return false
directly and preserve the existing nil-analysis and dangerous-segment behavior.

In `@tool/safety/rules_host.go`:
- Around line 163-168: Tighten the fallback matching in the source scan around
the lowercase `src` loop so privilege-command names are recognized only at token
or word boundaries, not anywhere inside quoted or unrelated command text.
Preserve the parsed-argv detection above and ensure benign text such as `echo
"please su to root"` is not classified as `host.privilege`.

In `@tool/safety/session.go`:
- Around line 111-125: Update postExecuteEvent to populate the returned
ScanEvent’s SessionHash with the current session digest, including both the
stashed-event path and the fallback event path, while preserving the existing
Redacted behavior so appendPostExecute receives a non-empty session hash.

---

Nitpick comments:
In `@tool/safety/analysis.go`:
- Around line 545-556: In extractNetworkTarget, remove the redundant host ==
"localhost" conditional because both branches only set t.Malformed = true.
Preserve the surrounding explanation as a single comment and retain the same
behavior for all hosts without a dot.

In `@tool/safety/artifact_test.go`:
- Around line 21-51: Add a test alongside TestRedactArtifact_* that creates an
artifact with credential-bearing URL or Name, non-secret binary Data, and a MIME
type such as application/octet-stream. Call redactArtifact and assert no error,
changed is true, and the credential value is absent from the redacted URL or
Name, covering redaction regardless of MIME type.

In `@tool/safety/decode.go`:
- Around line 223-228: Update the float64 case in rawInt to reject fractional
values instead of silently truncating them: retain the integer-value conversion
path, but return an unsuccessful result when n differs from its integer
representation. Remove the redundant branch that currently returns int(n), true
in both cases.

In `@tool/safety/otel.go`:
- Around line 11-14: Remove the unused go.opentelemetry.io/otel/trace import and
delete the dummy var _ = trace.SpanFromContext declaration in safety-related
code; retain the attribute import and all functional behavior unchanged.

In `@tool/safety/rules_path.go`:
- Around line 205-257: Remove the unused evaluatePathOp helper and its entire
duplicated finding logic from the path rules implementation. Leave
evaluatePathOpWithCwd and the rulePath call flow unchanged, since they provide
the active path evaluation behavior.

In `@tool/safety/rules_test.go`:
- Around line 433-435: Remove the unused scannerWithCustom construction near the
scanner setup and delete the separate suppression reference associated with it;
leave the custom-profile test’s own scanner creation and all other test behavior
unchanged.

In `@tool/safety/scanner.go`:
- Around line 107-119: Update ScanBatch to check ctx.Err() before processing
each input and stop promptly when the context is canceled or times out,
returning the context error. Ensure Scan propagates the context to any
rule-evaluation work it performs, and preserve existing scan behavior when ctx
remains active.

In `@tool/safety/telemetry.go`:
- Around line 87-105: Use the exported constants from telemetry/semconv/trace as
the single source of truth instead of redeclaring the KeyToolSafety* constants
in tool/safety/telemetry.go, provided this does not introduce an import cycle;
update tool/safety/telemetry_test.go to compare against the actual
telemetry/semconv/trace symbols rather than hardcoded literals, so drift is
detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e4d72b15-220c-43d9-889b-36b1fdabbb11

📥 Commits

Reviewing files that changed from the base of the PR and between 0c77741 and 7116631.

📒 Files selected for processing (51)
  • examples/tool_safety_guard/README.md
  • examples/tool_safety_guard/main.go
  • examples/tool_safety_guard/tool_safety_audit.jsonl
  • examples/tool_safety_guard/tool_safety_policy.yaml
  • examples/tool_safety_guard/tool_safety_report.json
  • internal/flow/processor/functioncall_safety_test.go
  • telemetry/semconv/trace/tool_safety.go
  • tool/safety/analysis.go
  • tool/safety/artifact.go
  • tool/safety/artifact_test.go
  • tool/safety/audit.go
  • tool/safety/audit_test.go
  • tool/safety/benchmark_test.go
  • tool/safety/concurrency.go
  • tool/safety/corpus_test.go
  • tool/safety/decode.go
  • tool/safety/doc.go
  • tool/safety/guard.go
  • tool/safety/guard_test.go
  • tool/safety/helpers.go
  • tool/safety/integration.go
  • tool/safety/otel.go
  • tool/safety/policy.go
  • tool/safety/policy_test.go
  • tool/safety/profile.go
  • tool/safety/quality_test.go
  • tool/safety/redactor.go
  • tool/safety/redactor_test.go
  • tool/safety/rules_code.go
  • tool/safety/rules_command.go
  • tool/safety/rules_dependency.go
  • tool/safety/rules_env_cwd.go
  • tool/safety/rules_host.go
  • tool/safety/rules_metadata.go
  • tool/safety/rules_network.go
  • tool/safety/rules_path.go
  • tool/safety/rules_resource.go
  • tool/safety/rules_secret.go
  • tool/safety/rules_shell.go
  • tool/safety/rules_test.go
  • tool/safety/scanner.go
  • tool/safety/scanner_test.go
  • tool/safety/session.go
  • tool/safety/telemetry.go
  • tool/safety/telemetry_helpers_test.go
  • tool/safety/telemetry_test.go
  • tool/safety/testdata/tool_safety_corpus.json
  • tool/safety/testdata/tool_safety_policy.yaml
  • tool/safety/testhelpers_test.go
  • tool/safety/types.go
  • tool/safety/url.go

Comment thread examples/tool_safety_guard/README.md Outdated
Comment thread tool/safety/analysis.go
Comment thread tool/safety/artifact.go Outdated
Comment thread tool/safety/concurrency.go
Comment thread tool/safety/guard.go Outdated
Comment thread tool/safety/redactor.go
Comment thread tool/safety/rules_code.go
Comment thread tool/safety/rules_command.go
Comment thread tool/safety/rules_host.go Outdated
Comment thread tool/safety/session.go
- Fix typos (unparseable -> unparsable) flagged by CI
- Pass ConfiguredNetworkCommands into shell token classification
- Gate global concurrency counter decrement on MaxActiveCalls > 0
- Stash scan events only on the allow path to avoid unbounded growth
- Use strconv.ParseInt in parseDecimalInt to reject overflowing sleeps
- Sort map keys in limitWithBudget for deterministic truncation
- Add token/accesskey substrings to isSecretFieldName
- Honor the URL allowlist for code.network_call findings
- Restrict raw-source dangerous-delete scan to parse-failure fallback
- Match privilege commands at token boundaries in raw-source fallback
- Populate SessionHash in postExecuteEvent for post-execute audit
- Align example README expected output with actual program output
- Add unit tests raising tool/safety coverage from 72% to 98%

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tool/safety/cover_core_guard_test.go`:
- Around line 315-323: Strengthen the pass-through assertions in the
RunAfterTool test so a nil output or nil CustomResult fails explicitly. Require
out to be non-nil, then require out.CustomResult to be non-nil and equal to
"partial", preserving the contract that the unchanged result is returned
untouched.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 71ec1590-e5de-450f-8218-5bcf12f41c18

📥 Commits

Reviewing files that changed from the base of the PR and between 7116631 and d6735fc.

📒 Files selected for processing (26)
  • examples/tool_safety_guard/README.md
  • examples/tool_safety_guard/tool_safety_audit.jsonl
  • examples/tool_safety_guard/tool_safety_report.json
  • tool/safety/analysis.go
  • tool/safety/artifact.go
  • tool/safety/concurrency.go
  • tool/safety/concurrency_test.go
  • tool/safety/cover_core_audit_test.go
  • tool/safety/cover_core_decode_test.go
  • tool/safety/cover_core_guard_test.go
  • tool/safety/cover_core_policy_test.go
  • tool/safety/cover_core_test.go
  • tool/safety/cover_rules_analysis_test.go
  • tool/safety/cover_rules_code_test.go
  • tool/safety/cover_rules_command_test.go
  • tool/safety/cover_rules_redactor_test.go
  • tool/safety/guard.go
  • tool/safety/guard_test.go
  • tool/safety/helpers.go
  • tool/safety/redactor.go
  • tool/safety/redactor_test.go
  • tool/safety/rules_code.go
  • tool/safety/rules_command.go
  • tool/safety/rules_host.go
  • tool/safety/rules_path.go
  • tool/safety/rules_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • examples/tool_safety_guard/README.md
  • examples/tool_safety_guard/tool_safety_audit.jsonl
  • examples/tool_safety_guard/tool_safety_report.json
  • tool/safety/concurrency.go
  • tool/safety/rules_test.go
  • tool/safety/helpers.go
  • tool/safety/rules_host.go
  • tool/safety/redactor_test.go
  • tool/safety/rules_command.go
  • tool/safety/rules_code.go
  • tool/safety/rules_path.go
  • tool/safety/analysis.go
  • tool/safety/artifact.go
  • tool/safety/redactor.go
  • tool/safety/guard.go

Comment thread tool/safety/cover_core_guard_test.go
@shiyudesu

Copy link
Copy Markdown
Author

Hi @Rememorio @WineChord @Flash-LHR, this PR is ready for review — CI is green, and all CodeRabbit findings have
been addressed. Could you take a look when you have time? Thanks!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

构建 Tool 执行安全扫描、Filter / Permission 拦截与监控机制

2 participants