Skip to content

fix(content): say what the sync relay actually carries - #5

Merged
lessevv merged 3 commits into
mainfrom
fix/relay-copy-accuracy
Sep 13, 2026
Merged

lessevv merged 3 commits into
mainfrom
fix/relay-copy-accuracy

Conversation

@lessevv

@lessevv lessevv commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

The site made two claims about the sync relay that the product does not support. Both were live in English and in Dutch, on the home page, the feature cards, the privacy page, the sync page, the security page, the Dutch FAQ and the served llms.txt.

Do not merge this yet. Auto-merge is deliberately not armed — this is public-facing privacy copy and the timing is the owner's call.

The two false claims, and what the code says

1. "a relay that only ever holds ciphertext it cannot read"

Modules/Sync/Internal/Pairing/PairingPeerOutbox::queueFor() json_encodes the pairing frame and hands the raw blob straight to RelayMailbox::deliverIfUnderQuota(). There is no encryption step. A PAIR_RESPONDER_ACCEPT sits in the relay mailbox as readable JSON carrying token_hash, both device ids, the responder's Ed25519 and X25519 public keys, and responder_name — the display name of the device.

The relay has exactly two production users. GdkRotationService::deliverWrap() seals its epoch key with sodium_crypto_box_seal() to the recipient's X25519 key, so those really are opaque. The pairing courier seals nothing. PAIR_CONFIRM is Ed25519-signed over PairingFrame::confirmSigningMessage(), which makes it unforgeable — not unreadable.

2. "if one device is asleep, the changes wait in a relay"

Transactions never cross the relay at all; op-log frames only ever travel the direct Noise session. The application already tells its own users this, in Modules/Sync/Resources/lang/en/devices.php:

A relay lets two devices complete pairing and exchange encryption keys when they cannot reach each other directly. Transactions themselves still sync only when both devices are on the same network.

Modules/Sync/tests/Feature/TheRelayHelpPromisesNoPullThePhoneCannotMakeTest.php pins that sentence and asserts the screen never offers the relay "as a road a transaction can take". The website was contradicting the application's own settings screen.

What changed

The claims are corrected, not deleted. A privacy page that goes quiet about the relay is worse than one that describes it, so the sync and security pages now say what a relay holds in both directions: key handovers sealed to the receiving device, pairing messages that are not sealed, the metadata it sees, and the fact that it cannot interpose itself because the safety number covers the keys.

Nineteen strings across seven files, nine English and ten Dutch:

File Surface
src/i18n/en.ts ×3 home: promises, privacy, v2.0 EN
src/i18n/cards.ts sync feature card EN
src/i18n/pages-en.ts ×4 privacy bullet, sync section, security card, new security "does not defend against" entry EN
public/llms.txt the served machine-readable summary EN
src/i18n/nl.ts ×3 home: beloften, privacy, v2.0 NL
src/i18n/cards.ts sync feature card NL
src/i18n/faq.ts ×2 "Hoe werkt sync zonder server?", "Kunnen jullie mijn gegevens lezen…" NL
src/i18n/pages-nl.ts ×4 privacy bullet, sync section, security card, new "waar het niet tegen beschermt" entry NL

The Dutch is written from the shipped Dutch help string in Modules/Sync/Resources/lang/nl/devices.php rather than translated from the new English, so the site and the application say the same thing in the same words.

A second commit fixes one more claim that is outright false about security: the Dutch privacy bullet on device removal had lost the qualifier its English counterpart carries, so it promised full revocation where the product provides forward-only revocation. Every other revocation sentence on the site already says "daarna"; this was the one that did not.

Verified

npm run check 0 errors, npm run build 33 pages, typos clean. The old phrasing is gone from dist/; the new phrasing renders on /privacy, /sync, /security, /nl/privacy, /nl/sync, /nl/security, /nl/faq and the home pages.

Two sites left alone on purpose, because the false claim originates upstream

  • src/data/seed-milestones.ts:45 — "zero-knowledge relay fallback" reproduces spec 00-overview/roadmap.md:140 verbatim and is fetched live from the spec at build time. Editing the seed would only drift from the canonical text it mirrors.
  • The English FAQ answer to "How does sync work without a server?" is rendered straight from spec 90-appendix/faq.md, which still says "changes wait in a relay that only ever holds encrypted data it cannot read". The Dutch translation in this repo is corrected here; the English will keep contradicting it until a spec PR lands.

The same claim also sits in spec 00-overview/glossary.md ("Zero-knowledge relay … Holds opaque ciphertext blobs"). A companion spec PR is needed for all three.

Spec: REPO-R41, REPO-R43

The site made two claims about the sync relay that the product does not
support, in English and in Dutch, across the home page, the feature cards,
the privacy page, the sync page, the security page, the Dutch FAQ and the
served llms.txt.

"A relay that only ever holds ciphertext it cannot read" is false.
PairingPeerOutbox::queueFor() json_encodes the pairing frame and hands the
raw blob to RelayMailbox::deliverIfUnderQuota() with no encryption step, so
a PAIR_RESPONDER_ACCEPT sits in the mailbox as readable JSON carrying both
device ids, the responder's Ed25519 and X25519 public keys and the display
name of the device. The relay has two production users: GdkRotationService,
whose epoch wraps are sealed with crypto_box_seal to the recipient's X25519
key, and the pairing courier, whose frames are not sealed at all. Only one
half of that was ciphertext.

"If one device is asleep, the changes wait in a relay" is false too.
Transactions never cross the relay; op-log frames only ever travel the
direct Noise session. The product already tells its own users so —
Modules/Sync/Resources/lang/en/devices.php reads "A relay lets two devices
complete pairing and exchange encryption keys when they cannot reach each
other directly. Transactions themselves still sync only when both devices
are on the same network", and Sync's
TheRelayHelpPromisesNoPullThePhoneCannotMakeTest pins that sentence. The
website was contradicting the application's own screen.

The claims are corrected rather than deleted. A privacy page that goes
quiet about the relay is worse than one that describes it, so the sync and
security pages now state what a relay holds in both directions: key
handovers sealed to the receiving device, pairing messages that are not
sealed, the metadata it sees, and the fact that it cannot interpose itself
because the safety number covers the keys. The security page's "what it
does not defend against" list gains the pairing metadata, which is where a
reader looking for the cost of a relay will go.

The Dutch is written from the shipped Dutch help string in
Modules/Sync/Resources/lang/nl/devices.php rather than translated from the
new English, so the site and the application say the same thing in the same
words.

Left alone deliberately: the "zero-knowledge relay fallback" roadmap label
in the milestone seed reproduces 00-overview/roadmap.md verbatim and is
fetched live from the spec at build time, and the English FAQ answer is
rendered straight from 90-appendix/faq.md. Both carry the same false claim
and both need a spec PR; correcting the seed here would only drift from the
canonical text it mirrors.

Spec: REPO-R41, REPO-R43

Signed-off-by: Wessel Verheij <info@nightworks.io>
…t true

The privacy page's English bullet says a removed device "cannot read
anything sent afterwards". Its Dutch counterpart lost the qualifier and
reads "zodat het verwijderde niets meer kan lezen" — so the Dutch page
claims full revocation while the English page claims forward-only
revocation, which is the one the product provides.

Revocation rotates the group key and re-wraps it to the devices kept; it
cannot reach back into what the removed device already holds. The Dutch
sentence as written was outright false about what removing a device
protects, which is why it is corrected here alongside the relay claims
rather than left on the list of copy to review.

Every other revocation sentence on the site already carries the qualifier
("van wat er daarna verstuurd is", "van alles daarna"), so this is the one
site that was out of step, not a policy change.

Spec: REPO-R43

Signed-off-by: Wessel Verheij <info@nightworks.io>
@github-actions github-actions Bot added the brand Design system, visual language, marks label Sep 13, 2026
@github-actions

Copy link
Copy Markdown

📎 Spec references

This PR cites the following spec identifiers:

  • REPO-R41 — The site MUST state what a sync relay can and cannot observe.
  • REPO-R43 — The site MUST NOT claim a protection the product does not provide.

Updated automatically on each push.

@github-actions github-actions Bot added the dependencies Dependency update (Renovate / Dependabot) label Sep 13, 2026
… ranges

`security / osv-scanner` refused this branch. Clearing the first two advisories
uncovered six more behind them, one critical:

    astro      7.1.4  -> 7.3.2   GHSA-26w7-cxv4-gfx2  9.8
                                 GHSA-376h-93r7-7g6f  6.3
    sharp     0.35.3  -> 0.35.4  GHSA-rgj7-g3m4-5g8c  8.9
    js-yaml    4.3.1  -> 4.3.2   GHSA-2883-xcg3-v3hh  7.5
    fast-uri   3.1.5  -> 3.1.7   four advisories      7.5 (dev)
    svgo       4.0.2  -> 4.1.0   GHSA-4vpr-x523-8j87  6.1
                                 GHSA-w27v-7q3p-w38r  8.2

Every one is satisfied inside the range package.json already declares, so this
is the lockfile alone. `npm update`, never `npm install <pkg>@latest`: the
latter promotes a transitive package to a direct dependency, which it did for
svgo on the first attempt and which would make this repo own a version astro
is responsible for.

`npm audit` now reports 0 vulnerabilities, and the site still builds 33 pages.

Unrelated to the relay copy this branch carries. The advisories predate it and
were blocking the merge; the scanner reports them all at once, so fixing only
the two first named would have left a critical standing behind a green check.

Signed-off-by: Wessel Verheij <info@nightworks.io>
@lessevv
lessevv force-pushed the fix/relay-copy-accuracy branch from b2ba2b3 to 75aee9e Compare September 13, 2026 17:51
@sonarqubecloud

Copy link
Copy Markdown

@lessevv
lessevv merged commit 5bbb704 into main Sep 13, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

brand Design system, visual language, marks dependencies Dependency update (Renovate / Dependabot)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant