[AAASM-4760] 🐛 (sdk): fail closed instead of silently fail-open when ungoverned - #272
Conversation
Under an enforce posture (the None default or explicit "enforce") a pure-Python / containerized install with no native agent_assembly._core extension previously returned the bare GatewayClient from build_governance_interceptor. The adapters got no check_tool_start, so every governed tool call proceeded UN-governed while the session looked governed — a silent fail-open (AAASM-4760). Now the native-missing enforce path returns a deny-all _FailClosedInterceptor with an explicit, logged reason and keeps the loud one-time warning: no tool call is authoritatively allowed when there is no native authority to consult. The explicit observe / disabled dry-run postures remain the opt-in advisory path (fail open), unchanged. Updates the two interceptor tests that encoded the old fail-open contract and adds a builder-level regression asserting the deny reason is explicit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019NafDDcehs2ez4Ax9kuWfi
End-to-end assertion for AAASM-4760: with the native extension absent under the default enforce posture, init_assembly must not run ungoverned-but-looks-governed. The test asserts both guarantees at once — ctx.registered is False (not a governed/registered session) and the wired interceptor's check_tool_start returns deny, never a silent allow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019NafDDcehs2ez4Ax9kuWfi
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
🤖 Claude Code — PR review CI: All checks green — CI Success, unit + integration test items (3.13/ubuntu), CodeQL, SonarCloud (Quality Gate passed, 100% coverage on new code), Codecov patch (all modified lines covered), pip-audit, LangChain contract test, docs build. Only Scope (AAASM-4760, HIGH): Complete. The fail-open hole is closed. With native Side-effects / security: Clean and correctly bounded.
Frontend: Playwright N/A — pure Python SDK, no UI. Verdict: APPROVE (not merging — review-required / merge decision left to dev-lead). The HIGH-severity fail-open is genuinely closed, the fix is minimal and consistent with the existing fail-closed-under-enforce semantics, and it is well covered by regression tests. |



Description
Fixes a HIGH-severity fail-open in the SDK's in-process enforcement path (AAASM-4760).
When the native
agent_assembly._coreextension is absent — the pure-Python / containerized install with no native wheel — under an enforce posture (theNonedefault or an explicitenforce),build_governance_interceptorreturned the bareGatewayClient. The framework adapters were then handed a client with nocheck_tool_start, so every governed tool call proceeded UN-governed while the session still reported as governed. A developer relying on the SDK layer believed they were protected; enforcement was completely inert.This PR makes that state impossible to miss: the native-missing enforce path now fails CLOSED.
Before / after enforcement behavior
_coreabsent)enforce/None(default) posture_FailClosedInterceptorwith an explicit reason + loud one-time warning → every governed tool call is denied (fail closed)observe/disabledpostureThe deny carries an actionable reason ("governance unavailable: … refusing to run ungoverned …") so the developer knows enforcement is unavailable (install the native extension, or opt into
observe) rather than mistaking it for a policy deny. Theregisteredflag on the returned context already surfaces the unregistered state (False) with a loud stderr warning; this PR closes the remaining hole where tool calls still ran ungoverned.This matches the project's existing fail-closed-under-enforce semantics (the "native present but runtime socket unreachable" case already returned
_FailClosedInterceptor; native-missing now behaves consistently) and mirrors Go's fail-closed / Node'sConfigurationErrorpostures.Scope note: this owns only the python-sdk behavior. The base-image side (shipping the native rc.6 wheel in the published
pythonbase image) is handled by AAASM-4761; together they close the gap.Type of Change
Breaking Changes
Under
enforcewith the native extension absent, governed tool calls now deny instead of silently proceeding. This is the intended security fix. Deployments that legitimately want the SDK to be advisory-only (relying on the proxy / eBPF layers) must opt intoenforcement_mode="observe", which is the documented, clearly-signalled non-enforcing posture. No API surface changed.Related Issues
Testing
Unit tests added/updated
Updated the two interceptor tests that encoded the old fail-open contract to assert the deny-all fail-closed behavior.
Added a builder-level regression asserting the deny reason is explicit (
test_native_missing_deny_reason_is_explicit).Added an end-to-end regression through
init_assemblyasserting both guarantees at once:ctx.registered is Falseand the wired interceptor denies a tool call rather than silently allowing it (test_native_absent_under_enforce_wires_fail_closed_interceptor).Full suite:
804 passed, 16 skipped.ruff checkclean on changed files;mypy agent_assemblyunchanged from baseline (only pre-existing native-_core/grpc import-not-found notes); pre-commit green.Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_019NafDDcehs2ez4Ax9kuWfi