[AAASM-4167] 🔒 (core): Fail closed on non-dict runtime policy return#216
Conversation
A non-dict query_policy() result (e.g. None from a malformed/partial runtime response) previously reached result.get() outside the try/except and raised AttributeError instead of the clean fail-closed deny. Guard with isinstance before extraction so it degrades through _on_query_failure. refs AAASM-4167 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7vqjjo5nrebYNt8WnCNbz
Injects a runtime whose query_policy returns a non-dict (None/str/int/list) and asserts a clean deny under enforce and a clean allow under observe, guarding against the AttributeError that result.get() raised before the isinstance guard. refs AAASM-4167 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 robustness-hardening fix for AAASM-4167 (LOW; graceful-degradation, not a fail-open). CI: Green — 19/19 checks pass. Codecov + SonarCloud quality gate both passed. Scope vs ticket: Full coverage. An Side-effects: None on the happy path. The guard fires only for non-dict results; a normal dict flows straight to the existing Shared-file / merge-order note: This edits — Claude Code review |



Description
Robustness hardening for the SDK-layer runtime interceptor. In
RuntimeQueryInterceptor.check_tool_start(agent_assembly/core/runtime_interceptor.py), theresult.get("decision", ...)extraction sat outside thetry/exceptguardingquery_policy(). Ifquery_policy()returned a non-dict (e.g.Nonefrom a malformed/partial runtime response),.getraisedAttributeErrorinstead of following the clean fail-closed deny path.This adds an
isinstance(result, dict)guard immediately after the try/except that routes a non-dict result through the existing_on_query_failurepath — so it denies under enforce and allows under observe, exactly like the other unauthoritative-verdict paths (raising query, error-sentinel decisions, unknown decisions).Not a fail-open bug: the pre-fix behaviour never returned allow — the exception aborted the call. This is purely graceful-degradation hardening. Realistically unreachable today (the PyO3
query_policyreturns a dict or raises, and a raise was already caught), filed as LOW.Type of Change
Breaking Changes
Related Issues
Testing
New parametrized regression tests in
test/unit/core/test_runtime_interceptor.pyinject a runtime whosequery_policyreturns a non-dict (None,str,int,list) and assert a cleandenyunder enforce and a cleanallowunder observe — noAttributeError.pytest test/unit/core/test_runtime_interceptor.py→ 59 passedpytest test/→ 749 passed, 17 skipped (optional native/framework deps)ruff checkon changed files clean;mypy agent_assemblyshows only the pre-existing optional-_core-native-module import gapsChecklist
Note — shared-file coordination with AAASM-4166
AAASM-4166 edits this same file (
runtime_interceptor.py) at ~line 78 (_ALLOW_DECISIONS); this PR touches a different function at ~line 285. The hunks are disjoint so git auto-merges, but whichever PR merges second may need a trivial rebase. The diff here is kept minimal and localized to keep the hunks disjoint.🤖 Generated with Claude Code
https://claude.ai/code/session_01R7vqjjo5nrebYNt8WnCNbz