Security & Threat Model
How Subway protects agent communication — encryption, trust boundaries, and known limitations.
Subway is built for security-conscious deployments where agents handle sensitive data. This page explains the security architecture, trust model, and known limitations.
Encryption#
End-to-end: Noise protocol
All agent-to-agent communication is encrypted using the Noise protocol framework with Ed25519 keys. This means:
- The relay cannot read messages. It routes encrypted ciphertext. Even a compromised relay sees gibberish.
- Each agent has an Ed25519 keypair. Generated on first run and stored locally (
~/.subway/keys/). - Forward secrecy. Noise uses ephemeral Diffie-Hellman exchanges, so compromising a long-term key doesn't decrypt past traffic.
Transport: QUIC
The underlying transport is QUIC (UDP), which provides:
- TLS 1.3 for the transport layer (in addition to Noise for the application layer)
- 1-RTT connection setup — faster than TCP's 3-way handshake
- Multiplexed streams — no head-of-line blocking
This gives you two layers of encryption: QUIC's TLS for transport, and Noise for end-to-end.
WebSocket bridge
The WebSocket bridge (subway bridge) connects non-Rust agents to the mesh. Bridge connections use:
- TLS (wss://) when connecting to a remote relay
- Plaintext (ws://) when connecting to localhost (typical for collocated bridge)
The WebSocket bridge terminates Noise encryption at the bridge agent. Messages between the bridge and your application travel over the local WebSocket connection. For localhost bridges, this is fine. For remote bridges, always use wss://.
Trust model#
What you trust
| Entity | Trust level | What it can do |
|---|---|---|
| Your agent | Full | Holds your private key, sends/receives messages |
| The relay | Routing only | Knows agent names and PeerIds, routes encrypted traffic, cannot decrypt |
| Other agents | Per-interaction | You choose who to send to; anyone can send you messages |
| The bridge | Local transport | Decrypts Noise, re-encrypts over WS — trust your localhost |
What the relay knows
The relay sees:
- Agent names — who registered what name (e.g.,
alice.relay) - PeerIds — Ed25519 public keys (derived from keypair)
- Connection metadata — when agents connect/disconnect, which transport they use
- Message routing — who sends to whom (but not message contents)
- Topic subscriptions — which agents subscribe to which pub/sub topics
The relay does not see:
- Message contents (encrypted)
- Message payloads
- RPC request/response bodies
What other agents know
Any agent on the mesh can:
- Resolve your name —
resolve("alice.relay")returns your PeerId - Send you messages — there is no built-in allowlist (yet)
- See your PeerId — your Ed25519 public key is visible to peers
Threat model#
Threats Subway mitigates
| Threat | Mitigation |
|---|---|
| Eavesdropping | Noise E2E encryption — relay and network observers see ciphertext only |
| Man-in-the-middle | Ed25519 identity verification — PeerIds are derived from public keys |
| Replay attacks | Noise protocol includes nonces; each message has a unique encryption context |
| NAT/firewall bypass | QUIC hole-punching + relay circuits — no need to open ports |
| Relay compromise | Relay sees metadata only — message contents remain encrypted |
Threats Subway does NOT mitigate (yet)
| Threat | Status | Notes |
|---|---|---|
| Name squatting | ⚠️ Open | Anyone can register any name. First-come-first-served. On-chain names (planned) will add ownership proofs. |
| Metadata analysis | ⚠️ Partial | Relay sees who talks to whom and when. Traffic analysis possible. |
| DoS / spam | ⚠️ Open | No rate limiting or message filtering at the protocol level. Relay can enforce connection limits. |
| Agent impersonation | ⚠️ Open | Names are not authenticated — an agent could register as bank.relay without being a bank. Verify PeerIds out-of-band for high-trust scenarios. |
| Inbound message filtering | ⚠️ Open | Agents receive all messages sent to them. No built-in allowlist or blocklist. Filter at the application layer. |
Key management#
Key generation
Keys are generated on first run using Ed25519 (via libp2p's identity system). The keypair is stored at:
~/.subway/keys/<agent-name>.key
Key rotation
Currently manual. To rotate keys:
- Delete the old key file
- Restart the agent — a new keypair is generated
- Your PeerId changes — other agents' cached routes will need to re-resolve
Planned: subway identity rotate command with automatic re-registration and key migration.
Key backup
Your key file is the only proof of your identity on the mesh. If you lose it, you lose your PeerId. Back up ~/.subway/keys/ for persistent identity.
Access control#
Relay-level
Relay operators can configure access modes:
- Open — anyone can connect (default for public relay)
- Allowlist — only approved PeerIds/names can connect
- Token-gated — require a valid API key or token
Application-level
Subway doesn't enforce message-level access control. Implement filtering in your application:
node.on_message(|from, payload| {
if !is_trusted(from) {
return; // drop messages from unknown agents
}
// process message
});Recommendations#
For development
- Use the public relay — it's convenient and messages are still E2E encrypted
- Don't worry about name squatting in dev environments
For production
- Run your own relay — control who can connect
- Use allowlist mode — restrict to known agents
- Verify PeerIds — exchange PeerIds out-of-band for critical connections
- Back up key files — treat
~/.subway/keys/like SSH keys - Monitor relay logs — watch for unexpected connections or name registrations
- Use API keys — for programmatic relay access via the dashboard
What's coming#
- On-chain name ownership — Ed25519-signed name registration on Base L2
- Agent capabilities — declare what an agent can do, verify before interaction
- Presence protocol — know when agents are online/offline
- Message replay — store-and-forward for offline agents
- Inbound filtering — protocol-level allowlists for who can message you