Skip to content

Security: itsonu/contractpal

Security

SECURITY.md

Security

ContractPal handles people's private paperwork. If you find a way to break the guarantees below, please report it.

Reporting

Open a private security advisory on this repository. Please do not open a public issue for a vulnerability.

Include what you did, what happened, and what you expected. A proof of concept helps. You will get an acknowledgement within a few days.

What this project guarantees

These are the properties worth attacking. Each is enforced in code, and a break in any of them is a security bug rather than a feature request:

Guarantee Enforced by
An uploaded document is never persisted or logged npm run audit:retention, which fails on any filesystem write, datastore client, object-storage call, localStorage use, or document identifier inside a console call
The page cannot send a document to a third-party origin connect-src 'self' and form-action 'self' in the CSP (next.config.ts)
Upload type is decided by content, not by the client magic-byte sniffing in lib/upload.ts; filename and Content-Type are untrusted
A token cannot choose its own algorithm the algorithm is pinned on verify in lib/entitlement.ts; an alg: none token is refused, and a test asserts it
A recovery link cannot be used as a scan credential recovery tokens carry a different audience (contractpal:recovery) from entitlement tokens (contractpal:scan); asserted in both directions
The recovery endpoint cannot enumerate subscribers /api/entitlement/magic-link always answers 202 with an identical body, including when the send fails
Recovery links cannot be printed into a production log the development mailer refuses to run under NODE_ENV=production, and the registry refuses to hand it out there

Known and accepted limitations

These are deliberate, documented trade-offs rather than oversights. Reports that restate them are welcome but already known:

  • The free-scan cookie is forgeable. cp_free=1 is not signed. Clearing or forging it grants one extra document analysis. The cost of abuse is a single model call; signing it would not stop the simpler attack of just clearing it.
  • Recovery links are replayable within their 15-minute window. With no datastore there is nowhere to mark a link consumed. Mailbox access is the recovery factor by design, and the blast radius is a subscription that address already paid for. See docs/okf/decisions/adr-0009-mail-seam.md.
  • Entitlement survives to token expiry after cancellation — at most one billing period plus a two-day grace. The Stripe webhook that would allow early revocation was deliberately not built; see docs/okf/apis/stripe-webhook.md.
  • Rate limiting is per-instance and in-memory. On serverless it resets on a cold start. It blunts a script, it is not a quota.
  • script-src allows 'unsafe-inline' for Next's bootstrap. Tightening it needs a per-request nonce from middleware. 'unsafe-eval' is development-only and is proven absent from the production policy.

Out of scope

The AI provider's own handling of a document is governed by that provider's terms, not by this codebase. What each supported provider commits to is recorded in its adapter's retention field and reflected on the privacy page.

There aren't any published security advisories