Skip to content

Gateway enforcement never consumes principal-keyed trust #192

Description

@Mearman

agent-comms#187 built the principal-trust primitive (GatewayTrust.addPrincipal/isTrustedFor, admitAgentForDm's delegable-principal path, delegateDmSendToDevice) and proved it with a real end-to-end test, but the actual gateway enforcement gate never got wired to consume it. Confirmed by reading hub-session.ts directly: deps.isTrusted(deviceHex) is checked against every gossiped directory entry and every relayed manage-request before either is acted on, and it's a bare boolean over a device-id string, never isTrustedFor. Gossiped adverts and relayed frames carry no verifiable capability token at this layer at all, so there is nothing to chain-walk against a trusted principal even if the call site were changed.

Practical effect: the gateway's own admission gate, the thing deciding whether cross-machine traffic gets acted on at all, still requires trusting each of the other person's devices individually via the bare gateway_trust path. #187's principal path only helps one layer later, for dm:send admission once traffic is already past the gateway gate. This is the real remaining friction confirmed while answering "are we able to do user-user agent comms."

Closing it for real needs a new wire mechanism, not just a call-site change: a gossiped advert or relayed request would need to carry a verifiable token (or at minimum a signature back to a principal) the gateway can check against isTrustedFor, since bare device-ids carry no such proof today. GatewayTrust's own doc comment already names "gating the hub itself" as deliberately out of scope for #156; this is a related but distinct question, since it's about what a gateway checks on already-flowing gossip/relay traffic, not about gating the hub's own connection acceptance.

Decided: stop gating room-domain verbs behind the coarse bare-device isTrusted check at all. This session's own hub-session.ts already argues the right answer in its own doc comment without the code following through on it: "A real room-domain verb (room.send, room.join, room.notify, ...) carries no equivalent risk, it is independently gated by its own room:member capability token, verified regardless of which transport path it arrived over." That check is real cryptographic verification (signature plus chain-walk) and, since #187, already principal-aware via isTrustedFor. Stacking the coarse bare-device gate in front of it is redundant, not protective, and for room.join specifically (the one deliberately ungated verb, whose own security model is "ask a human") it actively defeats the protocol's own intended design by never letting an untrusted device reach that approval step at all.

Concretely: consume()'s isTrusted check should apply only to traffic with no independent per-message security of its own, the legacy FRAME_VERB path (state_sync/state_update, the genuine spoofed-delivery risk PR #169's review caught and fixed) and gossip directory-merge. Room-domain verbs bypass the gateway-level device gate entirely and rely on their own capability-token verification (or, for room.join, the existing human-approval flow) exactly as the code's own comment already describes as the intended model.

One real, free improvement while touching this: gossip directory-merge currently checks only bare-device isTrusted. Extend it to also accept GatewayTrust's principal set, so a gossiped entry from a device already known to belong to a trusted principal (via some prior verified interaction) surfaces without needing its own separate individual bootstrap.

No wire-mesh-core protocol change needed (rejected: gossip/relay frames carrying a verifiable token, the biggest and slowest option, when the actual fix is removing a redundant check rather than adding a new one). No new human-approval mechanism needed (rejected: treating #188's connection-code bootstrap as sufficient on its own, since it still requires re-bootstrapping every one of a principal's devices individually, which is exactly the friction principal-keyed trust exists to remove).

Activity

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions