Skip to content

Security: hseshadr/privacy-core

Security

SECURITY.md

Security Policy

Reporting 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/API, and the impact you think it has. A proof-of-concept helps but isn't required.

What to expect: an acknowledgement within a few days, and an honest assessment of severity and fix timeline. This is a solo-maintained OSS reference project, not a funded product — there's no bug-bounty payout, but credit in the fix/release notes is offered unless you'd 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.

Security model (what's in scope)

privacy-core's whole design is raw private text never crosses the wire. The parts worth probing:

  • The type-enforced Egress Guard. Provider adapters accept only the branded RedactedPayload (src/egress.ts), whose sole legitimate constructor is redactForEgress (src/redact.ts). A raw string is not assignable — handing raw text to a provider is a compile error, not a runtime check. The brand factory (mintPendingRedaction) is deliberately not exported from the public barrel.
  • The reversible vault. Vault (src/vault.ts) maps typed placeholders ([CARD_1], [NAME_2]) back to real values in memory only (v0, by design — it clears on reload). Rehydration runs on-device after the reply; real values never leave the machine. Under a bound vault, an unresolvable placeholder fails closed (UnresolvedPlaceholderError) rather than leaving a lookalike token in place.
  • The audited escape hatch. unsafeBypass is the only way to hand raw text to a provider, and it must emit an AuditEntry. A bypass that leaves no audit trail is a vulnerability.
  • The e2e wire proof. A Playwright test intercepts the outbound request and asserts only placeholders cross the wire; that guarantee is the product.
  • No API key in the browser. The library is env-agnostic — a host passes its own key in. The bundled Vite demo deliberately keeps OPENROUTER_API_KEY server-side: it is read only by a same-origin dev proxy (vite.config.ts) and injected into the outbound request, never VITE_-prefixed and so never embedded in the client bundle. Shipping a real key to the browser would be a bug; report it as one.

In-scope reports include: any way to construct or forge a RedactedPayload outside redactForEgress; any path that puts raw (un-redacted) text on the wire; a bypass that skips the audit sink; or vault contents escaping the local device.

Known, documented limits (not vulnerabilities): detection is deterministic (regex + checksums + dictionaries) — 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 (see the roadmap in docs/ARCHITECTURE.md).

There aren't any published security advisories