Repository navigation
Record every decision failure and count them at session end - #273
bar-capsule wants to merge 2 commits into
Conversation
|
This pull request's description says it closes #37, which is on the project roadmap. When it merges, GitHub closes that issue and the roadmap counts it as delivered. Please confirm the change delivers the whole issue before merging. |
…n issue #37 signature_invalid keeps a tampered response apart from organic failures. handshake_failure counts a session that started without a completed handshake, and its event goes on the acs.session span, not a step span. PR #273's sessionEnd count keys on this set of eight classes. Signed-off-by: rocklambros <rock@rockcyber.com>
|
@bar-capsule, both classes from your issue #37 answer are in PR #275 now ( |
Section 6.4 now requires an audit record for every decision failure under either posture, kept in the deployment's own audit log and emitted on Trace as acs.decision.failure when the deployment claims ACS-Trace. Section 4.1 surfaces a session started unguarded as the same event with class handshake_failure. sessionEnd.summary gains decision_failures, a per-class count the Observed Agent SHOULD report, and a session that ends without it reads as unmeasurable. conformance.md and the hooks.md sessionEnd summary follow, and a test keeps the counted classes equal to the acs.failure.class values in the OTel mapping. Signed-off-by: Bar Kaduri <bar@capsule.security>
…ke_failure its posture A Guardian emits no acs.decision.failure or acs.decision event under the pair of a request it rejects before trusting it: an envelope that fails validation, a failed signature, a replay, or a timestamp outside the window. Such a request can carry a genuine request's pair. handshake_failure now covers the refuse startup posture too, and its acs.failure.posture comes from the startup posture, since on_decision_failure is never negotiated. Section 4.1 records an unguarded session with posture fail_open. Signed-off-by: Bar Kaduri <bar@capsule.security>
2d48e5d to
dffca95
Compare
|
@rocklambros @afogel ready for review - the rest of #37 on top of #275, plus the two gaps from the #275 review: requests the Guardian rejects stay off the genuine request's IDs, and handshake_failure takes its posture from the startup posture. Small spec PR, all checks green. |
What changed
The rest of the #37 decision, the parts that live outside the Trace event #275 added. §6.4 now requires an audit record for every decision failure under either posture: in the deployment's own audit log, and as an
acs.decision.failureevent on Trace when the deployment claims ACS-Trace. §4.1 surfaces a session started unguarded as the same event with classhandshake_failure.sessionEnd.summarygainsdecision_failures, a count per failure class that the Observed Agent SHOULD report, and a session that ends without it reads as unmeasurable.It also closes two gaps from the #275 review. A request the Guardian rejects before trusting it can carry a genuine request's pair, so it produces no failure or decision event under that pair; a deployment that records rejected requests uses other attribute names, as decided on #283. And
handshake_failuregets a posture: it comes from the startup posture, sinceon_decision_failureis never negotiated.Which issue does this implement
Closes #37
Refs #283, which decided how the reference Guardian records requests it can't vouch for
meaning, so it needs no issue
Base branch
integration, because this touches the specification, schemas, a referenceimplementation, an adapter, tests, or CI
main, because every changed path is on the documentation lane allowlist inCONTRIBUTING.md
Type of change
Specification changes
No Discussion was opened. The decisions are on #37 and #283.
docs/spec/instrument/specification.mdsessionEndcount is a SHOULD. §4.1: an unguarded session becomesacs.decision.failurewith classhandshake_failureand posturefail_openon the session span.specification/v0.1.0/hooks/session-end.jsonsummary.decision_failures: an object keyed by failure class, each value an integer of 0 or more. The keys are limited to the eightacs.failure.classvalues.specification/v0.1.0/trace/otel-mapping.jsonSIGNATURE_INVALID,REPLAY_DETECTED,TIMESTAMP_OUT_OF_WINDOW).handshake_failurecovers both startup postures, and itsacs.failure.posturecomes from the startup posture.docs/spec/trace/events.mddocs/spec/conformance.mddocs/spec/instrument/hooks.mdsessionEndpayload line names the real fields (reason, notsession_reason, plus the missingabandonedvalue) and the new count.tests/test_decision_failure_classes.pysession-end.jsoncounts differ from theacs.failure.classvalues inotel-mapping.json.Breaking for implementers? The schema change is additive:
decision_failuresis optional. Two rules tighten. §6.4 previously required a record only for a step that proceeded without a decision, and now a fail-closed decision failure needs one too. And a Guardian that used to record a rejected replay, or any other untrusted request, under its claimed pair now records it under other names or not at all.I tested this
uv run pytest -vpasses on my machineuv run mkdocs build --strictpasses on my machinepytest reports 919 passed and 1 skipped (a case-insensitive filesystem skip). From
reference-implementations/agt/,bun run typecheckis clean andbun testreports 1234 passed, 1 skipped, 0 failed.session-end.jsonwas checked with jsonschema (Draft 2020-12): a count for known classes, a zero, and a payload with no summary are valid; an unknown class, a negative count and a non-integer are rejected.Merging
Signed-off-byline.Checklist
git commit -s(required by the DCO)Security
It adds detection rather than surface: a blocked handshake now produces an event and a count, and a forged or replayed request can no longer make a genuine decision read as unenforced.