Changelog
This changelog is a reader's summary of what Alcove does and how that changes per release: the major capabilities, breaking changes to them, and the headline security posture. It is not a log of every commit or every refinement (that is the git history). Each entry states the capability in a line or two and points to docs/ for the full behavior and docs/spec/ for wire-level detail. The format loosely follows Keep a Changelog; Alcove uses semantic versioning.
[Unreleased]
Added
- The CLI runs recurring, authenticated SFTP exchanges between two parties from a saved config, and one-off exchanges with no config via a
zero-setupURL. Seedocs/CLI.md. - The
filedropchannel exchanges through a shared local directory where neither SFTP nor WebRTC is available. Seedocs/COMMUNICATION.md. - The CLI runs exchanges over the
webrtcchannel: a peer-to-peer data channel reached through a peer-coordination server, with no file server or shared directory in between. Either party may be a browser, so a CLI party and a web-app party can exchange with each other. Seedocs/CLI.md. - The web app runs backend-free, invitation-based browser-to-browser exchanges over WebRTC: one side mints a single-use invitation link, the partner opens it to review the terms and consent, and the two browsers rendezvous directly. See
docs/WEB_APP.mdanddocs/SECURITY_DESIGN.md. - The web app runs recurring exchanges: either party can save an exchange as recurring at invite creation or at accept (its terms and rotating secret persisted in the browser, origin-isolated), then run it again with the same partner without a new invitation. A saved exchange exports as a plaintext credential file under operator custody, for backup against browser-storage eviction and for moving the exchange to another device. See
docs/MANAGED_EXCHANGE.md. - A recurring (managed) exchange in the web app exports to the command line: the detail surface hands the operator the
alcove.yamland.alcove.keyfilesalcove exchangereads, the ready-to-run invocation, and cron / Task Scheduler lines, and spends the browser's copy on the operator's confirmation so exactly one party holds the secret. Seedocs/MANAGED_EXCHANGE.md. - The web application installs as an offline-capable app: a dependency-free service worker caches the app shell and every screen's code at launch, so an installed app opens offline, states plainly what works without a connection, and applies a new version only on an operator-confirmed reload. See
docs/MANAGED_EXCHANGE.md. - The web app can run as a single-party console appliance, driving the party's own
alcoveexchange (filedrop, SFTP, or, as the inviting party, WebRTC) as a server-side subprocess so an operator runs and downloads a result without the command line. Off by default and gated behind a data root; reach it only from the operator's own machine by publishing the container port to host loopback. Seedocs/CONSOLE.mdanddocs/spec/SERVER_JOB_API.md. - The console runs a Direct exchange: a no-invitation, lowest-ceremony path where both parties run against a server they agreed on out of band, with linkage terms inferred from each party's file (previewed for confirmation) and no shared secret, so protection is transport-only -- the console analog of the CLI's zero-setup. See
docs/CONSOLE.mdanddocs/spec/SERVER_JOB_API.md. - The console can open the
alcove.yamlalready sitting in its mounted working directory and pre-fill every authoring step from it, then hand back a configuration byte-compatible with what the command line writes: the console does the settings and the command line does the work. Seedocs/CONSOLE.mdanddocs/spec/EXCHANGE_FILE.md. - The console sends the partner an accept kit: a printable, plaintext instruction sheet, downloaded alongside the invitation, that takes a partner who has only Docker -- Desktop or Engine -- to the point of accepting an SFTP or shared-directory exchange. It carries no secret and no invitation token. See
docs/CONSOLE.md. - The console's WebRTC exchanges can use a TURN relay: the operator names the relay urls (and optionally a relay registrar) beside the coordination server, the invitation names the same relay for the partner, and an exchange completes when either side's network blocks direct connections. See
docs/CONSOLE.mdanddocs/COMMUNICATION.md. - The hosted web app lists the project's TURN relay in every browser exchange, beside the default STUN server and any relay the operator or the invitation names, and enrolls the exchange's relay key at the project's registrar right before connecting, so two browsers connect on a network that blocks direct paths. The project, as the relay's operator, learns both parties' addresses on every exchange, and the app says so where an exchange is started or accepted. See
PRIVACY.mdanddocs/COMMUNICATION.md. - A WebRTC exchange, CLI or browser, falls back to a WebSocket relay when no WebRTC path opens, so two parties on a network whose inspection stops WebRTC and TURN still connect over one
wss://connection each; the relay forwards the exchange's encrypted messages unread. Seedocs/CLI.mdanddocs/spec/WEBRTC_TRANSPORT.md. - Each party writes a local, self-attested exchange record of what it disclosed, suitable for a HIPAA or FERPA accounting of disclosures.
alcove verify-receipt(CLI) and the web "Verify a receipt" page re-check a stored record for internal consistency, read-only. Seedocs/spec/EXCHANGE_RECORD.md. - Authenticated CLI exchanges configured with a signing identity (
signingblock, certificate mode) also produce a dual-signed exchange receipt: both parties sign the same terms and data-flow facts and swap signatures over the connection, each verifying the partner's pinned certificate before its signature, for a third-party-verifiable record of the data flow.alcove verify-receiptchecks a stored one later, on either party's side or an auditor's: both signatures, both certificates, and -- for the top-lineverifiedverdict -- both certificate slots anchored to something the verifier holds (a pinned partner fingerprint per slot, or the verifier's own signing identity for its own slot, resolved from--identity-fileorsigning.identity_file); a run anchoring only one slot gradesincomplete. A receipt is also paired to one exchange run: the record and the receipt of a run carry a shared run binder, so a receipt read beside a different run's record fails, and one verified with no record beside it gradesincomplete. The web "Verify a receipt" page checks a stored one on the same terms, anchoring the partner's slot with a typed pinned fingerprint and its own with an exported certificate -- no private signing key is used in the browser. Seedocs/spec/PROTOCOL.mdanddocs/spec/EXCHANGE_RECORD.md. - The
single-passlinkage strategy batches every agreed linkage key into one PSI exchange, keeping the round-trip count constant in the key count for a high-latency channel, as a consented disclosure tradeoff against the default per-keycascade. Seedocs/EXCHANGE_REFERENCE.mdanddocs/spec/PROTOCOL.md. - A linkage key can match on several values per record: the candidates a
split_ontransform produces (a hyphenated surname and its parts) each enter that key's round on their own, and a record matching on any of them is paired at most once and takes no part in the later keys. It runs under either linkage strategy: thesingle-passreceiver resolves the pairing, and acascaderound resolves its candidate sets to the same table; a count-only (psi-c) exchange refuses a candidate set outright. Seedocs/EXCHANGE_REFERENCE.mdanddocs/spec/PROTOCOL.md. - A linkage key element can match approximate variants of its value:
generate_fuzzy_comparisonsexpands a record's value into its two-position transpositions, its single-character deletions, the adjacent years of a date, or the date read with its day and month exchanged, so two records that disagree by one such difference still match. Each element's expansion counts toward a per-key candidate width both parties derive from the agreed terms, terms declaring more than the width admits are refused before any row is read, and both consent surfaces name the elements expanded and how. A key declaringswapmatches its two elements in either order under the same mechanism. It runs under either linkage strategy, on the same terms as the candidate set above. Because the built-in default keys include a swapped key, terms drawn from them are a candidate set: a count-only exchange over them is refused rather than matched on one value per record. Seedocs/EXCHANGE_REFERENCE.mdanddocs/spec/PROTOCOL.md. - The built-in matching rules come in two choices, highest precision and broader matching, with rules that match on a name and date of birth alone marked less certain. A quick exchange,
alcove init, and the web app start with broader matching. Seedocs/EXCHANGE_REFERENCE.md. - Count-only (
psi-c) exchanges are selectable and run end to end: both parties learn only the size of the intersection, subject to the agreed terms' output entitlement. The consent surfaces state three bounds: each round also discloses the number of key values that appear exactly once in each party's file, beside the record counts the terms exchange already carries; only the receiver computes the count, so the other party's copy is that party's report rather than an enforced fact; and the count bounds what Alcove discloses, not what a partner that chooses its own input set can learn. Seedocs/EXCHANGE_REFERENCE.mdanddocs/spec/PROTOCOL.md. - A party can deduplicate its own inputs against the partner's data:
deduplicate: truelets several of that party's records link to the same partner record, so rows that turn out to name one individual are grouped, and the result file, the exchange record, and both consent surfaces carry the multiplicity. One party declaring it runs under either linkage strategy, and so does both parties declaring it, where the result's pairs resolve into entity clusters that group each party's records against the other's. The one-sided shape runs from two configurations or from an invitation, which carries only the inviting party's declaration; the both-sided shape runs from two configurations, or from an invitation whose accepting party declares its own side -- on the web accept screen, which offers that party the setting beside the invitation's own, or afterwards in its own configuration. The consent surfaces state what a deduplicating match discloses -- the partner learns how many of the deduplicating party's records group onto each of its own matched records -- and which party's records are grouped to disclose it. Every run states, once the terms are agreed on, your own setting, the one your partner presented, and the cardinality the pair resolved to; your exchange record holds all three. Seedocs/EXCHANGE_REFERENCE.mdanddocs/spec/PROTOCOL.md. - The PSI engine selects a native N-API backend when a prebuilt addon is present for the running platform, falling back to WASM; the browser always uses WASM. It is a performance accelerator with byte-identical wire output. See
docs/spec/PROTOCOL.md. - A FIPS variant of the container image is published beside the default one, at
ghcr.io/georgetown-mdi/alcove:X.Y.Z-fips: built on Amazon Linux 2023 and embedding the CMVP-validated OpenSSL FIPS provider AWS publishes for that distribution, so Alcove's Web Crypto calls dispatch into that module. It is scanned and signed by the same release workflow, and reports at every container start whether the module is serving its crypto and whether the host kernel is in FIPS mode. Supplying a host in FIPS mode is the operator's, and the PSI masking stays outside any OpenSSL module. Seedocs/RELEASES.mdanddocs/COMPLIANCE.md. - Host-side console launchers, published stamped as release assets:
Start-Alcove.ps1(Windows) andstart-alcove.sh(macOS and Linux) collect the exchange folders, run the container's ownalcove doctorchecks against them, start the web console against the release image pinned by digest, and open the browser. Plaintext, no network of their own, no persistence, no auto-update. Seedocs/RELEASES.mdand the setup page insupport/windows-network-filedrop/README.md.
Changed
- BREAKING: the product is renamed Alcove, from psilink. The config, key, record, result, and receipt file names, the
ALCOVE_*environment variables, the container image (ghcr.io/georgetown-mdi/alcove), the exchange record and receipt formats, and the web app's stored data change with it. Nothing written by a pre-rename build is read: renamepsilink.yamland.psilink.keytoalcove.yamland.alcove.keyby hand, and set up again any recurring exchange a browser held, since its stored exchanges are not carried over. Seedocs/CLI.md. - BREAKING: the published container image runs unprivileged as uid 1000 (
USER node) instead of root, for both the CLI and console roles. A bind-mounted working directory -- including one an earlier root image wrote into -- must be writable by uid 1000: run the container as your own account with--user, orchown -R 1000:1000the directory. Seedocs/DEPLOYMENT.md. - BREAKING: exchange receipts are signed with ECDSA over P-256, an algorithm the targeted FIPS 140-3 certificate approves, through the platform's Web Crypto. The signing-identity, certificate, and dual-signed-receipt formats change with it, so every certificate fingerprint changes: regenerate the identity with
alcove fingerprint --forceand re-share the new fingerprint with each partner out of band. An identity file, certificate, or receipt in the earlier format is refused rather than read. Seedocs/spec/PROTOCOL.mdanddocs/spec/EXCHANGE_RECORD.md. - BREAKING: the self-attested exchange record format moves to
alcove-exchange-record/v11, adding a requiredoutcomefield, a requiredcertificateMismatchObservedmarker stating whether the run found that the partner's certificate is not the pinned identity, and a required governancematchingblock holding both parties'deduplicatevalues and the cardinality the pair resolved to; the agreed-terms hash domain also moves toalcove-agreed-terms/v3, because the hash now covers the resolved terms -- an unsetpayload.receiveresolved to the partner's stated send set, and an unsetpayload.sendstated from the metadata columns -- rather than the terms as exchanged. A record in an earlier format is refused rather than read, with no migration. A run whose received-payload reconciliation or signed-receipt swap terminates after this party's own payload exchange keeps a self-attested record rather than leaving none. Seedocs/spec/EXCHANGE_RECORD.md. - BREAKING: the redesigned web app's home route at
/opens on the browser's list of recurring (managed) exchanges once one exists, run again without a new invitation; a first-run visitor (or a browser that cannot store exchanges) lands on the quick path instead. The full recurring-exchange list, with its designed empty state and restore-from-backup import, is always reachable at/saved. Setting up or accepting a one-off exchange is the quick path (/quick, with/exchange,/accept, and/verifybehind it), and the legacy web interface is removed. Seedocs/DESIGN.md,docs/MANAGED_EXCHANGE.md, anddocs/COMMUNICATION.md. alcove accepton an invitation naming a WebRTC coordination server, given an input file, accepts and runs the exchange in one invocation; the same command previously wrote the configuration and stopped. A scriptedaccept --consent-to-termswritten before this change now also connects and transmits -- the consent surface and the flag's help text state the widened authorization. Seedocs/CLI.md.- BREAKING:
alcove inviteandalcove acceptrequire--identity, the label a partner reads as this party's name in the durable partnership they author. Alcove no longer stands in the account name it runs as, which in the published image is the image's own, so a scripted invite or accept relying on that default exits 64 until the flag is added. Nothing else gained a requirement: the quick path still runs with no flags and no questions, and a run given no label sends none rather than a name you did not choose -- every surface shows that absence rather than inventing one. Seedocs/CLI.md. - BREAKING: the CLI resolves the signing identity like a credential -- from
signing.identity_fileor--identity-fileand nowhere else. There is no default path: Alcove no longer reads or creates one under the home directory, probes no location, and reads no fallback, so a container whose home is ephemeral can no longer mint a fresh key with a fresh fingerprint on every run.alcove fingerprintwith no path named refuses and prints nothing rather than creating the identity somewhere the operator did not choose; a certificate-modealcove exchangewith none is refused before it connects;alcove verify-receiptrefuses nothing and leaves its own certificate slot unanchored. A file minted under the old default -- created at~/.psilink/signing-identity.json-- is neither found nor moved; setsigning.identity_fileto that file to keep using it. Seedocs/CLI.mdanddocs/DEPLOYMENT.md. - BREAKING:
alcove acceptandalcove exchangerefuse to run when the input cannot satisfy every linkage key the agreed terms declare, or when a declared key's cleaning can never produce a value; both previously warned and ran on the satisfiable keys. A run that proceeded on partial coverage before this change now exits 64 until the terms are renegotiated out of band or the input covers every agreed key. The web app and the console hold every pre-launch surface to the same rule, so a partially-covered file closes the launch, the Create button, and a recurring run where it used to warn and proceed. Seedocs/CLI.mdanddocs/COMMUNICATION.md. - BREAKING:
alcove verify-receiptexits 65 (EX_DATAERR) on a definite verification failure instead of 1, so a script can tell a bad receipt from an uncaught fault, which still exits 1. Seedocs/CLI.md. - BREAKING: the dual-signed receipt format moves to
alcove-signed-receipt/v4, which holds the partner's linkage terms beside the signed content -- soalcove verify-receiptand the web "Verify a receipt" page re-derive the agreed-terms hash from the receipt and your own terms, with no partner terms document kept beside them. A receipt in an earlier format is refused rather than read, with no migration: verify a run written before this change from its exchange record, passing the partner's terms with--partner-terms. Seedocs/spec/EXCHANGE_RECORD.md. - BREAKING: the web app and the CLI request and apply application-layer encryption on every WebRTC exchange, as the CLI does on its other channels. A web app build from before this change cannot complete a WebRTC exchange with a current CLI or web app, and a recurring exchange run across such a pair is set up again from a new invitation. See
docs/spec/CHANNEL_SECURITY.md.
Security
- Recurring CLI exchanges are encrypted end-to-end on the wire with an application-layer AEAD, authenticated with a P-256 key exchange, and SFTP connections verify the server host key fail-closed. See
docs/SECURITY_DESIGN.md,docs/spec/CHANNEL_SECURITY.mdanddocs/spec/SFTP_CONNECTION.md.
[0.1.0] - 2026-05-08
Initial proof-of-concept release.
Added
- Web application: browser-based PSI over WebRTC using ephemeral invitation links.
- Built-in PeerJS peer-coordination server in the web application, served under
/api/. - SFTP transport for recurring exchanges between two parties via the CLI.
exchangeCLI command: run an exchange over an SFTP connection and predefined linkage rules.- PSI protocol implementation wrapping the OpenMined PSI WebAssembly module (
@openmined/psi.js).