Repository navigation
Record a request the Guardian cannot vouch for apart from the steps, never under its claimed ids - #309
Merged
Conversation
rocklambros
requested review from
GangGreenTemperTatum,
afogel,
artmaro,
bar-capsule,
fewdisc,
mamicidal,
sclintonowasp and
stefanoamorelli
as code owners
October 20, 2026 22:13
…never under its claimed ids PR #273 adds a rule to Trace Events: a Guardian MUST NOT emit acs.decision or acs.decision.failure under the pair of a request it rejects before trusting it. The reference Guardian broke it: an envelope that failed the schema, carrying a forged request_id, got an acs.decision span under that request_id. Following Bar's ruling on issue #283, a request the Guardian cannot vouch for is now a guardian.rejected_request span, with the ids it claimed under guardian.rejected.* and no ACS pair, step attribute or decision event. That covers an envelope that fails validation, a Guardian fault, and a step answered with SIGNATURE_INVALID, REPLAY_DETECTED or TIMESTAMP_OUT_OF_WINDOW. handleAcsRequest validates once before the span opens. dispatch returns its trust verdict beside the response, and a step span gets its pair and step attributes only when it ends as a trusted step. Rebuilt on integration after PR #272: dispatch also takes the envelope log, and the step findings PR #272 added still reach the chain entry. Signed-off-by: rocklambros <rock@rockcyber.com>
rocklambros
force-pushed
the
refimpl/273-untrusted-trace-pair
branch
from
October 22, 2026 10:03
2016401 to
5af6ffb
Compare
9 of 15 tasks
bar-capsule
approved these changes
Oct 28, 2026
This was referenced Oct 28, 2026
jrcrittenden
added a commit
to jrcrittenden/ACS
that referenced
this pull request
Oct 28, 2026
Brings in GenAI-Security-Project#309, GenAI-Security-Project#273, GenAI-Security-Project#168 and GenAI-Security-Project#323. GenAI-Security-Project#309 moves a request the Guardian rejects before trusting it off the step spans, and leaves dispatch a `rejection` to report for this PR's checks. This merge wires them in: - dispatch keeps this PR's checks and returns them as Dispatched. A SIGNATURE_INVALID, TIMESTAMP_OUT_OF_WINDOW or REPLAY_DETECTED refusal reports signature_invalid, timestamp_out_of_window or replay_detected, and a payload that fails validation reports envelope_invalid, as it did in GenAI-Security-Project#309. - validateForDispatch checks the envelope's shape only. The method payload is still validated inside dispatch after the signature, so an unsigned request with a malformed payload is refused as unauthenticated, not answered with a trusted deny. - A method with no step span (handshake/hello, system/ping) opens no span, so a rejection dispatch reports for one is recorded as a rejected request when the response is sent. - The "until PR GenAI-Security-Project#229" notes from GenAI-Security-Project#309 are gone, the sample trace is regenerated, and the denied span is copied into the OpenTelemetry page. GenAI-Security-Project#309's "no span for handshake/hello" test sends a schema-valid ClientHello, because an invalid one is now a rejected request. - New end-to-end tests: a replayed request, an unsigned request and a forged hello are each recorded apart, and the genuine request keeps the only span under its ids. Signed-off-by: Jason Crittenden <jrcrittenden@gmail.com>
esherman-credo
added a commit
to esherman-credo/agent-control-standard
that referenced
this pull request
Oct 29, 2026
Brings in GenAI-Security-Project#309, which changed the exporter's start() to take whether the Guardian trusted the request. The session and message tests now pass TRUSTED, the sample is regenerated, and the sample test and the two sample descriptions count the session and message spans beside GenAI-Security-Project#309's rejected request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Eli Sherman <esherman@credo.ai>
President
added a commit
to disciplinedware/agent-control-standard
that referenced
this pull request
Oct 30, 2026
GenAI-Security-Project#309 changed packages/guardian/src. Only its tree ID changes; all 49 answers stay the same. Claude-Session: https://claude.ai/code/session_01Rhwvb5CFUg1pyMvKKnbHXt Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
PR #273 adds a rule to Trace Events: a Guardian MUST NOT emit
acs.decisionoracs.decision.failureunder the pair of a request it rejects before trusting it. Our reference Guardian broke that rule. An envelope that failed the schema, carrying a forgedrequest_id, got anacs.decisionspan under thatrequest_id. This fixes the reference Guardian, following @bar-capsule's ruling on issue #283: keep the rejected request visible, under separate names, never under the genuine request's ids.guardian.rejected_requestspan. It carries noacs.session.id,acs.request_id, step attribute, oracs.decisionevent. The ids it claimed go underguardian.rejected.claimed_session_idandguardian.rejected.claimed_request_id, withguardian.rejected.reasonand the error code.guardian_fault, which still records itsguardian_error), and a step answered withSIGNATURE_INVALID,REPLAY_DETECTEDorTIMESTAMP_OUT_OF_WINDOW.dispatchnow returns its trust verdict next to the response. So a check that runs after the span opens can't leak a rejected request's ids onto a step span, whether it answers with an error or with a deny. That's where PR fix(agt): implement ACS-Core wire safeguards #229's checks will run.extend_opentelemetry.mdsay what a rejected span carries. The known limits say plainly that this Guardian checks nothing but the schema yet, so a well-formed forgery or replay is still recorded as a step until PR fix(agt): implement ACS-Core wire safeguards #229 lands.@sabasiddique1, this implements item 6 of issue #283, so you can skip that item. Items 1 to 5 and 7 are still yours.
Merge order: this is safe to merge before PR #273. Before that rule exists, the only effect is that a rejected request moves off the step spans. Please don't promote it to
mainwithout PR #273, or the other way round.Which issue does this implement
Part of #283
Refs #37
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 normative text changes.
docs/spec/trace/extend_opentelemetry.mdchanges only where it describes what the reference Guardian exports.Breaking for implementers? No ACS change. Anyone reading the reference Guardian's trace sees a schema-invalid envelope as a
guardian.rejected_requestspan. Before, it was agen_ai.tool.callwith anenvelope_invaliddeny.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 (1245). I checked the new tests by breaking each behavior on purpose. With trust always true, a late rejection ignored, the pair attached early,dispatch's verdict dropped, the fault event removed, or the arrival time ignored, the matching tests fail. Before writing this I ran a six-perspective premortem and a code review, and their findings are fixed here.Checklist
git commit -s(required by the DCO)Security
Without this, anyone who can reach the Guardian's port could write a decision into the trace under a live session's ids by sending a malformed envelope with those ids. A reader joining the Guardian's and the host's streams by pair would then see a step that never happened. A well-formed forgery or replay still gets through until PR #229 adds signature, replay and timestamp checks. The README says so.