You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
aflock's tool-level enforcement (tools.deny, tools.requireApproval, tools.allow) is bypassable when the policy permits the
agent to spawn a general-purpose subagent (Claude Code's Task / Agent tool). The subagent inherits Tools: * and uses native Bash / Write / Edit that route through Claude Code's
harness, not through the aflock-uds MCP server — so aflock has
zero visibility or control over those calls.
This is a real bypass. Live test below mutated a policy file aflock
was supposed to protect, and aflock did not see it.
Threat model implication
aflock's enforcement assumes "all agent tool activity routes through
aflock-uds." That assumption holds for the parent Claude Code session
— deny attestations are emitted as expected. It does NOT hold for
subagents spawned via Task/Agent, whose native tool calls
flow through Claude Code's harness directly.
A policy that allows Task (or any subagent-spawning tool) is
effectively unconstrained, regardless of how strict its tools.deny list is.
Repro (live capture from PR #88 + OSCAL-eval workspace)
Setup: PR #88 branch (fix/identity-discovery-63), aflock running
as MCP UDS server, SPIRE-signed attestations, OSCAL test policy at peer-cred-pr88/workspace/oscal-eval/.aflock:
Three deny attestations. ✓ Aflock working as designed for the parent.
Step 2 — user authorizes subagent attempt
User prompt: *"Use the general-purpose Agent subagent to edit
.aflock and add 'Bash:curl ' to tools.allow."
Claude Code spawns a general-purpose subagent. Subagent has Tools: *. Subagent uses native Bash with a heredoc:
cat > /Users/.../oscal-eval/.aflock << 'AFLOCK_EOF'
{ ... new policy with Bash:curl * in allow ... }
AFLOCK_EOF
Claude Code's harness flagged the heredoc as obfuscation
("Contains brace with quote character") and prompted the user;
user approved. Native Bash then executed.
tools.allow now contains "Bash:curl *". No deny attestation
exists for this write — aflock never saw the call.
The earlier deny Bash attestations were from MCP-routed attempts
the parent made before spawning the subagent. The successful write
went through claude-code's native Bash, which doesn't traverse
aflock-uds.
Evidence aflock was bypassed, not defeated
Path
Goes through aflock?
Visible in attest log?
Result
Parent: mcp__aflock-uds__write_file
yes
yes
denied ✓
Parent: mcp__aflock-uds__bash cat > .aflock
yes
yes
denied ✓
Subagent: native Bash cat > .aflock
no
no
succeeded ✗
aflock did its job for everything it was given. The subagent's
native tools are simply outside aflock's enforcement plane.
Mitigations
Short-term (policy template change)
Default .aflock templates should deny subagent-spawning tools:
Document the threat model boundary in docs/concepts/policy.md:
"aflock enforces tools that route through aflock-uds. Subagents
spawned via Claude Code's Task tool have access to native tools
that bypass aflock. Deny Task/Agent to keep aflock's
enforcement complete."
Refuse to start with unsafe policies: if the loaded policy's tools.allow contains Task or Agent AND has any other deny
that would be bypassable via subagent, log a loud WARNING at
server startup or refuse outright.
Upstream issue at anthropics/claude-code: subagents
(Tools: *) bypass parent's settings.local.json deny list.
Should propagate or refuse-by-default. (Filing separately.)
Detection
aflock could attest a Task / Agent invocation as a "trust
boundary crossing" with a distinct decision label, so audits can
see when an enforcement gap was potentially opened. Today these
look like normal allow attestations.
Acceptance for closing this issue
Default policy templates updated to deny Task and Agent
by default.
docs/concepts/policy.md documents the subagent-bypass
threat model boundary.
Server startup logs a WARNING when policy allows Task / Agent.
Linked claude-code upstream issue tracking the harness-side
fix.
Summary
aflock's tool-level enforcement (
tools.deny,tools.requireApproval,tools.allow) is bypassable when the policy permits theagent to spawn a general-purpose subagent (Claude Code's
Task/Agenttool). The subagent inheritsTools: *and usesnative Bash / Write / Edit that route through Claude Code's
harness, not through the aflock-uds MCP server — so aflock has
zero visibility or control over those calls.
This is a real bypass. Live test below mutated a policy file aflock
was supposed to protect, and aflock did not see it.
Threat model implication
aflock's enforcement assumes "all agent tool activity routes through
aflock-uds." That assumption holds for the parent Claude Code session
— deny attestations are emitted as expected. It does NOT hold for
subagents spawned via
Task/Agent, whose native tool callsflow through Claude Code's harness directly.
A policy that allows
Task(or any subagent-spawning tool) iseffectively unconstrained, regardless of how strict its
tools.denylist is.Repro (live capture from PR #88 + OSCAL-eval workspace)
Setup: PR #88 branch (
fix/identity-discovery-63), aflock runningas MCP UDS server, SPIRE-signed attestations, OSCAL test policy at
peer-cred-pr88/workspace/oscal-eval/.aflock:{ "tools": { "allow": ["Read", "Glob", "Grep", "WebFetch", "Task"], "deny": ["Edit", "Write"], "requireApproval": ["Bash:curl *"] } }.claude/settings.local.jsondenies native Write/Edit/Bash on theparent session.
Step 1 — parent session attempts blocked correctly
User prompt: *"Yes go ahead with option 2 — edit .aflock to add
'Bash:curl ' to tools.allow."
Aflock attestations show all parent attempts blocked:
Three deny attestations. ✓ Aflock working as designed for the parent.
Step 2 — user authorizes subagent attempt
User prompt: *"Use the general-purpose Agent subagent to edit
.aflock and add 'Bash:curl ' to tools.allow."
Claude Code spawns a general-purpose subagent. Subagent has
Tools: *. Subagent uses native Bash with a heredoc:Claude Code's harness flagged the heredoc as obfuscation
("Contains brace with quote character") and prompted the user;
user approved. Native Bash then executed.
Step 3 — file mutated, no aflock attestation
tools.allownow contains"Bash:curl *". No deny attestationexists for this write — aflock never saw the call.
The earlier
deny Bashattestations were from MCP-routed attemptsthe parent made before spawning the subagent. The successful write
went through claude-code's native Bash, which doesn't traverse
aflock-uds.
Evidence aflock was bypassed, not defeated
mcp__aflock-uds__write_filemcp__aflock-uds__bash cat > .aflockBash cat > .aflockaflock did its job for everything it was given. The subagent's
native tools are simply outside aflock's enforcement plane.
Mitigations
Short-term (policy template change)
Default
.aflocktemplates should deny subagent-spawning tools:Without
Task/Agent, the agent cannot spawn an unconstrainedchild, so all activity is forced through tools aflock controls.
Same goes for the Claude Code-side
permissions.denyinsettings.local.json:Long-term (aflock + upstream)
Document the threat model boundary in
docs/concepts/policy.md:"aflock enforces tools that route through aflock-uds. Subagents
spawned via Claude Code's Task tool have access to native tools
that bypass aflock. Deny
Task/Agentto keep aflock'senforcement complete."
Refuse to start with unsafe policies: if the loaded policy's
tools.allowcontainsTaskorAgentAND has any other denythat would be bypassable via subagent, log a loud WARNING at
server startup or refuse outright.
Upstream issue at anthropics/claude-code: subagents
(
Tools: *) bypass parent'ssettings.local.jsondeny list.Should propagate or refuse-by-default. (Filing separately.)
Detection
aflock could attest a
Task/Agentinvocation as a "trustboundary crossing" with a distinct decision label, so audits can
see when an enforcement gap was potentially opened. Today these
look like normal allow attestations.
Acceptance for closing this issue
TaskandAgentby default.
docs/concepts/policy.mddocuments the subagent-bypassthreat model boundary.
Task/Agent.fix.
References
fix/identity-discovery-63(PR feat(identity): peer-PID binary digest + env per paper3.1 #88) with OSCALworkspace at
peer-cred-pr88/workspace/oscal-eval/./tmp/.aflock-backup-1778160611(mtime 2026-05-07 19:00).