feat(core): gateway allowlist, the cross-machine trust boundary - #183
Merged
Merged
Conversation
An in-memory, deny-by-default set of trusted remote device-ids, keyed per individual device rather than per remote machine since wire-mesh-core's relay-hub protocol carries no field attributing a gossiped entry or relayed request to its originating gateway connection. Not yet wired into the hub forwarding/session paths.
forwardAdvertsToHub/pushHubCatchUp now refuse to advertise anything onto the hub until at least one remote device is trusted. Coarse by necessity: wire-mesh-core's relay-hub broadcasts a gossip frame to every connected peer with no per-recipient targeting, so this approximates "advertise only to allowlisted gateways" as "advertise nothing until the operator has opted in by trusting someone".
…trust HubSessionDeps gains isTrusted; consume() now refuses to dispatch a relayed request from an untrusted device-id (a room-domain request gets an explicit unauthorized error, a legacy frame is silently acked and dropped), and connect()'s own directory-merge loop drops any entry whose device-id isn't trusted before it ever reaches onDirectory. The state_sync/state_update filter stays unconditional even for a now-trusted sender: gateway trust means "worth acting on", not "may directly overwrite this side's mesh state".
Pick<...> written literally in a doc comment is parsed as malformed HTML by the tsdoc linter; wrap it in backticks instead, and reference the retiring federation.ts commit by hash rather than a bare hyphen number that reads like a GitHub issue reference.
MeshStore owns one GatewayTrust instance (public, mirroring discovery) and exposes addTrustedGateway/removeTrustedGateway/listTrustedGateways. WireMeshTransport takes it as a trailing constructor argument (defaulting to a fresh, empty instance for every existing call site), passes isTrusted into HubSession, hasAny into both hub-forwarding call sites via connectHubGateway (hub-forwarding.ts's own connect-then-catch-up helper), and gates sendRoomRequest's own hub fallback on the target device being trusted so an untrusted target fails fast with an unauthorized outcome rather than waiting out the hub's own silent-drop timeout. bridge-mesh.ts and test-transport.ts both pass store.gatewayTrust through so the store's own add/remove calls and the transport's own gates read the same set. Extracts listener-registry.ts (addListener/removeListener/listListeners/ advertisedAddresses, previously inline in WireMeshTransport) to stay under this repo's max-lines cap, the same reason hub-session.ts, connection-approval.ts, room-router.ts, peer-lifecycle.ts, and gossip-directory.ts were each split from the same file -- a pure extraction, unchanged behaviour.
Gateway trust (agent-comms#156) is deny-all by default, so every scenario here now trusts the specific remote device-ids it needs before expecting forwarding, directory merge, or DM routing to happen -- trust is keyed per device, not per remote machine, so a non-gateway local peer (a2) needs its own device-id trusted even though its own machine's gateway (a1) is trusted separately.
… tests Deny-all by default (agent-comms#156) means the two transports here no longer discover or message each other, or exercise the state_sync/state_update filter, without each explicitly trusting the other's device-id first. Strengthens the #169 security-finding test along the way: it now proves the filter still applies even to a hub peer this side has explicitly trusted, not merely an anonymous one.
An untrusted target now resolves unauthorized before ever touching the hub; a trusted one falls through to the pre-existing not_connected outcome. Split into wire-mesh-transport-gateway-trust.test.ts rather than growing wire-mesh-transport.test.ts past this repo's max-lines cap, the same reason wire-mesh-transport-hub.test.ts and wire-mesh-transport-shutdown-unref.test.ts were split before it.
Removes a now-redundant double type cast on a test fixture and reflows an import that now fits on one line.
…eway_list_trusted tool actions Follows mesh_set_visibility/mesh_get_visibility's own established shape: three CommsAction variants, a device field on the shared MCP tool parameter schema, buildAction parsing with an exhaustiveness check against every action literal, and three MeshOnlyFeatures-gated CommsTool handlers delegating to MeshStore's own addTrustedGateway/removeTrustedGateway/listTrustedGateways.
…ateway trust readvertiseGossip's periodic tick called sendGossipUpdate on every live session unconditionally, including the hub's own session -- a completely separate mechanism from forwardAdvertsToHub's own already-gated advertiseDevices path, and one that bypassed the gateway trust boundary entirely. A visible local agent's presence, hosted rooms, and self-advert leaked onto the hub the moment the periodic gossip timer fired, regardless of whether any remote gateway was trusted. Caught by gateway-trust.integration.test.ts's own deny-by-default coverage. Fixed via HubSession.ownsSession, so the loop can identify and gate only the hub's own session without ever exposing the raw session object.
Three real-hub scenarios: a remote agent's own directory entry never merges without explicit trust even once a trusted control agent proves gossip propagation had time; an untrusted side's own local agents never reach the hub at all, regardless of whether the far side would have accepted them; and a room request to an untrusted device is refused immediately rather than hanging on the hub's own silent-drop timeout. This is the test that caught readvertiseGossip's own trust-gate bypass in the preceding commit.
Mearman
force-pushed
the
feat/gateway-allowlist
branch
from
September 17, 2026 18:15
cea5a99 to
4ad262b
Compare
Mearman
marked this pull request as ready for review
September 17, 2026 18:17
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
🎉 This PR is included in version 3.13.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.
Implements agent-comms#156, the third leg of the cross-machine mesh epic (#153).
Adds GatewayTrust: an in-memory, deny-by-default allowlist of trusted remote device-ids (not persisted to disk, mirroring v1's own FederationManager.trustedFingerprints). Wired through the entire hub-crossing surface:
Trust is keyed per individual device-id rather than per remote machine: the wire protocol carries no field attributing a gossiped entry or relayed request to its originating gateway connection, only to the individual device (which may be an ordinary local peer forwarded on a remote machine's behalf). This is documented in GatewayTrust's own class doc as the intended, wire-protocol-honest granularity, not a shortfall.
Exposed via three CommsTool actions (gateway_trust, gateway_untrust, gateway_list_trusted), following mesh_set_visibility/mesh_get_visibility's own established shape.
Closes #156