Skip to content

docs: tighten section 10 verifier rules - #352

Open
justrunme wants to merge 2 commits into
GenAI-Security-Project:integrationfrom
justrunme:docs/section-10-verifier-rules
Open

justrunme wants to merge 2 commits into
GenAI-Security-Project:integrationfrom
justrunme:docs/section-10-verifier-rules

Conversation

@justrunme

@justrunme justrunme commented Oct 29, 2026 •

Copy link
Copy Markdown

What changed

Adds a normative "Verifier rules" block to §10 and a client-side algorithm list to the handshake, following the discussion with @bar-capsule in both issues.

Which issue does this implement

Refs #315
Refs #316

  • This is an editorial correction (typo, grammar, link, formatting) with no change in
    meaning, so it needs no issue

Base branch

  • integration, because this touches the specification, schemas, a reference
    implementation, an adapter, tests, or CI
  • main, because every changed path is on the documentation lane allowlist in
    CONTRIBUTING.md

Type of change

  • Specification change (schema, hooks, events, AgBOM)
  • Reference implementation or adapter
  • Documentation
  • Tooling or CI
  • Governance (licensing, security policy, contributor docs)

Specification changes

Breaking for implementers? Possibly for verifiers that accepted unsigned envelopes or fell back to a non-negotiated algorithm; those now get SIGNATURE_INVALID. The new ClientHello field is optional.

Open points for review

  • If the client omits signature_algorithms_supported, I defined the negotiated set as the ACS-Core baseline HMAC-SHA256 only. That is a choice, not something the issues settled.
  • Both directions use one intersection. A separate list for verifying responses would be possible, but I kept the change smaller.

I tested this

  • I synced my branch with the base branch before opening this
  • uv run pytest -v passes on my machine
  • uv run mkdocs build --strict passes on my machine

tests/test_verifier_rules.py checks the schema change and the normative text. This repository has no reference verifier, so there are no runtime tests for constant-time comparison, atomic replay reservation or state isolation. Those belong with the Guardian implementation. The full test suite and mkdocs build --strict pass.

Checklist

  • Commits are signed off with git commit -s (required by the DCO)
  • Prose follows STYLE.md
  • No secrets, tokens, or internal URLs in the diff

Security

  • This change has no security impact

It tightens signature verification in §10: verification order, state isolation before verification, constant-time comparison, and rejection of missing or non-negotiated signatures.

Add normative verifier rules for signature verification order, state
isolation, constant-time comparison, missing signature rejection, and
non-negotiated algorithm rejection. Align schema optionality with
ACS-Core conformance requirements.

Refs GenAI-Security-Project#315, GenAI-Security-Project#316

Signed-off-by: Andrey Lesnikov <52860508+justrunme@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Thanks for this. It is queued rather than ignored.

This pull request does not reference an issue carrying status:accepted, so a maintainer has not looked at it yet and will not until the underlying issue is triaged. Nothing here is rejected. See Current Priority Scope for what the project is working on, and help wanted for work that is already accepted.

If this is an editorial correction, tick that box in the description and this comment stops applying.

@drk-ram drk-ram left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged e1b2fca onto origin/integration at d09e1bd. 1b8f970 is an ancestor of that tip. The merge is clean.

python -m pytest -q on the merge gave 1169 passed, 10 failed, 49 errors, and 1 skipped. tests/test_verifier_rules.py itself was 13 passed. The 10 failures and 49 errors are this Windows machine. gh is not a Win32 application, symlink creation is denied, python3 is missing from the shebang tests, and the theme partials do not match the installed copy. The same 10 and 49 show up on the other three merges. This diff does not touch those files.

I then broke the new text and reran that file.

Deleting the atomic reservation sentence still passed, 13 tests. The test looks for the word atomic anywhere, and the batching roadmap already says atomicity. Deleting the constant-time MUST sentence, and leaving the heading, still passed. The heading contains Constant-time. Rewording "no fallback" still passed, because the key_id paragraph already says MUST NOT fall back to another secret. Restoring the empty-array exemption failed one test. Renaming intersection in the schema failed one test. Renaming the four state kinds failed one test.

Two spec problems should be fixed before this lands.

Section 10.1 still says signatures are already field-optional. Rule 3 says a missing signature MUST be rejected with SIGNATURE_INVALID. Issue 316 asked for both the rule and the fix to that sentence. Pull request 339 rewrites the sentence and merges cleanly with this branch, but 339 is not on integration. Landing this alone leaves the contradiction in the section this pull request edits. Drop that clause here, or stack on 339. Keep signature optional in the JSON Schema. The exception for system/ping and an unresolvable key_id is the right reason, and your schema test already pins that.

Rule 2 applies regardless of algorithm, then requires a comparison of decoded signature bytes. That is the right check for HMAC-SHA256. ECDSA-P256, RSA-PSS-SHA256, and ML-DSA do not compare a signature to an expected value. They verify an equation. A literal reading makes those verifiers non-conformant. Limit the byte comparison to MAC tags. For the asymmetric algorithms, say the verifier must not compare the base64 wire text, and must not leak the key through an early exit.

One smaller ambiguity in rule 1. The atomic reservation is "after verification succeeds". The sentence before it withholds state changes until both the signature check and the timestamp check pass. Say "after both checks pass". Otherwise a skewed but well-signed request can burn a request_id.

