Repository navigation
Carry the decision's replay facts and decision failures on Trace - #275
Conversation
The acs.decision event could not carry reason_codes, policy_references, cited_provenance_ids or chain_hash, so a decision could not be replayed from the trace alone. Bar decided on #228 to add them, with Maurits's padding rule: acs.policy.ids, acs.policy.versions and acs.policy.rule_ids have the same length and order as policy_references, with an empty string where an entry lacks the field. acs.provenance.source_id follows the same rule against acs.provenance.origin. Every step span now carries acs.request_id, the step's own request_id, through a new step_span_attributes list. acs.request_id_ref on a result span had nothing to join to without it. acs.decision.failure is defined next to decision_event with the names Bar chose on #37, the values each enumerated attribute takes, and her recording rule. The Guardian cases are the two she listed. The spec prose in sections 4.1 and 6.4 and the sessionEnd count stay with #37. On the OCSF side, decision_class carries the same facts under unmapped.acs. verdict_id is an analyst's verdict on an incident, so it gives way to disposition_id, with a mapping from each ACS disposition. disposition_id comes from OCSF's Security Control profile, which decision events now declare. evidences leaves required_fields, because OCSF only recommends it and ACS had no rule for what fills it. The deny example validates against OCSF 1.9.0. trace-pillar.ts learns the six decision attributes and acs.request_id in the same commit. Its decisionAttributeSite throws on an unknown decision attribute, so the mapping change alone breaks the reference implementation's tests. acs.decision.failure is deliberately not measured, because its attributes describe what the recording party saw, not a field of either envelope. A test pins that choice. Signed-off-by: Rock Lambros <rock@rockcyber.com>
A review of this change found that the new fields read as stronger evidence than they are. A late deny was recorded as an enforced block. When a Guardian finishes after the negotiated timeout, the Observed Agent has already applied its failure posture, so the acs.decision event and an OCSF Blocked disposition describe a verdict that never took effect. The decision event now says it records the verdict, not the applied outcome, and a reader must not count it as enforced when a failure event sits on the same span. OCSF has no failure form yet, so a Guardian must not emit an OCSF decision event for a late decision. acs.chain_hash is a copy of a response field. The trace carries neither the signature nor the ContextEntries a verifier needs, so the text now says so, and requirement 3 of the ACS-Trace bar no longer claims replay without Guardian state for provenance joins and the chain head. The decision failure event now carries the caveat Bar gave on #37: the Observed Agent reports on its own conduct, so a count is a lower bound. It also gains a counting rule, a bound on acs.failure.detail, and a note that a response breaking sections 6.3 or 6.5 is a DENY, not a failure. Smaller fixes: the three policy arrays are emitted together or not at all, provenance attributes are always arrays, request_id joins stay within a session, the OCSF decision event gains request_id, decision and metadata so it can be joined and carries the same facts as OTel, and attacker-influenced attributes are marked as untrusted. The checker's policy rows now resolve to policy_references itself, and its pin test covers every list and value Bar fixed. Signed-off-by: Rock Lambros <rock@rockcyber.com>
A second review found that some of the rules added in the previous commit could not be applied as written. The Observed Agent and the Guardian each emit their own step span, so a rule keyed to "the same step span" never matched, and the rule calling a repeated request_id an integrity finding fired on every step. Every step span now also carries acs.session.id, joins key on session and request id together, the integrity finding is limited to two spans from one emitter, and a retried step carries its final request_id. The counting rule counted only the Observed Agent's events, so the party being watched could zero the count. A reader now counts distinct steps from either side, and a step only the Guardian reported is still counted. The note on unusable responses misstated section 6.5, which keeps the original summary at postCompact, and missed a section 6.3 violation that also fails schema validation. The OCSF late-decision rule now names the clock it uses and says a Blocked disposition is a verdict only. The OCSF side gains reasoning and session_id so it carries what the OTel event carries, the deny example uses metadata as acs_fields says, and the disposition and severity tables must key exactly the five dispositions. New tests catch a typo in either table, an acs_fields entry that is not a response field, and a renamed policy_references member field, each of which passed every check before. Attacker-influenced text is now a SHOULD for every attribute value, stated once on the Trace Events page. Signed-off-by: Rock Lambros <rock@rockcyber.com>
A third review found that the retry rule from the previous commit detached failures from the attempts they belong to, and that the integrity rule rested on a resource attribute nothing requires. Each attempt is now its own request, with its own step span, so failures stay on the attempt they happened to and a count counts failed attempts. The integrity rule is gone: section 10.3 already rejects a reused request_id at the Guardian, and a Guardian must not record that rejection as a failure of the earlier request. The OCSF join key now has a machine-readable home, join_fields, because session_id is not a response field and could not sit in acs_fields. The OCSF disposition, severity and class-entry schemas are open again. Closing them changed what a deployment may publish without anyone deciding it. Tests now catch the same typos instead. The examples use UUID session ids, as the request envelope requires. Signed-off-by: Rock Lambros <rock@rockcyber.com>
The OCSF decision class now requires action_id, action, disposition_id, disposition, evidences and metadata.correlation_uid beside finding_info. Defer maps to disposition_id 99, not 14, and modify gets action_id 4. is_alert is true for deny and false for allow, and evidences, authorizations and correlation_uid carry the agent, the gated action, the policies and the join pair in native fields. A Guardian SHOULD NOT set verdict_id or status_id. A late decision is no longer suppressed on OCSF. It keeps the verdict in action_id and disposition_id, adds a late_decision finding type, and carries the failure facts under unmapped.acs.failure. policy_references fields and provenance source_id now reject the empty string, so a "" in a padded Trace array always means absent (#263). Signed-off-by: rocklambros <rock@rockcyber.com>
is_alert is now true exactly when severity_id is 3 or higher. ask and defer alert because an ASK can time out to allow and Trace has no event for how either one ends. modify does not, because the action ran in the form the policy required. finding_info.title stays required, as finding.title was. Signed-off-by: rocklambros <rock@rockcyber.com>
|
@afogel @bar-capsule, this is the issue #228 change with all ten of your answers applied, plus the |
- is_alert equals severity 3 or higher only on an on-time event. A late decision under fail-open is the one case it can be true below 3, and severity_id keeps the table's value. - A reader MUST NOT count an OCSF decision flagged late as enforced. - api.operation goes in evidences only for the two tool-call steps. - A policy reference with neither policy_id nor policy_name becomes an authorizations entry with no policy object, which OCSF accepts. - Producers SHOULD omit a member rather than send "", which the schema now rejects. - The AgBOM example carries correlation_uid, the deny example no longer joins to the unrelated tool call, and the OTel page names both join keys. - The reference Guardian reads an empty AGT reason as no rule, so it can't send a rule_id the new minLength rejects. Signed-off-by: rocklambros <rock@rockcyber.com>
|
@afogel @bar-capsule, I ran this through the same review PR #244 got before asking you to approve it, and it found real gaps. I fixed them in Here's what changed, all inside the rules you already answered:
Five things came up that I didn't change, because each one is a new rule and that's your call:
None of these block the merge as far as I'm concerned. If you agree, I'll file them as one issue and we can sort them out after Friday. |
…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>
The test pins the class list so a change has to be deliberate, and adding signature_invalid and handshake_failure is that change. Signed-off-by: rocklambros <rock@rockcyber.com>
- Regenerate the embedded schema copy and re-record the TypeScript answers after merging integration; GenAI-Security-Project#275 changed four schemas and the Guardian sources. No recorded answer changed. - An empty string in an AGT verdict field is read as absent, as the TypeScript mapVerdict now does: no policy_references, no reason_codes, and a warn without a rule is an evaluation failure (GenAI-Security-Project#263). - One test signer for the agtbridge tests instead of five copies. Claude-Session: https://claude.ai/code/session_01DFdYnoBitHhPcUSq7wMWs1 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
What changed
The
acs.decisionTrace event now carries what a reader needs to replay a decision from the trace alone: reason codes, policy references, cited provenance ids, and the chain hash. Every step span carries the join pair, and decision failures get their own event. The OCSF decision class now uses the Security Control profile, with native fields a SIEM can read. Each of the ten proposals below was answered on issue #228, and this PR applies the answers as given.Which issue does this implement
Closes #228
Closes #263
Refs #37
Refs #235
Refs #262
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
Breaking for implementers? Yes. The Spec leads accepted these breaking changes, since ACS is pre-1.0. They ship in release 0.1.4, a patch release, and the specification version stays v0.1.0.
acs.request_idandacs.session.id.finding_info,finding_info.title,action_id,action,disposition_id,disposition,evidencesandmetadata.correlation_uid, and lists thesecurity_controlprofile. It no longer usesverdict_id.defermaps todisposition_id99, not 14.finding_info.types. Before, it was suppressed.policy_id,policy_version,policy_name,rule_idand Provenancesource_idnow reject the empty string.The ten proposals and their answers
acs.request_idandacs.session.idare required, and joins key on the pair.action_id(allow 1, deny 2, modify 4, ask 99, defer 99) sits besidedisposition_id, with defer at 99.evidencesstays required and carries the agent, the session, and the gated action.authorizationscarries the policies, andmetadata.correlation_uidcarries the join pair.verdict_idorstatus_id.is_alertis true exactly whenseverity_idis 3 or higher. That makes it true for deny, ask and defer, and false for allow and modify.unmapped.acs.failure, and it alerts on any verdict other than allow under fail-open.acs.failure.posturenow reads "the deployment's failure posture".minLength: 1from issue [Bug] Empty strings in policy_references and source_id collide with Trace padding #263 is pulled into this release.acs.chain_hashproves nothing on its own. Accepted, with one sentence added: differentchain_hashvalues for one pair across the two parties' streams are an integrity event under §8.6.metadata.correlation_uid,finding_info.descSHOULD carryreasoning, and policies also go inauthorizations.I tested this
uv run pytest -vpasses on my machineuv run mkdocs build --strictpasses on my machineFrom
reference-implementations/agt/,bun run typecheckis clean andbun testpasses. The deny example inextend_ocsf.md, and a late fail-open variant of it, pass the OCSF 1.9.0 validator at schema.ocsf.io with no errors or warnings.Checklist
git commit -s(required by the DCO)Security
It does, and it improves what a SOC can see. A late deny under fail-open used to produce no OCSF event, which left OCSF silent whenever someone slowed the Guardian down. It now produces a flagged, alerting event. ASK and DEFER decisions alert, because an ASK can time out to allow and Trace has no event for how either one ends. Trace values are marked as untrusted data for consumers.