Skip to content

fix(intent_bridge): validate JWK and kid before signature verification and require dict authorization in sign_bridge (#507) - #510

Merged
imran-siddique merged 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-intent-bridge-jwk-and-signer
Oct 14, 2026
Merged

imran-siddique merged 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-intent-bridge-jwk-and-signer

Conversation

@drk-ram

@drk-ram drk-ram commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

What this changes

Closes #507.

  • verify_bridge (src/agentrust_trace/intent_bridge.py):
    • Require isinstance(trusted_authorizer_jwk, dict) and raise IntentBridgeError("trusted_authorizer_jwk must be an object") before reading kid or reconstructing the public key, matching verify_successor_artifact.
    • Check trusted_authorizer_jwk.get("kid") == authorization["authorizer_key_id"] before reconstructing the key and verifying the Ed25519 signature, matching verify_successor_artifact, so an authorization naming a different authorizer_key_id reports "authorizer_key_id does not identify the trusted key" rather than failing inside pub.verify(signature, body) as "authorization signature is invalid".
    • Hoist _pubkey_from_jwk(trusted_authorizer_jwk) out of the try ... except Exception: raise IntentBridgeError("authorization signature is invalid") block and wrap its ValueError in IntentBridgeError(f"trusted_authorizer_jwk is unusable: {exc}"), matching how verify_bridge wraps sign._b64url_decode and sign._integer_value so a malformed caller JWK is not misreported as an invalid signature and does not escape as a bare ValueError.
  • verify_successor_artifact (src/agentrust_trace/intent_bridge.py):
    • Hoist _pubkey_from_jwk(trusted_observer_jwk) out of the try ... except Exception: raise IntentBridgeError("successor signature is invalid") block and wrap its ValueError in IntentBridgeError(f"trusted_observer_jwk is unusable: {exc}").
  • sign_bridge (src/agentrust_trace/intent_bridge.py):
    • Require isinstance(authorization, dict) and raise IntentBridgeError(f"authorization must be a JSON object, got {type(authorization).__name__}"), matching sign.sign_record, provenance.sign_record, and intent_bridge.digest_jcs. Inner authorization field validation stays in verify_bridge because verify_bridge checks decision != "allow", timestamps, scope, pic, digests, and transcript_required after pub.verify(signature, body), and the test suite uses sign_bridge to sign intentionally malformed authorization dicts that exercise those verifier checks.

Independent cold-reader review (CONTRIBUTING.md "Before a pull request is reviewed")

An independent cold-reader review of git diff main..HEAD and the draft PR description found:

  1. test_bridge_naming_another_authorizer_key_id_is_rejected had no control assertion showing that an untrusted key with the matching kid="key-7" still reaches pub.verify and raises "authorization signature is invalid", and did not test the signing key itself with a mismatched or missing kid. Added that control and the same-key mismatched/missing kid cases, and aligned the test docstring with _fixture()'s authorizer_key_id="key-7".
  2. The draft PR description said provenance.verify_record wraps _pubkey_from_jwk (provenance.verify_record actually wraps jwk_thumbprint). Updated the description to cite verify_bridge's wrapping of sign._b64url_decode and sign._integer_value instead, and explained why sign_bridge validates isinstance(authorization, dict) without validating inner fields.

Type of change

  • Editorial (typo, link fix, clarification: no normative effect)
  • Non-breaking spec change (new optional field, new platform profile, informative addition)
  • Breaking spec change (requires 14-day comment period and Project Lead sign-off)
  • Schema change
  • Example addition

Reference-library bug fix in src/agentrust_trace/intent_bridge.py. No normative spec text or schema changes.

Spec section

None. docs/integration/pic-trace-bridge-v1.md (informative).

Tests for a fix

Each change in src/agentrust_trace/intent_bridge.py has a corresponding test in tests/test_intent_bridge.py that fails on main (21 fail on behaviour, 0 fail on a missing name or file under tools/review_bar.py --base main --head HEAD) and when that specific change is switched off:

  • verify_bridge isinstance(trusted_authorizer_jwk, dict) check: tests/test_intent_bridge.py::test_verify_bridge_rejects_non_object_trusted_authorizer_jwk fails when switched off.
  • verify_bridge trusted_kid check before _pubkey_from_jwk / pub.verify: tests/test_intent_bridge.py::test_bridge_naming_another_authorizer_key_id_is_rejected fails when the check is moved back after pub.verify (raising "authorization signature is invalid" first on a different key) or when removed (DID NOT RAISE IntentBridgeError on the same key with a different or missing kid).
  • verify_bridge _pubkey_from_jwk(trusted_authorizer_jwk) hoisted and wrapped as "trusted_authorizer_jwk is unusable": tests/test_intent_bridge.py::test_verify_bridge_reports_unusable_trusted_authorizer_jwk_instead_of_invalid_signature fails when _pubkey_from_jwk is left inside try: pub.verify(...) (raising "authorization signature is invalid" instead) or left unwrapped (raising a bare ValueError).
  • verify_successor_artifact _pubkey_from_jwk(trusted_observer_jwk) hoisted and wrapped as "trusted_observer_jwk is unusable": tests/test_intent_bridge.py::test_verify_successor_artifact_reports_unusable_trusted_observer_jwk fails when _pubkey_from_jwk is left inside try: pub.verify(...) (raising "successor signature is invalid" instead) or left unwrapped (raising a bare ValueError).
  • sign_bridge isinstance(authorization, dict) check: tests/test_intent_bridge.py::test_sign_bridge_requires_authorization_to_be_a_json_object fails with DID NOT RAISE IntentBridgeError when the isinstance(authorization, dict) guard is switched off.

Checklist

  • DCO sign-off on all commits (git commit -s)
  • CHANGELOG.md updated (for any normative change)
  • Breaking changes marked with <!-- CHANGED: #NNN: description --> in spec text
  • Backward compatibility statement included (for breaking changes)

Contributor: Raghuvar Durga (@drk-ram)

Fixes: #508

…n and require dict authorization in sign_bridge (agentrust-io#507)

Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@drk-ram
drk-ram requested review from a team, lywinged and rajnisht7 as code owners October 14, 2026 05:53

@imran-siddique imran-siddique left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified locally. 8 of the new tests fail against main's intent_bridge.py; all 84 pass on this head. Nothing that was rejected now passes: the change is that a key-id mismatch or an unusable JWK reports as itself instead of as an invalid signature.

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

Labels

None yet

Projects

None yet

2 participants