Compatibility-preserving encrypted application-data synchronization for web, desktop, JavaScript-native, and native protocol consumers.
The package extracts the behavior already implemented by EasyBC and Family Chores. It does not own application schemas, merge policy, persistence, UI, or lifecycle policy.
A primary package objective is to let a static frontend provide encrypted sync and multi-user sharing without an application-owned trusted backend. The complete interactive flow must be implementable with browser cryptography and passkeys, Google Identity Services, and Google Drive:
- no server-held client secret or refresh token;
- no application database, certificate signer, or authorization service;
- no server required to encrypt, decrypt, invite, accept, reconcile access, or publish a signed revision.
Google remains the OAuth, identity, and storage provider. Participant keys, signed access-control history, and the pinned owner key remain the cryptographic authority. An optional backend adapter may add unattended jobs, notifications, or operational automation, but package correctness and interoperability must never depend on one.
Backendless browser deployments operate while the app is open and may need user interaction when OAuth access expires or a passkey ceremony is required. That limitation must not be hidden by broad Drive scopes or by making a backend mandatory.
Version 0.1.1 was published to npm on 2026-06-30. It removes the
application-specific runtime presets from 0.1.0 and keeps compatibility
profiles consumer-owned.
Version 0.2.0 adds accessible drive.file dataset discovery and fixes
dataset-specific codec routing across the complete Web controller lifecycle,
with equivalent Android and cross-platform fixture coverage. The RC19
candidate passed live existing-profile discovery and reload validation on Web
and Android before stable promotion.
Version 0.2.1 hardens sharing acceptance, Drive write concurrency, link ACL
timing, destructive dataset authorization, authenticated link file manifests,
and cross-context Web sharing-identity creation. Web and Android implement the
same controller and link-manifest behavior. Existing datasets and identities
need no migration, but pending join links created by 0.2.0 or earlier must be
recreated because new recipients reject their unbound sk-files manifests.
Version 0.3.0 adds recipient-accepted profile ownership transfer across Web
and Android. The dual-signed transfer covers every dataset plus the managed
app-root and exchange folders, preserves the prior owner as an admin by
default, and resumes safely after partial Google Drive completion. The built-in
provider path targets consumer Google accounts; Workspace direct transfer is
not claimed by this release.
The tests in this repository cover frozen compatibility vectors, mocked browser providers, package orchestration, and installation/imports from the packed tarball. They do not install this package into EasyBC, Family Chores, or Keynote.
As separate baseline checks, the existing sync-related tests in EasyBC and Family Chores were run unchanged from those repositories. No consumer repository was modified. Consumer migrations and their post-migration test runs remain separate release gates.
npm install @keyneom/sync-kitThe package has no runtime dependencies and is ESM-only.
import {
createV1EnvelopeCrypto,
createWebCryptoBackend,
defineV1CompatibilityProfile,
} from "@keyneom/sync-kit/crypto";
import { createSnapshotSync } from "@keyneom/sync-kit/snapshot";
import { createWebPasskeyProvider } from "@keyneom/sync-kit/keys/web-passkey";
import { GoogleWebAuthorizationProvider } from "@keyneom/sync-kit/auth/google-web";
import {
GoogleDriveAppDataStore,
GoogleDriveSnapshotStore,
} from "@keyneom/sync-kit/stores/google-drive";Applications define their own profile:
const profile = defineV1CompatibilityProfile({
appId: "my-notes",
filename: "my-notes-sync-v1.json",
aad: "my-notes-sync-envelope-v1",
hkdfInfo: "my-notes-content-key-v1",
compression: "gzip-if-smaller",
passkey: {
rpName: "My Notes",
userName: "encrypted-sync",
userDisplayName: "My Notes encrypted sync",
algorithm: -7,
residentKey: "required",
userVerification: "required",
timeoutMs: 60_000,
},
});
const envelopeCrypto = createV1EnvelopeCrypto(
profile,
appCodec,
createWebCryptoBackend(),
);The package does not export profiles for EasyBC, Family Chores, or any other
consumer. Profile values are application configuration. Application data
models, parsing, migration, merge, and fingerprints remain in appCodec, so a
model change does not require a sync-kit release.
Available exports:
/core— provider contracts and standardized errors/crypto— v1 envelopes, profiles, AES-GCM/HKDF backends, base64url, optional gzip, and canonical JSON/AAD helpers/snapshot— serialized snapshot synchronization/snapshot/lifecycle— opt-in browser lifecycle binding/sharing— browser-independent shared-backup protocol types, validation, roles, invitations, signed multi-recipient envelopes, and join-link helpers/sharing/web-crypto— P-256 identities, ECDH key grants, signed revisions, invitation/key-response proofs, and decryption/sharing/controller— headless multi-dataset orchestration, owner pinning, serialized writes, exchange processing, roles, revocation, Drive permission reconciliation, dual-signed/resumable ownership transfer, and optional IndexedDB registry persistence/sharing/control— encrypted, signed coordination ledgers for Picker enrollment, participant provenance, and hard-cutover migration acknowledgements/sharing/web-passkey— passkey-encrypted sharing identities with non-extractable runtime keys and optional IndexedDB ciphertext storage/sharing/account-binding— backendless Google ID-token and WebAuthn challenge binding, signature verification, and account provenance/keys/web-passkey— WebAuthn PRF keys with exact credential selection, unlock coalescing, raw-secret zeroing, and explicit cache clearing/auth/google-web— Google Identity Services with memory-only token reuse, expiry skew, request coalescing, and explicit invalidation/auth/google-web/identity— uncached nonce-bound Google ID tokens for account-binding exchanges/stores/google-drive— low-levelappDataFolderobjects, a typed snapshot wrapper, normal-Drive folder/per-file stores, folder-name helpers, and accessible app-folder discovery/stores/google-drive/sharing— managed app/exchange folders, conditional dataset writes, provenance checks, per-dataset permissions, and read-only discovery of every managed dataset visible to the currentdrive.filegrant/stores/google-drive/picker— explicit folder Picker and Drive Open-with state parsing
The root, /core, /crypto, and /snapshot expose no browser adapter.
Browser globals and script loading are used only after an explicit browser
subpath method is called.
Applications provide their own codec and state callbacks:
const controller = createSnapshotSync({
appId: "family-chores",
codec: {
serialize: (value) => value,
parse: parseFamilyChoresPayload,
merge: mergeFamilyChoresPayloads,
fingerprint: stableFamilyChoresFingerprint,
updatedAt: (value) => value.exportedAt,
},
envelopeCrypto,
keyProvider,
authorizationProvider,
cloudStore,
readLocal,
applyMerged,
envelopeUpdatedAt: (envelope) => envelope.updatedAt,
});
await controller.setup();
await controller.sync("change");
controller.lock();The controller serializes cloud operations, queues at most one real local change behind active work, ignores foreground feedback during OAuth/passkey operations, and avoids encryption/upload when the merged stable fingerprint matches the remote value.
The wire protocol is the native compatibility boundary; the browser adapter is not.
- Android:
com.keyneom:sync-kit-android(moduleandroid/synckit) provides private v1 snapshot crypto,SnapshotSyncController, DriveappDataFolderstorage, and Credential Manager passkey PRF — the same contracts as the npm/crypto,/snapshot, and appData store paths. Published to GitHub Packages. See android-library.md. - Shared backups, mixed-codec invitations, and signed control datasets are implemented on both npm and Android. Browser-only Picker UI remains an adapter concern; Android may hand off to a web Picker grant page because SAF grants do not authorize Drive API access.
- Google/passkey sharing account bindings are implemented on both npm and Android. Consumers own RP ID, exact web/APK origin allowlists, Google OAuth audience, UI, and migration timing; real-device Credential Manager and live two-account validation remain consumer release gates.
- React Native and other JavaScript-native runtimes can inject a
CryptoBackend<K>backed by platform crypto. - Tauri applications can use
/core,/crypto, and the low-level Google Drive object store, but should perform OAuth in the system browser through Authorization Code + PKCE.
See native consumer guidance and the exact v1 compatibility contract.
The experimental sharing surface encrypts each revision with a fresh content key, wraps that key to every participant's ECDH public key, and accepts writes only when an owner/admin/writer signs the complete revision. Viewers receive a decryption grant but are not authorized revision signers.
Sharing follows the package-wide backend-independent objective above. A static frontend can execute the protocol directly against Google Drive; an application-owned server is neither a trust root nor a deployment requirement.
Shared files use normal Google Drive storage with the per-file drive.file
scope. A recipient must select every required shared data/control file through
Google Picker; selecting the parent folder does not grant recursive child-file
access. The app persists the folder ID for profile routing and compares Picker
results to the signed expected file IDs; broad access to Drive is not required.
appDataFolder files cannot be shared.
New integrations can use GoogleDriveFileSnapshotStore, which defaults to an
app-owned top-level folder:
Sync Kit - <appId>/
<consumer filename>
The folder name can be overridden. A selected parent folder is optional rather
than part of the default flow. Existing v1 consumers continue using
GoogleDriveSnapshotStore and appDataFolder until they explicitly migrate.
For shared data, the default policy makes the app folder readable to all app
participants, makes its exchanges/ child writable, and upgrades selected
participants to writer on individual dataset files. This exposes filenames,
metadata, and ciphertext to app participants while cryptographic grants control
decryption. Applications needing metadata isolation can instead use direct file
sharing or limited-access subfolders.
See the protocol and threat model, authoritative implementation handoff, consumer responsibilities, and control dataset and topology migration guide, background notifications. The phased gates remain in the sharing execution plan. The crypto and per-file transport, Picker, protected identity, account binding, signed ancestry/fork policy, key rotation, and controller primitives are implemented. The same sharing fixture is consumed by Java, and the packed package completes an exchange/decryption smoke test. Live Google OAuth/Picker, Drive conditional-write/permission validation, and adoption in an external consumer remain release gates.
Each application owns the profile that preserves its filename, AAD, HKDF label, compression behavior, and passkey names. V1 readers remain separate because their cryptographic contexts are intentionally incompatible.
The Drive snapshot wrapper rejects the wrong appId before making a request.
WebAuthn unlock requests include only the envelope's exact credential ID and
reject the wrong RP ID before opening passkey UI.
Do not change EasyBC's writer to v2 until Android can read v2. V2 envelopes and desktop-specific authorization/key adapters remain deferred.
The package:
- keeps OAuth tokens and derived keys in memory only;
- makes AES-GCM keys non-extractable in the WebCrypto backend;
- zeroes raw PRF output after derivation;
- clears key and authorization caches on explicit lock/delete;
- authenticates each v1 ciphertext with its application-specific AAD.
Applications still control provider configuration, metadata exposure, merge correctness, local persistence, lifecycle timing, and access to the JavaScript runtime. The package does not make a compromised application private.
npm install
npm run checknpm run check verifies deterministic fixtures, lint, types, tests, build
output, packed contents, npm installation, pnpm installation, and every
documented subpath import from the installed tarball.
The extraction plan and phase gates are in docs/implementation-plan.md and docs/execution-checklist.md.
Pushing a version tag publishes both package surfaces:
.github/workflows/publish-android.ymlpublishes the matching Android artifact to GitHub Packages;.github/workflows/publish-npm.ymlrunsnpm run check, verifies that the tag matchespackage.json, then publishes prereleases under thercdist-tag and stable versions underlatest.
npm publishing uses OIDC trusted publishing instead of a long-lived token.
Configure @keyneom/sync-kit once on npm with GitHub organization/user
keyneom, repository sync-kit, workflow publish-npm.yml, no environment,
and the npm publish action allowed. After that, pushing vX.Y.Z is the only
release operation.