Security

A narrow promise, held at the wire.

The useful security review is not “is it private?” but “what exactly does the guard stop, and what is still the integrator’s job?” This page is specific about both, including the parts the library cannot fix.

This page is sourced from SECURITY.md and docs/ARCHITECTURE.md. If this page and the repository disagree, the repository wins.

The promise

The whole design is stated plainly in SECURITY.md: raw private text never crosses the wire. Detection, the vault and rehydration run on the device; only an approved, labelled payload is sendable. The documented limit sits on top of that promise: values outside the fixed ruleset are not detected, so the review step is what catches the rest.

The egress guard

This is the load-bearing boundary, and it has two independent halves:

An unapproved payload is refused, not warned about. A redaction with no detections is still not sendable; it passes through approve() like any other. The audit sink is required, so an approval nobody can observe is not an approval this boundary grants.

The one exception is explicit by name: unsafeBypass(raw, reason, audit) always writes an audit entry recording the human-supplied reason. A bypass that leaves no audit trail is a vulnerability, not a feature.

What the tests prove

These are the repository’s own claims, each tied to a test you can run with pnpm gate:

ClaimBacked by
Only labels cross the networkpnpm test:e2e drives the browser demo in real Chromium, intercepts the outbound request and fails if any real value is in it.
Every format in the recognized table is redacted at the wiretest/detector-completeness.test.ts drives each documented format through the real send path.
Nothing that v0.2.2 redacted stops being redactedtest/v022-recall-floor.test.ts carries a frozen copy of the v0.2.2 recognizers.
A raw string cannot reach a providertest/brand-compile-proof.test.ts and pnpm build.
Hostile input stays fast: 512 KiB of card-, IBAN- or email-shaped text in under 1.5 seconds (100–250 ms measured on a CI-class Linux container, Node 22)test/detection-regressions.test.ts, timed outside coverage instrumentation.
Every source line and branch is exercisedVitest coverage thresholds pinned at 100% in pnpm gate.

What the tests do not prove is below. A green suite is not a claim that the library catches everything, anonymises text, or survives a compromised host.

Boundaries

Over-claiming privacy is worse than claiming none. These are the limits, stated as boundaries rather than caveats:

Not providedWhy
Detection beyond the rulesetThe detector is regex, checksums and two small dictionaries. It misses oddly formatted account numbers, unusual names, and kinds of private data nobody wrote a rule for. The built-in name list is three demo names; general name detection is not shipped.
AnonymityEven with names and numbers stripped, the shape of the text can identify a person. “$482.10, insurance, early January” can point at one person with no identifier left in it. This reduces direct identifier leakage; it is not anonymisation.
Protection from a compromised deviceA malicious browser extension, a cross-site scripting bug or a compromised dependency can read the in-memory vault. There is no secure element involved.
Hardware-backed browser key custodySigning keys held in a browser are protected by the browser’s same-origin rules and nothing stronger. Anything that can run code on your origin can reach them.
PersistenceThe vault is ordinary process or tab memory and clears on reload. An encrypted, stored vault is planned, not shipped.
Control over the providerWhat the AI company does with the labelled text it receives is outside this library.
Host attestation from receiptsA signature proves a record has not changed since it was signed. It does not prove the signing host was uncompromised, and it is not a replay-prevention token.

If any of these limits is unacceptable for what you are doing, this is the wrong tool. The repository says so directly rather than working around it.

Receipts: evidence, not host attestation

Receipts are opt-in. Hand guardedProvider a governance context — provider name, signing seed and an onReceipt sink — and every decision it makes is signed with Ed25519 through @gainratio/avow. Refusals are recorded too, so a blocked send cannot simply vanish. A receipt contains a SHA-256 hash of the redacted text, not the text itself.

There is a runnable allow-and-deny receipts example if you want the shape without the theory.

What counts as a security report

The parts worth probing are the boundary itself. In-scope reports include:

Known, documented limits are not vulnerabilities: detection is deterministic (regex + checksums + dictionaries), so recall is bounded and the human-visible preview is the backstop for anything the ruleset misses. The contextual NER tier that would widen recall is deliberately deferred.

Report a vulnerability

Please report security issues privately — do not open a public issue.

Useful things to include: what you found, how to reproduce it, the affected file or API, and the impact you think it has. A proof of concept helps but is not required.

What to expect: an acknowledgement within a few days, and an honest assessment of severity and fix timeline. This is a solo-maintained open-source reference project, not a funded product. There is no bug-bounty payout, but credit in the fix or release notes is offered unless you would rather stay anonymous. Please give a reasonable window to ship a fix before any public disclosure.

Supported versions

Fixes land on the latest release on main. Older tagged releases are not patched — upgrade to the current release. 0.3.2 is the current release; 0.3.1 was the first under @gainratio/privacy-core, and 0.3.0 and older shipped as @edgeproc/privacy-core.

Supply chain

Releases are published from CI with npm provenance — an SLSA build attestation linking the tarball to this repository’s workflow. You can check the attestation for the current release:

npm view @gainratio/privacy-core@0.3.2 dist.attestations

And in a project that installed it:

npm audit signatures

The only runtime dependency is @gainratio/avow, used to sign the optional receipts. The library itself reads no environment variables and ships no server component.

Read next