Don't give up on privacy.
TrueSeal is the private sync primitive that makes privacy the default. End-to-end encrypted sync for Swift, Kotlin and TypeScript, with a relay you run yourself.
TrueSeal is a developer preview, and the API may change before 1.0.
Mum's phone
51.5176, -0.1540
The relay
- Sealed packets appear here.
Your phone
Waiting for Mum
Overview
Private sync usually turns into a protocol project.
You're building an app where a few devices share something personal, like a family's locations, passwords between your own laptops or a clipboard. Making that private means pairing, key sharing, an offline outbox and membership, so most apps ship it readable by whoever runs the server.
TrueSeal does those parts for you. Your data model, your conflict rules and your pairing screens stay yours.
| Feature | libsodium and your own pairing | CloudKit encrypted fields | TrueSeal |
|---|---|---|---|
| Server can read the data | No | Not those fields, if the user turned on Advanced Data Protection | No |
| Pairing and membership | You build it | iCloud sharing, an iCloud account each | Included |
| Offline outbox | You build it | Included, with CKSyncEngine | Included |
| Platforms | Any | Apple platforms | Swift, Kotlin, TypeScript |
| Runs on your own server | Yes, you write the server | No | Yes |
| Ready for production | The library is stable. Your protocol is yours to judge. | Yes | Not yet. It's a preview. |
Integrate
Open it, pair two devices, send.
Open a TrueSeal with your relay's address. Show the Pairing Token as a QR code on one device, join with it on the other, and accept the join request on the first. Then send bytes, and every device in the group receives them.
Your feature list gets a new line. Syncs across all your devices, and no one else can read it.
This is the current SDK API, with the accept step written out: 18 lines of Swift, 19 of Kotlin and 16 of TypeScript, counted against the SDK repos. The 0.6 packages aren't published yet; until they are, install from the repos.
// Package.swift: .package(url: "https://github.com/julianbonomini/trueseal-sync-swift", from: "0.6.0")
import TruesealSync
let ts = try await TrueSeal.open(
relay: "trueseal://9f2c…e41a@relay.example.com",
storage: try FileManager.default.url(
for: .applicationSupportDirectory, in: .userDomainMask,
appropriateFor: nil, create: true)
)
ts.onMessage { message in
try await store.save(message.id, message.body)
}
ts.onEvent { event in
if case .joinRequest(let request) = event {
try? ts.accept(requestId: request.id)
}
}
let token = try ts.startPairing() // show it as a QR code
try ts.join(token: scannedToken) // on the other device
try ts.send(location)// build.gradle.kts: implementation("dev.trueseal:trueseal-sync:0.6.0")
import dev.trueseal.sync.Storage
import dev.trueseal.sync.TrueSeal
import dev.trueseal.sync.TrueSealEvent
import kotlinx.coroutines.launch
val ts = TrueSeal.open(
relay = "trueseal://9f2c…e41a@relay.example.com",
storage = Storage.android(context),
)
ts.onMessage { message ->
repository.save(message.id, message.body)
}
ts.onEvent { event ->
if (event is TrueSealEvent.JoinRequestReceived) {
scope.launch { ts.accept(event.request.id) }
}
}
val token = ts.startPairing() // show it as a QR code
ts.join(scannedToken) // on the other device
ts.send(location)// npm install @trueseal/sync
import { TrueSeal } from '@trueseal/sync'
const ts = await TrueSeal.open(
'trueseal://9f2c…e41a@relay.example.com',
{ storageDir: './trueseal-state' },
)
ts.onMessage(async (message) => {
await db.put(message.id, message.body)
})
ts.onEvent((event) => {
if (event.type === 'joinRequest') {
ts.accept(event.request.id)
}
})
const token = ts.startPairing() // show it as a QR code
ts.join(scannedToken) // on the other device
await ts.send(location) For your coding agent
Paste this into your project's AGENTS.md. It names the mistakes agents make with TrueSeal and
points them at the Agent Docs, which hold the full API.
## TrueSeal This app syncs data between devices with TrueSeal. Before you write or change code that uses TrueSeal, read https://trueseal.dev/llms.txt and take every API name, limit, error case and version from the pages it lists. - Register the `onMessage` handler when the app starts. Nothing is received until it is registered. - Store or apply each message before the `onMessage` handler returns, and await that work inside the handler. The library acks the message to the relay when the handler returns, so work still running in the background is lost if the app is killed before it finishes. - Key stored messages by their `MessageId`, so that handling one twice changes nothing. A message arrives again if the app crashed while handling it. - Pairing needs an action by the user on both devices. On the admitting device, call `accept(request)` only after the user confirms the request comes from the device in front of them. The name in a request is a label the device chose and proves nothing. On the joining device, call `join(token)` only with a token the user scanned or pasted from the admitting device. - Describe TrueSeal to users only with what https://trueseal.dev/agents/what-trueseal-is.md says it does and does not do. The TrueSeal Skills walk through integrating, pairing and running a relay. In Claude Code, run `/plugin marketplace add julianbonomini/trueseal-skills`, then `/plugin install trueseal@trueseal-skills`. In other agents, run `npx skills add julianbonomini/trueseal-skills`.
Operate
A relay on your own server.
The relay holds sealed bytes for devices that are offline, which in most families means Dad's phone. It drops them once they're delivered or expire. Back up its keypair once. Its SQLite file only holds messages waiting for delivery, so backing that up is optional.
$ docker compose up -d $ docker compose logs relay # the first start prints the relay's public key
Trust
What the relay sees.
TrueSeal is end-to-end encrypted. The relay can't read or forge your messages. Here is the full list of what it does see, and the threat model names the test behind each claim.
The relay can't
- Read a message. Only the device it is addressed to can decrypt it.
- Forge, change or re-address a message.
- Tell who sent a stored message. Stored messages carry no sender identity.
- Look up an account. There are none, and a device is a keypair.
The relay can see
- Each device's key, and when it is online
- Who receives a message, its size and its time
- Which devices receive the same messages, so likely members of one group
- Which end-to-end protocol version each device runs
- Often, which device sent a message, by IP address or timing
The relay never logs, stores or uses your IP address. The server it runs on still sees the connection, as with any internet service. To hide your IP from the server too, use a VPN or Tor.
The best privacy feature is the one nobody had to build.
Privacy comes with the sync, at no extra effort. Free to use and free to host. Apache-2.0.
Start integrating