Skip to content

intent_bridge,successor_observation: validate caller options and trusted JWKs at entry and deep-copy observation before predicate #617

Open

Description

@drk-ram

What is wrong

At main (a8e3559), src/agentrust_trace/intent_bridge.py and src/agentrust_trace/successor_observation.py have three verification-ordering and input-isolation gaps:

  1. verify_bridge and verify_successor_artifact check kid equality before validating trusted_authorizer_jwk / trusted_observer_jwk with _pubkey_from_jwk:

    • In src/agentrust_trace/intent_bridge.py:173-198 (verify_successor_artifact) and lines 364-371 (verify_bridge), the verifier checks trusted_kid != observer_key_id / authorizer_key_id (and in verify_successor_artifact, also checks observer != expected_observer, successor_observation_digest, and signature base64url) BEFORE calling _pubkey_from_jwk(trusted_*_jwk).
    • When a caller passes an unusable trusted_authorizer_jwk or trusted_observer_jwk (for example, carrying private key material "d", wrong kty="RSA", missing "x", or non-canonical base64url "x") whose "kid" happens not to match, verify_bridge and verify_successor_artifact blame the artifact ("authorizer_key_id does not identify the trusted key") instead of rejecting the unusable trusted_*_jwk first.
  2. verify_bridge validates caller arguments (now, declaration, tool_call, pic_intent_digest, pic_args_digest) only after verifying the Ed25519 signature:

  3. evaluate_successor_observation passes bound["observation"] directly to predicate without copy.deepcopy:

    • In src/agentrust_trace/successor_observation.py:126, result = predicate(observation) passes the caller's inner after["observation"] dict by reference (since _bind_successor_observation returns {**after, "observed_at": observed_at}, a shallow copy), allowing predicate to mutate after["observation"] in place (unlike platform_measurement.py:139, which passes copy.deepcopy(runtime)).

Reproduction on a8e3559:

from agentrust_trace import intent_bridge, sign

key = sign.generate_key()
bridge = intent_bridge.sign_bridge({
    "authorization_id": "a1", "decision": "allow", "authorizer": "auth",
    "authorizer_key_id": "k1", "authorized_at": 100, "expires_at": 200,
    "scope": {"tools": ["t"], "impacts": ["i"]},
    "pic": {"profile": "PIC-CJSON/1.0", "intent_digest": "sha256:" + "1" * 64, "args_digest": "sha256:" + "2" * 64},
    "declaration_digest": "sha256:" + "3" * 64, "tool_call_digest": "sha256:" + "4" * 64,
    "transcript_required": False,
}, key)

# 1. Private key 'd' in trusted_authorizer_jwk is ignored and reported as kid mismatch:
intent_bridge.verify_bridge(
    bridge, {"kty": "OKP", "crv": "Ed25519", "x": sign.key_to_jwk(key)["x"], "d": "secret", "kid": "wrong"},
    declaration={"impact": "i"}, pic_intent_digest="sha256:" + "1" * 64,
    pic_args_digest="sha256:" + "2" * 64, tool_call={"name": "t"}, now=150,
)

Spec section or file

  • src/agentrust_trace/intent_bridge.py (verify_bridge, verify_successor_artifact)
  • src/agentrust_trace/successor_observation.py (evaluate_successor_observation)

Expected behavior

  1. Validate trusted_authorizer_jwk and trusted_observer_jwk with _pubkey_from_jwk (and check isinstance(trusted_kid, str) and bool(trusted_kid)) at entry before comparing kid or inspecting the untrusted artifact's signature.
  2. Validate caller-supplied arguments (now, declaration, tool_call, pic_intent_digest, pic_args_digest, trusted_observer, authorization_id) at entry in verify_bridge and verify_successor_artifact before inspecting the bridge/successor artifact or verifying signatures.
  3. Pass copy.deepcopy(observation) to predicate in evaluate_successor_observation so a caller predicate cannot mutate after["observation"].

Impact

Malformed caller options or private-key-carrying trusted_*_jwk dicts are misreported as artifact kid mismatches or signature failures in intent_bridge, and evaluate_successor_observation exposes after["observation"] to in-place mutation by predicate.

Activity

  1. github-actions commented on Oct 23, 2026

    @github-actions
    Contributor

    🟡 Contributor Check: MEDIUM

    Check Result
    Profile MEDIUM
    Credential LOW
    Overall MEDIUM

    Automated check by AgenTrust Contributor Check.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions