Repository navigation
v0.99.31
Upgrade notes
- Hubs off loopback, or declared multi-seat, now need provisioned identity.
- They refuse to start without
--identity-trustand
--require-identity-binding. - Enrol each seat with
synapse identity machine-key --sender NAME --trust FILE.
--insecure-unbound-identitykeeps the old behaviour with a warning. - The production
docker-compose.ymlneedsSYNAPSE_IDENTITY_TRUST_FILEand
SYNAPSE_HEALTH_KEY_FILE; seedocs/deployment.md. - Single-owner loopback hubs are unaffected.
- They refuse to start without
- A
--dbhub createsDB.rowmac.keybeside its log on the first start.
Back it up with the log: a log whose rows carry MACs refuses to start without
it. - Downgrade.
- An older version can still open the log. The rows it writes carry no MAC, so
a later 0.99.31 start quarantines them and serves read-only. - To accept them after review, clear every row MAC
(UPDATE events SET mac = NULL) and move the key file aside, so a new key
starts at the current tip.
- An older version can still open the log. The rows it writes carry no MAC, so
- The durable-ingress chat quota is on by default
(--no-durable-ingress-quotaopts out). - Pathless MCP claims now claim the whole Git worktree. Outside Git they
needtask_only=true.
Added
-
Add two
synapse doctorchecks against MCP server impersonation. An MCP
client cannot tell the genuinesynapse mcpfrom a server that reports the
same name and tools. Such a server can answerclaim grantedwhile the hub
holds nothing (reproduced).mcp-host-sourceslists every server whose name containssynapsein the
documented host sources and warns about any that does not launch this
installation'ssynapse mcp. The sources are Claude Code's user and local
scope, the project.mcp.json, the Synapse Claude plugin and the Codex
profile.mcp-claim-guardwarns when a host has such a server but no claim guard.
The guard asks the hub itself before an edit, so it stops a false grant.- Both checks are read-only and warn rather than fail.
synapse adapters mutation-statusnow also counts.claude/settings.local.jsonand
the Synapse plugin's hooks as a configured Claude guard.
-
Let
synapse healthsign its registration.--identity-key-fileand
--identity-key-idname an Ed25519 key enrolled in the hub's
--identity-trustbundle for the probe's--name(defaultHEALTH), so a
container healthcheck can probe a hub started with
--require-identity-binding. On a token-guarded hub a refused registration
gets no welcome, so an unenrolled probe reports the hub unhealthy. Giving only
one of the two flags, or a key file that cannot be read, exits2as a
configuration error. -
Add
synapse identity machine-key. Every client signs its registration with
this machine's auto-provisioned key; the command prints that key as a
trust-bundle entry for the--sendernames given, or enrols it with--trust.
A seat can then join a hub started with--require-identity-bindingor
--team-securewithout per-command key flags. A key is enrolled once per
bundle; a second enrolment exits2. -
Ship the
cryptographyruntime in the container image. The hash-locked image
closure now includes theencryptionextra (cryptography50.0.1 withcffi
andpycparser, the versions the other locks pin). Before, the image could
not verify an Ed25519 identity-bound registration, so--require-identity-binding
and--team-securecould not work inside the container. The compose smoke
now signs a registration inside the running image and checks that a foreign
sender is refused.
Changed
-
Record durable message-auth sequence floors under
--secureand--team-secure.
When per-message auth and a durable replay ledger are present, an unset
--message-auth-sequence-floor-modenow becomescompat, which records each
key's high-water sequence without refusing anything.strictis not the profile
default. Tested through the public claim route: the shipped client numbers
frames from 1 in every process, so a strict floor refused a restarted agent's
first frame withsequence_mismatch. It stays an explicit operator choice. -
Accept a retried chat once. Once a copy with a
client_msg_idhas reached a
live recipient (or a peer hub's forward outbox), a same-content retry from the
same sender within 24 hours in the same hub process is no longer stored,
journalled or delivered again. The sender gets asystemframe with
duplicate: truenaming the first copy'smsg_id, and the same id with
different content is refused. A retry of a chat that reached nobody is still
routed again, so redelivery flows such asreview-feedback routekeep working.
Before, every retry was delivered and journalled again (reproduced: two sends
reached the recipient twice). Chats withoutclient_msg_idare unchanged, and
peer-forwarded chats keep theirforward_iddedupe. New metrics:
synapse_chat_duplicates_suppressed_totaland
synapse_chat_client_id_conflicts_total. This revises INV-DG-1 in
docs/coordination-spec.md. -
Anchor the anti-rollback checkpoint while the hub serves. Before, the hub
anchored its log only at start and never closed the checkpoint store, so
cutting events written while it ran passed the next start (reproduced on the
previous code). Now it anchors every--checkpoint-intervalseconds (default
60), folding only new events, and a clean stop anchors at once and closes the
store. A hub served again re-verifies before serving. The declared window is
the interval: after a crash, writes newer than the last anchor can still be cut
undetected.SynapseHubgainscheckpoint_interval, andEventStore.iter_events
gainsafter_seq. -
Require provisioned identity on exposed and multi-seat hubs. A hub that binds
off loopback, or that declares a multi-seat profile on loopback
(--expect-multi-seat,--bridge-exposed,--private-directed-messages,
--require-role-claim,--role-grantsor--identity-trust), now refuses to
start with exit2unless it runs with--identity-trustand
--require-identity-binding. The refusal happens before any durable store is
opened. Before, a shared token was the only guard, so any token holder could
register as any seat. Migration: enrol each seat's key
(synapse identity machine-key --sender NAME --trust FILEon each client
machine, orsynapse identity keygen --trust FILE) and add both flags, or pass
--insecure-unbound-identityto keep the old behaviour with a warning.
--paranoidand--securerefuse that flag. The production
docker-compose.ymlnow mountsSYNAPSE_IDENTITY_TRUST_FILEand a signed
health-probe keySYNAPSE_HEALTH_KEY_FILE(seedocs/deployment.md). The
local-development compose file carries the explicit downgrade. Single-owner
loopback hubs are unaffected, and library users constructingSynapseHub
directly are unaffected. A peer hub granted by--multihub-serving-policy
proves its id with its pinned mutual-TLS client certificate. So cross-hub
forwarding, watch and claim routing keep working into an identity-bound hub,
while a socket that uses the peer's id without that certificate is still
refused. -
State the open loopback default explicitly. A hub on loopback without a
connect token or identity binding now prints a one-line start-up notice, and
SECURITY.mdhas a new "Open loopback default" section on who can join such a
hub and which profile to use instead. -
Bound how long a disconnected holder keeps its claims. A claim survives its
holder's disconnect for the hub's lease window (--lease-offline-ttl, default
3600 s). During that windowsynapse statemarks itholder-offline=<s>, and
the state snapshot carriesholder_onlineandholder_offline_seconds. After
the window the next claim attempt releases the claim, journals the release and
announces it withreleased_by: "hub:holder-offline". Before, such a claim
stayed locked until its lease TTL even when its holder never came back. The new
metric issynapse_claims_released_abandoned_total. -
Turn the durable-ingress chat quota on by default for
synapse hub. The limit
is 100 chats and 1 MiB of serialized chat frames per quota principal per 60
seconds; zero values for the size flags keep these defaults.
--no-durable-ingress-quotaturns it off and is refused under--secure.
Before, a default hub accepted unbounded durable chat growth. Measured on a
100-day production log, the peak was 49 chats and 72,560 bytes per window, so
no recorded traffic would have been refused. Library users constructing
SynapseHubdirectly are unaffected. -
Make the scope of a claim without paths explicit. MCP
synapse_claimwithout
paths now claims the MCP process's whole Git worktree, and the reply names it.
Outside Git it is refused. The newtask_only=trueform locks the task id alone
with no file scope, like a keylesssynapse lock. Previously such a claim was
sent with an empty worktree, which made it a lock over the hub's shared default
namespace while the reply said "the whole worktree". Remote (HTTP) MCP claims
must usetask_only=true. The JavaScript SDK'sclaimgains a
{ taskOnly: true }option and throws on a claim with neither paths nor a
worktree identity.
Security
-
Authenticate every durable event row (K4-REPLAY).
- A
--dbhub MACs each row it writes (HMAC-SHA256 overseq,ts,kindand
payload) with a key kept outside the log, inDB.rowmac.key(owner-only,
created on first start) or at--row-mac-key-file. - At start, rows written since the key existed are checked. A missing or wrong
MAC quarantines the row through the journal-recovery gate: replay skips it
and the hub refuses mutations. - Before, anyone who could write the SQLite file could append a claim or
release that replay applied as hub state. The anti-rollback checkpoint does
not catch appended rows. - Existing rows are the unchecked legacy prefix.
- A log with MACs refuses to start without its key.
- The SQLCipher migration carries the MACs.
EventStoregainsenable_row_mac,has_row_macs,row_quarantine, and an
apply_row_quarantineswitch oniter_eventsthat the Merkle commitment
uses to fold raw rows.synapse sandbox run --attest DBMACs its row with the hub's key when
DB.rowmac.keyexists, so an attestation into a hub log is not quarantined.
- A
-
Apply the WebSocket handshake's Host and Origin guard to the
/metricsand
/healthprobes before they answer. An open loopback hub with--metricsand
no metrics token answered anyHost, so a DNS-rebinding page could read its
metrics and health (reproduced: a foreign Host got200). Scrapers that reach
the hub by a name other than the loopback names must now pass it with
--advertised-host.
Artifact checksums (SHA-256)
8858c6a030a74a0b2f468af93ef07e997d8f86031032697c36e3704bd8a1ec6b synapse_channel-0.99.31-py3-none-any.whl
f9f041860c229ff6cfecc4635289ff4935b0b0e043ea2eed40a51782c37bf25c synapse_channel-0.99.31.tar.gz
5769d2e8b70eca90dfb6883e977100d4969fdb6ccb62a9e1422e56d13f70d2da synapse-channel-v0.99.31-sbom.cdx.json