Secure mode¶
synapse hub --secure is the strict multi-seat production umbrella. It composes
the --team-secure trust profile and the
--paranoid exposed-hub profile, then adds bounded per-agent,
per-host, and per-host-connection flood limits. One flag turns on the strongest
shipped hub posture and prints one consolidated report.
--secure generates no credentials, reuses no secret for a second purpose, and
enables no metrics surface. When required operator material is missing it fails
closed before any socket binds or durable store opens, and it lists every
absent input in a single error so the operator can supply them all in one pass.
The mode is for a multi-seat hub that is also network-exposed. For a
loopback-only multi-seat hub use --team-secure; for a single-owner exposed hub
use --paranoid. --secure is an unambiguous superset of both, not a third
overlapping definition of "secure".
What the umbrella composes¶
--secure forces and validates every gate the two subordinate profiles own:
- a connect token;
- a durable event store (
--db); - Ed25519 connection identity binding (
--identity-trust); - role-claim grants (
--role-grants); - private directed routing;
- sender-bound per-message authentication (
--message-auth-key); - deny-by-default ACL enforcement (
--acl-policy); - native WSS (
--tls-certfile/--tls-keyfile); - bearer-only metrics authentication when
--metricsis enabled; - the metrics query-token and insecure-off-loopback relaxations disabled.
team_secure.py and paranoid.py remain the single authorities for their own
checks; the umbrella turns both on and reports once instead of twice.
Flood limits¶
--secure bounds aggregate traffic. When a rate is left disabled it receives the
named preset default; a stricter positive value is preserved; a value above a
preset ceiling is refused so the named posture is never silently weakened. The
burst allowances are held to the same discipline as the rates: a burst above its
ceiling is refused, and a zeroed burst beside a stricter rate receives the preset
default so the token bucket is never unbounded or absent.
| Limit | Preset default | Ceiling | Behaviour |
|---|---|---|---|
| Per-agent message rate | 100/s | 100/s | disabled → default; ≤100 kept; >100 refused |
| Per-agent burst | 20 | 20 | disabled → default; ≤20 kept; >20 refused |
| Per-host frame rate | 500/s | 500/s | disabled → default; ≤500 kept; >500 refused |
| Per-host burst | 100 | 100 | disabled → default; ≤100 kept; >100 refused |
| Connections per host | 10 | 10 | disabled → default; 1–10 kept; >10 refused |
A disabled value and an explicit --rate 0 are indistinguishable and both mean
"disabled"; under --secure that is replaced by the named ceiling. Non-finite
values (nan, inf) are rejected for every rate and burst — at the argument
parser for every hub run, and again by the preset for programmatic callers —
because nan compares false against every ceiling and would otherwise disable
the limiter while appearing configured. These numbers follow the external
audit's recommendations and let a small fleet share one loopback or NAT host
while bounding aggregate traffic; they are an approval point, not an empirical
throughput claim.
Auto flood-enable without --secure (REV-SEC-06)¶
When the hub is not started with --secure, flood limits left at the
disabled default (0) can still be filled automatically if the startup posture
is exposed. The decision lives in core/rate_policy.py and is applied at hub
startup after security profiles run and before limiters are built.
Any of the following triggers auto-enable of the same secure bounded defaults
used above (operator-positive limits are preserved; --secure already fills and
stands down this path):
- non-loopback bind;
- a connect token (or token file) is configured;
- multi-seat intent (see below);
- bridge exposed (see below).
--expect-multi-seat (default off) declares that more than one agent seat is
expected. Multi-seat intent is also inferred from --team-secure / --secure,
--require-role-claim, --require-identity-binding,
--private-directed-messages, a non-empty --identity-trust path, or a
non-empty --role-grants path. Use the explicit flag when those are absent but
the hub still serves multiple seats on loopback.
--bridge-exposed (default off) declares that an A2A and/or MCP bridge is
knowingly reachable alongside this hub (separate process or co-located).
Operators running synapse a2a-serve or synapse mcp against the hub should
pass it: bridge traffic can flood the hub without a connect token, and the hub
does not auto-detect those separate processes.
Local-first loopback single-seat hubs with no token, no multi-seat intent, and no bridge declaration stay unbounded. Neither flag generates credentials or enables a bridge; they only feed the exposure posture for flood auto-enable.
Required operator material¶
--secure still needs the operator to supply real material — the flag checks
presence; the existing loaders remain the authority for parsing and cryptographic
validation. Deliver every secret from an owner-only (chmod 600) regular file
owned by the effective hub service user: an argv value is visible to anyone on
the machine running ps, so the file-backed companions are the production
forms on POSIX systems.
synapse hub --secure \
--token-file /run/secrets/hub-token \
--db /var/lib/synapse/hub.db \
--identity-trust /etc/synapse/identity-trust.json \
--role-grants /etc/synapse/role-grants.json \
--message-auth-key-file /run/secrets/hub-hmac-keys \
--acl-policy /etc/synapse/acl.json \
--tls-certfile /etc/synapse/hub.crt \
--tls-keyfile /etc/synapse/hub.key
--message-auth-key-file holds one KEY_ID:SECRET:SENDER[,SENDER...] entry per
line (# comments allowed), so key rotation is an edit to one file owned by the
same unprivileged account that runs the hub; entries merge with any argv
--message-auth-key values. The loader opens one bounded descriptor with
O_NOFOLLOW, then requires that same descriptor to be a regular file owned by
the effective service user with no group/other permissions. Symlinks,
non-regular files, foreign owners, oversized content, and path-replacement races
fail closed. Errors name only the flag and path, never content. Platforms that
cannot prove POSIX ownership and mode invariants refuse these file forms; run
the hub under WSL/POSIX or use a separately validated native secret provider.
Missing material fails closed with one aggregate error. Add
--metrics-token-file (or --metrics-token) when --metrics is enabled; the
explicit argv token wins when both are present, mirroring --token/--token-file.
What the report says¶
Startup prints one secure mode enforced: line naming the composed gates, one
secure mode effective limits: line with the exact rates and connection cap in
force, and one secure mode missing hooks: line listing controls the preset does
not compose (reusing the paranoid vocabulary). The command never implies that
one flag makes an exposed deployment safe; it makes the current posture obvious,
repeatable, and auditable.
Boundaries¶
--secure is a composition and enforcement switch, not a key manager. It does not
rotate credentials, verify client certificates, load signed-event trust, or
certify a deployment. Those controls ship separately and are listed in the missing
hooks report so an operator can add them deliberately.