The Observed Agent clause says it returns SIGNATURE_INVALID. The agent refuses the response locally. It has nowhere to return that code. Say it refuses the response.

The schema change is otherwise fine. ClientHello.signature_algorithms_supported is optional, the enums match the request envelope, and the empty-array exemption is gone from the ServerHello description. The omitted-field baseline of HMAC-SHA256 only is a choice you already flagged. I am not asking to reopen it. The rule sits in section 10, which is the right altitude for a verifier mechanism.

Please pin the atomic sentence, the decoded-byte comparison, and the non-negotiated no-fallback sentence with phrases that do not already appear elsewhere. The three breaks above are the ones the file does not catch.

Scope constant-time comparison to MAC tags, reserve request_id after both checks pass, have the Observed Agent refuse a bad response locally, align 10.1 with the missing-signature rule, and pin the normative phrases in tests.

Signed-off-by: Andrey Lesnikov <52860508+justrunme@users.noreply.github.com>
@justrunme
justrunme force-pushed the docs/section-10-verifier-rules branch from 3dbadd2 to 8907c53 Compare November 2, 2026 06:57
@justrunme

Copy link
Copy Markdown
Author

Thanks for the careful review, and for running the mutations. Pushed 8907c53, all checks are green.

  • Rule 1: the atomic check-and-reserve now happens "after both checks pass", and the text says a skewed but validly signed request cannot burn a request_id.
  • Rule 2: the constant-time requirement is limited to MAC tags (HMAC-SHA256). For ECDSA, RSA-PSS and ML-DSA the rule says verifiers must not compare the base64 wire text and must not leak key or secret material through an early exit or data-dependent timing.
  • Rule 4: the Observed Agent now refuses the response locally instead of "returning SIGNATURE_INVALID".
  • §10.1: I replaced "signatures are already field-optional" with the same wording PR 339 uses, character for character. I checked that this branch merges cleanly with 339 and with current main, and the identical edit should resolve without a conflict in either order. If you would rather have the clause dropped here and the fix left to 339, I can do that instead.
  • Tests: the three sentences you listed are now pinned with phrases that appear nowhere else in the spec: "one atomic operation performed after both checks pass", "with no fallback to another algorithm or key", and "verifiers MUST compare the decoded signature bytes in constant time". I broke each sentence locally and the matching test fails.

@drk-ram drk-ram left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-verified at head 8907c53bcf07be2740a5c3d919994ffda01c581e, which is the head I am approving.

Tests. python -m pytest tests/test_verifier_rules.py -q on a clean checkout of 8907c53: 13 passed. A merge-tree against current origin/integration (bba1b84) is clean. The schema, handshake and spec-related selection (-k "handshake or schema or spec or verifier or openapi or conform") gives 88 passed, 1 skipped and 2 failures. Both failures are the symlink tests in tests/test_publish_schemas.py, which cannot create symlinks without elevation on this Windows machine and do not touch this diff.

Each finding from my last review, against the new text:

  1. Rule 1 now says the check-and-reserve of the request_id is one atomic operation performed after both checks pass, and states that a skewed but validly signed request cannot burn an ID. Resolved.
  2. Rule 2 limits the constant-time decoded-byte comparison to MAC tags (HMAC-SHA256). For ECDSA, RSA-PSS and ML-DSA it forbids comparing base64 wire text and forbids leaking key or secret material through an early exit or data-dependent timing. A verifier that runs an ordinary signature-verification equation is no longer non-conformant by a literal reading. Resolved.
  3. Rule 4 now has the Observed Agent refuse the response locally rather than "return" SIGNATURE_INVALID. Resolved.
  4. §10.1 no longer says signatures are "already field-optional". The replacement sentence is identical, character for character, to the one in #339, so the two branches converge without a conflict in either order. The JSON Schema keeps signature optional, with the system/ping and unresolvable-key_id exemption stated in the text. Resolved.
  5. The three sentences I could break without a failing test are now pinned. I deleted each one individually and reran the file: the "one atomic operation performed after both checks pass" sentence now fails 1 test, the "with no fallback to another algorithm or key" sentence fails 1 test, and the "verifiers MUST compare the decoded signature bytes in constant time" sentence fails 1 test. The phrases are unique in the spec, so the earlier false passes are gone.

Residual, non-blocking. Three clauses are still unpinned: the base64 wire-text prohibition for asymmetric algorithms, the "cannot burn an ID" clause, and the Observed Agent's local refusal. Deleting each still leaves 13 passing. They are normative text, so a short follow-up test for each would close the gap, but I would not hold this PR for it.

Schema. ClientHello.signature_algorithms_supported is optional, its enum matches the request envelope, the omitted-field baseline of HMAC-SHA256 only is documented in both places, and the empty-array exemption is gone from the ServerHello description. The handshake-time asymmetry (the Guardian verifies the ClientHello against its own set; the Observed Agent verifies the ServerHello against the client's set or the baseline) is spelled out, which closes a bootstrapping ambiguity I had not raised.

For anyone reading the verifier rules against the evidence layer, draft-durga-agenttrust-trace-verification-00 (work in progress) uses the same discipline of rejecting before any state transition; this PR states it at the right altitude for section 10.

Approving.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:needs-triage Not yet triaged. Applied by the issue forms

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants