bus: handle_requests_with_context — expose the verified caller principal - #75
Merged
Conversation
…principal
Request handlers registered via `Bus::handle_requests` receive only the request
body: the authenticated caller identity, though known, is discarded. That is
fine for peers that serve any same-mesh caller, but a capability-gated responder
(e.g. a dock service that must authorize WHICH agent is calling) cannot express
its policy — authorization is forced onto the dialer, which is not complete
mediation.
Add an additive, wire-compatible seam:
- `RequestContext { caller_user_fp, caller_agent_fp }` — the verified principal.
Every inbound envelope is already `verify()`-ed at the transport boundary, so
`sender_agent_fp()` / `sender_user_fp()` are authenticated (not claimed); this
is the request's *signer*, the correct principal for authorization (a relay
can only deliver a request its signer already authorized).
- `Inbox::register_handler_with_context` + `Bus::handle_requests_with_context`,
which hand the handler `(RequestContext, body)`. The existing body-only
`handle_requests` / `register_handler` are unchanged (they wrap, discarding
the context), so no caller churns.
No transport, wire, or protocol change: the caller fingerprints are already
computed in `on_envelope`; this threads them through `dispatch_request` to the
handler.
## What this PR does
- add `RequestContext` and the `*_with_context` handler registration to the bus
- keep the body-only API as a zero-context wrapper (back-compat)
## Test plan
- `inbox::a_context_handler_receives_the_verified_caller_fingerprints` — the
handler sees the ACTUAL envelope signer's user+agent fingerprints, not a body
value
- `bus::context_handler_sees_the_calling_agent_over_the_transport` — full
in-memory round-trip proving the responder learns the caller from the verified
envelope
- `cargo test -p agent-mesh-bus` 59 pass; `clippy --all-targets -D warnings` clean
## Out of scope
- binding the principal to the QUIC session key (`conn.remote_id()`) as an
additional cross-check — the envelope signature already authenticates the
signer; the connection cross-check is defense-in-depth, a follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_3c98e0d5-b015-4cd7-b279-d5a21b145a37) |
hartsock
added a commit
to Gilamonster-Foundation/newt-agent
that referenced
this pull request
Aug 12, 2026
#1, #2, #3, #8) Close the same-operator direct-dial bypass at the resource owner. Previously the mesh dock responder served ANY caller the handshake admitted (same UserKey), gated only by the local kill-switch — so a sibling AgentKey the operator never approved could bind a DockClient and list / mirror / INJECT directly, bypassing the hub-side gate entirely. NewtDockService now uses agent-mesh's `handle_requests_with_context` (agent-mesh PR #75) to get the VERIFIED caller principal (the envelope signer), and `authorize_caller` resolves that agent fingerprint against THIS peer's OWN signed dock registry before any disclosure or side effect. Same operator is authentication, not authorization: an unapproved sibling is refused here. Fail-closed by default; `NEWT_INSECURE_DOCK_NO_APPROVAL` is the named unsafe opt-out. Scope is now typed authority, not signed decoration: `DockScope { Mirror, MirrorInject }` is enforced per operation — a Mirror dock may list + read transcript but is refused inject. An unknown scope token fails to deserialize, so a tampered/forward scope drops the whole record at load (fail-closed). CLI defaults to least authority (`--scope mirror`). Revocation linearization: the responder re-reads the registry on every request, so once a revocation commits the next request from that caller is denied (`approved()` excludes revoked rows). The false `verify_at(gen)` doc is removed. Test plan (macOS/arm64, native): - newt-mesh: `a_sibling_agent_the_operator_never_approved_is_denied_over_the_mesh` — REAL loopback QUIC, three agents / one UserKey: approved hub served, unapproved sibling denied on list/transcript/inject, its inject never lands; `dock_lifecycle_over_loopback_mesh` (approved caller) still green; `a_mirror_scope_caller_can_read_but_never_inject`, `a_revoked_caller_is_denied_on_the_next_request_linearization`, `an_unapproved_caller_is_refused_before_any_disclosure` (deterministic). - newt-core: DockScope per-op + parse + unknown-token-drops-record; 17 tests. - drive.sh 40/0; clippy -D warnings clean on newt-mesh/newt-core. Depends on Gilamonster-Foundation/agent-mesh#75 (the RequestContext seam); CI ref pinned in a follow-up commit. Part of the PR #1643 adversarial security closure (fronts: responder authz, scope, revocation). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
recv_envelope already verifies the envelope signature, but accept_conn forwarded the envelope without checking that its signer owns the QUIC session it arrived on. A validly-signed envelope replayed or relayed over a different peer's connection would then be authorized as its original signer. Add a pure envelope_matches_session(session_id, env) helper and reject at accept_conn any envelope whose cert_chain.agent_pubkey does not match conn.remote_id(). This binds the application principal to the transport session (defense in depth over the signature verify). Proven by an_envelope_is_bound_to_its_signers_quic_session and, end-to-end, by the newt-mesh live loopback-QUIC dock tests (approved hub still served, unapproved sibling still denied). Follow-up noted in #75. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0d32a861-5836-41d5-90f8-a72157d3a140) |
This was referenced Aug 13, 2026
Closed
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 this PR does
RequestContextand the*_with_contexthandler registration to the busTest plan
inbox::a_context_handler_receives_the_verified_caller_fingerprints— thehandler sees the ACTUAL envelope signer's user+agent fingerprints, not a body
value
bus::context_handler_sees_the_calling_agent_over_the_transport— fullin-memory round-trip proving the responder learns the caller from the verified
envelope
cargo test -p agent-mesh-bus59 pass;clippy --all-targets -D warningscleanOut of scope
conn.remote_id()) as anadditional cross-check — the envelope signature already authenticates the
signer; the connection cross-check is defense-in-depth, a follow-up.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Note
High Risk
Touches authz surface (caller principal handed to handlers) and inbound admission (session-binding drops mismatched envelopes), both security-critical paths.
Overview
Request handlers can now authorize on who called, not just the request body.
Adds
RequestContext(authenticatedcaller_user_fp+caller_agent_fpfrom the verified envelope) andhandle_requests_with_context/register_handler_with_context. The existing body-onlyhandle_requestsAPI remains as a zero-context wrapper for back-compat.Also hardens iroh accept:
envelope_matches_sessiondrops envelopes whose signer is not the TLS-authenticated QUIC session key, blocking relay/replay of a validly-signed envelope over another peer's connection.Reviewed by Cursor Bugbot for commit 8e4d608. Bugbot is set up for automated code reviews on this repo. Configure here.