From 534f8df1277ba92a0a5ea0ac4c2c090920254563 Mon Sep 17 00:00:00 2001 From: Ian Jhumel Bautista Date: Thu, 20 Aug 2026 13:09:38 +0800 Subject: [PATCH] Ship the OAI composition rules and reclassify unrestricted-shell rules Brings the stale feat/severity-reclassification-acac work onto current main. All of it existed only on that branch, so no user was receiving any of it. Adds four rules that were mirrored in the engine's test fixture but shipped to nobody: OAI-025 (dead tool via call-graph liveness), OAI-107 (handoff target wires shell tools), and OAI-112/OAI-113 (transitive shell through handoff chains). OAI-112 and OAI-113 are the only rules anywhere exercising reachability_is and transitive_capability_exceeds_direct. Reclassifies six permission-bypass and unrestricted-shell rules from high to critical: CSDK-103, CSDK-120, CSDK-201, CSDK-202, ADK-008 and LC-101. ADK-008 took a merge conflict. main had corrected the title and widened the matcher to cover both ExecuteBashTool (current google-adk) and BashTool (earlier releases); the stale branch still had the BashTool-only matcher. Kept main's corrected title, explanation and wider matcher, and took only the severity bump from the branch. Compliance crosswalks are excluded: they moved to the engine repo, which is where the only consumer lives. CLAUDE.md now records that this repo is not their home. --- CLAUDE.md | 9 +++ claude_sdk/agent_safety.yaml | 4 +- claude_sdk/repo.yaml | 4 +- google_adk/builtin_tools.yaml | 2 +- langchain/agent_safety.yaml | 2 +- openai_sdk/agent_safety.yaml | 99 +++++++++++++++++++++++++++++++++ openai_sdk/tool_definition.yaml | 27 +++++++++ 7 files changed, 141 insertions(+), 6 deletions(-) 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.