diff --git a/CLAUDE.md b/CLAUDE.md index 49fbd8e..ba9b755 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -185,4 +185,13 @@ product. notes, examples, or work-in-progress `.yaml` files inline — they will fail the loader. Markdown docs (`README.md`, `CLAUDE.md`, per-pack READMEs) are fine; the loader only reads `.yaml`. +- Not the home of compliance crosswalks. Control mappings (rule ID to + NIST/ISO/SOC 2/PCI/EU AI Act controls) live in the **engine** repo at + `internal/compliance/mappings/` and are embedded in the binary. They do not + belong here: this repo is **public**, compliance is a Guard-only surface (the + open-source scanner ships no `compliance` command), and the engine's rule + loader skips a `mappings/` subtree anyway — so a crosswalk here is exposed to + everyone and read by no one. Engine builds released before that skip existed + walk every `*.yaml` in the pack and hard-fail on a non-policy file, so if a + `mappings/` directory ever reappears here it must never contain `.yaml`. - Not the home of the schema or predicates. Those live in the engine repo. diff --git a/claude_sdk/agent_safety.yaml b/claude_sdk/agent_safety.yaml index 87acc02..637e09e 100644 --- a/claude_sdk/agent_safety.yaml +++ b/claude_sdk/agent_safety.yaml @@ -63,7 +63,7 @@ rules: - id: CSDK-103 title: AgentDefinition sets permissionMode to bypassPermissions - severity: high + severity: critical confidence: 0.9 language: python applies_to: @@ -145,7 +145,7 @@ rules: - id: CSDK-120 title: TypeScript AgentDefinition sets permissionMode to bypassPermissions - severity: high + severity: critical confidence: 0.9 language: typescript applies_to: diff --git a/claude_sdk/repo.yaml b/claude_sdk/repo.yaml index 0a3948e..a0414dc 100644 --- a/claude_sdk/repo.yaml +++ b/claude_sdk/repo.yaml @@ -10,7 +10,7 @@ policy: rules: - id: CSDK-201 title: Project default permission mode bypasses approvals - severity: high + severity: critical confidence: 0.9 applies_to: - claude_sdk @@ -35,7 +35,7 @@ rules: - id: CSDK-202 title: Session permission mode bypasses approvals - severity: high + severity: critical confidence: 0.9 applies_to: - claude_sdk diff --git a/google_adk/builtin_tools.yaml b/google_adk/builtin_tools.yaml index 5da3f94..21f37f6 100644 --- a/google_adk/builtin_tools.yaml +++ b/google_adk/builtin_tools.yaml @@ -10,7 +10,7 @@ policy: rules: - id: ADK-008 title: Agent grants the bash tool with no restrictive command policy - severity: high + severity: critical confidence: 0.75 language: python applies_to: diff --git a/langchain/agent_safety.yaml b/langchain/agent_safety.yaml index 2350f54..bd3b243 100644 --- a/langchain/agent_safety.yaml +++ b/langchain/agent_safety.yaml @@ -9,7 +9,7 @@ policy: rules: - id: LC-101 title: LangChain agent wires a code-execution or shell built-in tool - severity: high + severity: critical confidence: 0.85 language: python applies_to: diff --git a/openai_sdk/agent_safety.yaml b/openai_sdk/agent_safety.yaml index 5ec5304..a4bd971 100644 --- a/openai_sdk/agent_safety.yaml +++ b/openai_sdk/agent_safety.yaml @@ -109,6 +109,42 @@ rules: privileged tools are needed but sandboxing is impractical, document the decision and acknowledge the elevated risk explicitly. + - id: OAI-107 + title: Handoff-target agent wires shell or filesystem-touching tools + severity: high + confidence: 0.85 + language: python + applies_to: + - openai_agent + - openai_sandbox_agent + scope: agent + match: + all: + - agent_is_subagent_of_any: true + - agent_uses_tool_kind: [shell_invocation] + explanation: > + This agent is the target of a handoff (it appears in another agent's + handoffs=[...]) AND is wired with tools that execute shell commands or + touch the filesystem. In the OpenAI Agents SDK, input_guardrails run + only on the agent that first receives the user input — a handoff target + never runs its own input_guardrails on the handed-off turn. A parent can + therefore route around its own guardrails by delegating to this child and + asking it to run the shell-touching tool the parent would have screened. + The child's privileged tools then execute on input that no guardrail + ever inspected. This is why the rule does not require the child's + input_guardrails to be empty: even a child that declares guardrails does + not run them on the delegated turn, so the shell exposure is the hazard + regardless. + fix: > + Either remove the shell or filesystem-touching tools from this handoff + target, or move the privileged tools up to the top-level agent that + receives user input — where its input_guardrails actually run — and have + that agent do the work instead of delegating it. If the handoff must + stay, validate the handoff payload before it reaches the child (for + example with an @input_guardrail on the parent and an input_filter on + the handoff), since the SDK will not run the child's own guardrails on + the delegated turn. + - id: OAI-109 title: Agent uses WebSearchTool without input_guardrails severity: high @@ -193,3 +229,66 @@ rules: wire it via `inputGuardrails: [...]` on the `Agent({...})` constructor. Pin MCP servers to trusted endpoints and treat web and file results as untrusted data. + + - id: OAI-112 + title: Guarded agent transitively wields shell through its handoff chain + severity: high + confidence: 0.6 + language: python + applies_to: + - openai_agent + - openai_sandbox_agent + scope: agent + match: + all: + - agent_kwarg_present: + - input_guardrails + - transitive_capability_exceeds_direct: + - shell + explanation: > + This agent declares input_guardrails but delegates — directly or through + a chain of handoffs — to an agent whose tools execute shell commands, a + capability this agent does not wield itself. In the OpenAI Agents SDK, + input_guardrails run only on the agent that first receives the user + input, and a handoff target never runs its own input_guardrails on the + delegated turn. The guardrails here therefore create false confidence: + the shell-executing turn happens downstream, on a payload the handoff + may have transformed, with no guardrail in front of it. + fix: > + Treat the handoff chain as part of this agent's attack surface. Either + move the shell-wielding work up to this agent, where its guardrails + actually run; remove the shell tools from the downstream agent; or + constrain the delegated payload explicitly — an input_filter on the + handoff plus tool-level validation on the downstream agent. + + - id: OAI-113 + title: Unguarded agent's handoff chain reaches shell execution + severity: medium + confidence: 0.6 + language: python + applies_to: + - openai_agent + - openai_sandbox_agent + scope: agent + match: + all: + - not: + agent_kwarg_present: + - input_guardrails + - transitive_capability_exceeds_direct: + - shell + explanation: > + This agent declares no input_guardrails and delegates, directly or + through a chain of handoffs, to an agent whose tools execute shell + commands — a capability this agent does not wield itself. If this agent + is where user input enters, nothing screens that input anywhere on a + delegation path that ends in shell execution: input_guardrails run only + on the first agent in the run, and handoff targets never run their own + on delegated turns. The chain makes the exposure easy to miss — each + agent looks harmless in isolation. + fix: > + Add input_guardrails to this agent so the entry point of the delegation + chain screens input before any downstream shell tool can act on it, and + review whether the downstream agent's shell access is needed at all. + The downstream agent's own configuration is flagged separately by the + direct shell-tool rules. diff --git a/openai_sdk/tool_definition.yaml b/openai_sdk/tool_definition.yaml index 7ef835b..7b053a9 100644 --- a/openai_sdk/tool_definition.yaml +++ b/openai_sdk/tool_definition.yaml @@ -95,3 +95,30 @@ rules: Provide a concise `description` string in the `tool({...})` options stating what the tool does and when the model should call it. The description is the model's primary routing signal alongside the tool name. + + - id: OAI-025 + title: Tool is defined but never referenced anywhere in the repo + severity: low + confidence: 0.5 + language: python + applies_to: + - openai_tool + scope: tool + match: + reachability_is: + - unreachable + explanation: > + This @function_tool-decorated function is never referenced anywhere in + the repo's Python code — it appears in no Agent's tools=[...], no + handoff, and no call site. Trustabl's call-graph liveness marks a tool + unreachable only when nothing references its name at all, so this is + dead capability code: it is not exposed to any model today, but it + carries tool-grade capability (and drifts unreviewed) until a later + refactor wires it in. Dead tool definitions also misdirect review + effort toward code no agent can call. + fix: > + Delete the tool definition, or wire it into the intended agent's + tools=[...] list deliberately. If the tool is dispatched dynamically + (getattr, a string-keyed registry), static liveness cannot see that + reference — prefer a direct name reference so both the model wiring + and this analysis can track it.