[AAASM-4166] 🔒 (core): Fail closed on UNSPECIFIED policy verdict#217
Conversation
The proto3 zero-value decision "unspecified" ("no decision rendered") was in
_ALLOW_DECISIONS, so a non-authoritative verdict was treated as an authoritative
allow and proceeded under enforce — Python failed open where Node fails closed.
Drop "unspecified" from _ALLOW_DECISIONS so it falls through to the existing
fail-closed path (deny under enforce, allow under observe), matching the Node
SDK (AAASM-4166). Update the known-good-under-enforce assertion that codified
the old behavior.
Refs AAASM-4166
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7vqjjo5nrebYNt8WnCNbz
Regression test for AAASM-4166: an "unspecified" runtime decision denies under enforce (fail-closed, was allow) and still proceeds under observe (fail-open), matching the treatment of any other non-authoritative verdict. Refs AAASM-4166 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7vqjjo5nrebYNt8WnCNbz
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Claude Code review — COMMENT (not an approval)Verdict: approve-ready. Correct, minimal, well-tested fix for AAASM-4166 (LOW / defense-in-depth, cross-SDK consistency). CI: Green — 19/19 checks pass. Codecov + SonarCloud quality gate both passed. Scope vs ticket: Full coverage. Side-effects: None. Cross-SDK convergence: Confirmed — this + go-sdk #129 both make UNSPECIFIED deny under enforce, closing the py/go fail-open gap vs. Node. That is the point of 4166. Shared-file / merge-order note: This edits — Claude Code review |



Description
The proto3 zero-value policy decision
UNSPECIFIED(=0, "no decision rendered") was folded into_ALLOW_DECISIONSinagent_assembly/core/runtime_interceptor.py, so a non-authoritative verdict was treated as an authoritative allow and proceeded underenforce. Python (and Go) failed open here, whereas the Node SDK already fails closed (Decision::Unspecifiedroutes to itsdefault:→ deny).This PR drops
"unspecified"from_ALLOW_DECISIONSso it falls through to the existing fail-closed path (_on_query_failure): deny under enforce, allow under observe — exactly how any other non-authoritative/unrecognized decision is handled, and matching Node.Defense-in-depth / cross-SDK consistency (LOW): only reachable if the trusted local runtime returns an unset/partial decision (a runtime bug, not attacker-controllable over the ownership-checked UDS); the gateway/proxy/eBPF layers remain authoritative.
Type of Change
Breaking Changes
Related Issues
Testing
test_unspecified_decision_denies_under_enforce(deny under enforce) andtest_unspecified_decision_allows_under_observe(fail-open under observe) added; thetest_known_good_decisions_allow_under_enforceassertion that codified the old behavior was updated. Full suite: 742 passed, 17 skipped.ruff checkclean;mypy agent_assemblyshows only the pre-existing optional-_core/grpc-stub baseline (unchanged by this PR; pre-commitmypyhook passes).Cross-repo note
This is the Python half of AAASM-4166; the Go half is a separate PR against
ai-agent-assembly/go-sdk.Rebase note (shared file)
AAASM-4167 also edits
runtime_interceptor.py(a different function, ~line 285). This PR's changes are localized to_ALLOW_DECISIONS(~line 61) and its docstring, so the hunks are disjoint and git auto-merges; whichever of the two PRs merges second may need a trivial rebase.Checklist