[AAASM-4013] 🐛 (sdk): Fail closed on runtime-down + unknown decision under enforce#224
Conversation
The napi shim folds an unreachable/slow/closed runtime (QueryFailed,
ChannelClosed, Shutdown) onto a NON-throwing `allow` + FAIL_OPEN_REASON,
and an unspecified verdict onto the empty decision "". The AAASM-3996
gateway guard is catch-only, so these non-throwing sentinels bypass it
and a policy-denied tool executes under `enforcementMode: "enforce"`.
Make the raw-verdict mapping (`mapDecisionToPolicyResult`) fail-closed
aware: under `failClosed` (set only for enforce), the fail-open sentinel
and the unknown/empty decision map to `{ denied: true }` instead of
allow. Genuine allow/redact and observe/disabled/unset postures are
unchanged (fail open). This matches the Python SDK's enforce posture.
Refs AAASM-4013
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MvjnG3ysnqTY6Gu1wQ2h73
…orce Add native-client tests exercising the REAL non-throwing allow sentinel (decision "allow" + FAIL_OPEN_REASON) and the unknown/empty decision under failClosed → denied, plus the fail-open counterparts. Add an end-to-end gateway+wrapper test proving the sentinel and unknown verdict block the tool under enforce through the real native client. Refs AAASM-4013 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MvjnG3ysnqTY6Gu1wQ2h73
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
🤖 Claude Code review — approve AAASM-4013 enforce fail-closed. JS-layer fix: Note: |
|
🤖 Claude Code — deep review: APPROVE (merge-ready) CI: green except Side-effects (regression check): observe/disabled stay fail-open — Non-blocking note: the |
…M-4013) Add tests for the previously-uncovered new lines flagged by codecov/patch: - mapDecisionToPolicyResult: a REDACT verdict is authoritative and proceeds even under failClosed; an unknown/empty decision without failClosed still fails open (observe/disabled). - createClient: under enforcementMode enforce a runtime-down fail-open sentinel is denied end-to-end through check(); under observe the same sentinel proceeds — exercising the failClosed=enforce plumbing (both branches).
|



What changed
Under
enforcementMode: "enforce"+napi-inprocess, a killed / stalled / unreachable runtime made the nativequery_policyreturn a non-throwingOk({ decision: "allow", reason: FAIL_OPEN_REASON })(the shim foldsQueryFailed/ChannelClosed/Shutdownonto allow), and an unspecified verdict onto the empty decision"". The AAASM-3996 gateway guard is catch-only, so these non-throwing sentinels slipped past it and a policy-denied tool executed.This makes the raw-verdict mapping (
mapDecisionToPolicyResultinsrc/native/client.ts) fail-closed-aware:FAIL_OPEN_REASONconstant kept byte-for-byte in sync withnative/aa-ffi-node/src/lib.rs.failClosed(set only forenforce, plumbed via a newInitAssemblyOptions.failClosedfrom bothcreateNativeClientcall sites), the fail-open sentinel and the unknown/empty decision map to{ denied: true }instead of allow.allow/redact, and theobserve/disabled/ unset postures, are unchanged (fail open).This is the JS-layer fix (option 2):
aa-sdk-clientis pinned by git rev and the native shim intentionally folds error cases onto the sentinel, so no native rebuild is needed. It complements the gateway-layer catch guard (3996) as a second, non-throwing-path defense.Why
The SDK is advisory, but under
enforceit is a security control and must fail closed — a stalled/killed sidecar must not turn deny-on-failure into allow-on-failure. This restores parity with the Python SDK's enforce posture (enforce = enforcement_mode == "enforce"; error-sentinel / unspecified decisions deny under enforce).How to verify
pnpm test— full suite green (353 passed, 2 skipped).tests/native-client.test.tscases exercise the REAL non-throwing allow sentinel (decision "allow"+FAIL_OPEN_REASON) and the unknown/empty decision underfailClosed→{ denied: true }, plus the fail-open counterparts (sentinel withoutfailClosedstill allows; a genuine authoritative allow still proceeds under enforce).tests/native-gateway-enforcement.test.tscases drive the real native client throughcreateNativeGatewayClient+withAssembly, asserting the sentinel and the unknown verdict both block the tool (PolicyViolationError) under enforce, while observe fails open.pnpm typecheckandpnpm lintclean.Closes AAASM-4013
🤖 Generated with Claude Code
https://claude.ai/code/session_01MvjnG3ysnqTY6Gu1wQ2h73