feat(node): expose authenticated identity evidence - #298
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new local transport identity attestation tool get_transport_identity to the MCP server, allowing nodes to sign a nonce-bound statement proving their local node key and expected trusted control-plane key. Feedback on the implementation suggests optimizing lock contention by performing cryptographic operations outside of the read lock in trustsControlPlaneFingerprint, and improving error handling when reading the node public key by wrapping the original error and separating the key size validation.
93507fc to
0a3ca54
Compare
|
Interop follow-up, explicitly not a merge condition: Agoragentic will not treat public SAM discovery as permission to invoke an unknown provider. After this transport-identity primitive is accepted, we are looking for one independently operated, deterministic, free/read-only MCP fixture for a bounded conformance canary: exact PeerID/service/tool/schema, explicit operator consent and revocation contact, immutable UTC window, one zero-value/no-settlement call at most, no credentials or private data, and no retry. If a maintainer or community operator runs such a fixture on a SAM mesh, please point us to the appropriate operator contact. If no suitable fixture exists, we will keep the production activation gate closed. |
|
@aojea Maintainer review requested on exact signed head |
|
hi @rhein1 thank you very much, but I need to get a bit more context to be completely sure I fully understand the problem you are trying to solve. Machinery already in tree that may overlap with thisNode identity is the libp2p PeerID. The host is created with Every node carries a control-plane-signed biscuit. Minted at enrolment A mutual identity handshake already exists. In The biscuit is holder-of-key, not bearer. "Challenge a provider before invoking it" is implemented. And it's already exposed to clients. Trust-anchor distribution exists. Key rotation is handled. Nodes learn new control-plane keys from signed Revocation exists. A The local API already has three authentication modes. Unix socket Questions
|
|
Thank you. This exposed an ambiguity in our PR wording. Exact answers:
Given the machinery you described, I agree this PR should not be treated as a new provider-challenge primitive. I see two narrow paths:
If Unix socket or mTLS plus an evidence/preflight API is the canonical SAM design, we should use that and avoid parallel cryptography. Which direction would you prefer? |
|
I think we should go second path. For the local check, use the Unix socket. In Once the connection itself is trustworthy the rest is just a read. I think new local endpoint that allows to get the identities will solve your problem:
{
"peer_id": "12D3KooWLTp...",
"control_plane_url": "https://cp.example",
"trusted_control_plane_keys": [
{ "fingerprint": "sha256:9f86d0...", "received_at": "2026-08-24T14:02:11Z" }
],
"enrolled": true,
"biscuit": "<base64>",
"biscuit_expires_at": "2026-08-25T14:02:11Z"
}I'd keep your fingerprint format (sha256 over the SPKI DER) so any pinning config For the preflight:
{
"peer_id": "12D3KooWProvider...",
"verified": true,
"biscuit": "<base64 raw bytes>",
"verifying_key_fingerprint": "sha256:2c26b4...",
"attested": { "role": ["sam:role:node"], "labels": { "region": "us-east-1" } },
"expiration": "2026-08-25T09:00:00Z",
"revocation_ids": ["a3f1...", "bb90..."],
"checked_at": "2026-08-24T16:40:02Z"
}Most of this exists already. IMHO both of these should be plain HTTP endpoints rather than MCP tools. The MCP NOTE: On doing so we we can add a field with the node id to |
|
The second path is the right one, and plain authenticated HTTP endpoints are a better boundary than an MCP tool. The model should not choose whether identity evidence is collected. The proposed split also matches our two checks:
The provider payload is almost sufficient for our private receipt verifier. We need these semantics made explicit:
The raw Biscuit, roles, labels, expiration, revocation IDs, selected key, and connection-bound PeerID then cover what Agoragentic needs. We do not need SAM to carry Agoragentic operator consent, Principal Authority, target binding, or zero-value/no-settlement policy; those remain separately resolved platform authority. Adding the local node ID to With those additions, I will rework this PR away from |
|
@aojea Reworked as requested at exact signed head 19fb4c1.\n\nThe model-callable get_transport_identity MCP tool is gone. This head adds two read-only HTTP endpoints:\n\n- GET /sam/identity\n- GET /sam/peer/{peer_id}/evidence\n\nBoth require a strongly authenticated local transport: Unix socket by default, or verified mTLS on TCP. Bearer-only/plain TCP is rejected for these evidence reads. The peer endpoint reuses etchPeerBiscuit and VerifyBiscuitAndGetKey, returns the connection-derived, requested, and Biscuit-bound PeerIDs with a fail-closed binding verdict, selected trusted Ed25519 SPKI DER key material/fingerprint, raw Biscuit, labels/roles, expiry, revocation IDs/current revocation verdict, and freshness fields under closed versioned schemas. It performs fresh evidence retrieval and revalidates revocation and the selected trust key before returning.\n\nLocal validation at this exact head:\n- go test ./internal/node -run Evidence -count=1\n- go test ./internal/node -count=1\n- go vet ./internal/node\n- git diff --check\n\nThe code-bearing hosted checks observed so far are green. Bats e2e tests and �2e-android did not start: GitHub annotated both with the repository/account billing or spending-limit runner-allocation error, so they produced no test evidence.\n\nPlease review the endpoint contract and implementation when available. Separately, if you know an independently operated SAM mesh exposing a deterministic free/read-only fixture, please connect us with its operator. Agoragentic will keep its private SAM client and all live authority default-off until upstream contract review plus explicit operator consent, exact PeerID/tool/schema binding, and a bounded zero-value/no-settlement window exist. |
|
you need to rebase to avoid the problem with the runners, will review later. See tests/integration or tests/e2e for adding a test that guarantees the end to end feature works as we designed, basically mimicking what your operator will do, so guarantee that flow is consistent and does not break you in the future |
|
CI fixed in #312 |
19fb4c1 to
e1f5561
Compare
|
@aojea Rebased onto current Exact signed head: The new Focused race tests, |
|
Exact-head hosted CI is complete for |
|
Thanks. One blocking issue, then a few smaller points. public API schemas must be defined in
|
|
Addressed the blocking review and all three non-blocking points at signed head
Local exact-head validation: generated output byte-identical after regeneration; |
|
Thank you for the PR! Overall this LGTM after @aojea's suggestions. A few minor, cheap things to consider:
This is a great contribution — nice work! |
|
@rhein1 seems we are almost there, just the last comments and please squash all commits and we get this in, I want to cut beta soon and have this as part of it |
Split trusted-key snapshot failures from the empty-key case so error formatting never wraps nil with %w. Preserve the existing queryExpiration error propagation cleanup. Addresses the two unresolved review threads on google#298.
9e11c27 to
a9ef783
Compare
Classify identity evidence separately from egress, keep trailing-slash identity requests on the protected local surface, use human-readable sidecar errors, and pin the peer handler's HTTP error contract with focused tests. Addresses all four follow-ups in google#298 issue comment 5431144547.
Add a canonical transport-identity statement, issue and verify signed local evidence, expose authenticated identity and peer evidence endpoints on the protected sidecar surface, integrate operator verification and interoperability fixtures, and cover the flow with focused tests. Review follow-ups: - classify identity-evidence requests separately from egress - keep /sam/identity/ on the protected local surface with a clear 404 - use human-readable plain-text sidecar errors - pin peer endpoint 400, 403, 404, and 405 contracts, headers, and bodies Squashes the six commits in google#298.
5ceb056 to
50db599
Compare
|
Thank you for the review and for including me in the beta. I’m genuinely excited to exercise SAM in the real Agoragentic operator flow and provide focused interoperability and safety feedback. I'm currently writing a research paper on agentic discovery and safety based on the production issues and flaws I have found. I am very excited for SAM as the work we are doing complements each other in many regards. I really appreciate you guys being receptive to my input and I hope we can do more together in the near future. I love Agent 2 Agent and the infrastructure that we have for it. |
Expose authenticated, read-only local and peer identity-evidence endpoints using public protobuf schemas and SAM's existing libp2p TLS, Biscuit verification, key rotation, and revocation machinery. Review follow-ups: - classify identity-evidence requests separately from egress - keep /sam/identity/ on the protected local surface with a clear 404 - use human-readable plain-text sidecar errors - pin peer endpoint 400, 403, 404, and 405 contracts, headers, and bodies Rebased onto google/sam main 1fc706f and preserves GitHub's clean virtual-merge tree for google#298.
Expose authenticated, read-only local and peer identity-evidence endpoints using public protobuf schemas and SAM's existing libp2p TLS, Biscuit verification, key rotation, and revocation machinery. Review follow-ups: - classify identity-evidence requests separately from egress - keep /sam/identity/ on the protected local surface with a clear 404 - use human-readable plain-text sidecar errors - pin peer endpoint 400, 403, 404, and 405 contracts, headers, and bodies Rebased onto current google/sam main and preserves the clean virtual-merge result for google#298.
50db599 to
bcc1bd7
Compare
Expose authenticated, read-only local and peer identity-evidence endpoints using public protobuf schemas and SAM's existing libp2p TLS, Biscuit verification, key rotation, and revocation machinery. Review follow-ups: - classify identity-evidence requests separately from egress - keep /sam/identity/ on the protected local surface with a clear 404 - use human-readable plain-text sidecar errors - pin peer endpoint 400, 403, 404, and 405 contracts, headers, and bodies Rebased onto current google/sam main and preserves the clean virtual-merge result for google#298.
bcc1bd7 to
170abad
Compare
|
Final exact-head audit update for signed, single-commit head
Hosted evidence is green for No production SAM activation or provider call is implied by this source/CI state. |
|
it failed twice in
=== RUN TestBiscuitExpiryIsCappedByItsVoucher/refresh_clamps_to_the_end_of_the_OIDC_session does not seem related |
Problem
A local owner/control-plane process needs to authenticate its SAM node and independently verify fresh remote-provider identity evidence before any invocation. The original nonce-signed MCP tool duplicated SAM's trust machinery and did not expose the remote evidence that an independent verifier needs.
Change
Following maintainer direction, this revision exposes two authenticated, read-only HTTP endpoints using public messages defined in
api/sam.protoand serialized withprotojson:GET /sam/identityreturnsapi.IdentityEvidenceResponse: the local PeerID, raw Biscuit, Biscuit expiration, control-plane URL, trusted Ed25519 SPKI DER key set, and check time.GET /sam/peer/{peer_id}/evidenceperforms one fresh existingAuthProtocolIDhandshake and returnsapi.PeerEvidenceResponse: one bound PeerID, the raw Biscuit, selected Ed25519 SPKI DER verifying key, roles, labels, expiration, revocation IDs, and check time.A 200 is the node's fail-closed verdict. Independent callers recompute from the raw Biscuit and keys; the response no longer contains self-reported verification/enrollment booleans, duplicate PeerIDs, derivable fingerprints, fixed cache/revocation fields, or ad-hoc schema-version strings. Binary values are protobuf
bytes, timestamps are Unix seconds, and errors use the sidecar's plain-text convention.Both endpoints require the filesystem-protected Unix socket or verified mTLS. A bearer-authenticated plain TCP connection is rejected. The remote endpoint performs the intended pre-fetch and post-verification revocation checks and fails closed if requested, authenticated-connection, and Biscuit-bound PeerIDs differ, if the peer is revoked/banned, or if the selected control-plane key leaves the trusted set during verification.
This reuses SAM's existing libp2p TLS identity, mutual Biscuit handshake,
VerifyBiscuitAndGetKey, key rotation, and revocation machinery. It adds no dependency and grants no discovery, invocation, routing, payment, or settlement authority.Operator-flow integration coverage
tests/integration/identity_evidence_test.gostarts two realsam-nodeprocesses against the existing mock control plane, enrolls both, connects the owner to the provider over libp2p, and reads both evidence endpoints through the owner's Unix socket before any provider request. It is intentionally focused on socket access, enrollment, and the peer handshake; generated response-field semantics and fail-closed cases remain in unit tests. It does not invoke a tool or provider.Validation
Passed on signed head
bdafa65413057886a22258347542775f076c71a5in a clean Go 1.25.7 Linux container:protoc3.21.12 andprotoc-gen-go1.36.12go test ./api -count=1go test ./internal/node -count=1go test ./tests/integration -run TestIdentityEvidenceOperatorFlow -count=1 -timeout=10s(1.174s)go vet ./api ./internal/node ./tests/integrationgit diff --checkHosted CI is authoritative for the complete repository matrix.