Consent Docs

Security model

The security controls in the software as it ships, what they protect, and what the operator of a deployment must do.

Concept4 min readFor architects, security reviewers and auditors

These are the security controls implemented in the software as it ships. Controls that depend on how a deployment is run are the operator's, and are listed at the end.

Certification and testing

PIEDAP Consent holds no certification against SOC 2, ISO/IEC 27001 or any other framework. No independent penetration test has been carried out.

Trust boundaries#

The API has two surfaces with separate credentials.

Surface Used by Credential Protection
Console API, /api Your team IAM session cookie or IAM API token Scoped to one project. Credentialed requests only from known origins
SDK surface, /sdk/v1 Your sites, apps and servers Publishable key or secret key Never uses cookies. Origin list for a publishable key, scopes for a secret key

A secret key that reaches a browser is refused. A request with a secret key and an Origin or Sec-Fetch-Site header gets 403 SECRET_KEY_IN_BROWSER, and the console marks the key Exposed.

The origin list stops other websites from using a copied publishable key. It is not authentication against non-browser clients, which can send any header. Server integrations use the secret key.

Encryption and hashing#

In transit. The API speaks plain HTTP inside the deployment. The operator's load balancer or reverse proxy ends TLS. The API has no setting of its own for TLS on its PostgreSQL and Redis connections, so both must sit on a private network.

At rest, by field. These values are encrypted with AES-256-GCM before they reach the database:

  • the readable copy of an identifier, only when a project turns on Store a reversible subject identifier
  • the contact a person gives with a rights request
  • identity secrets and webhook signing secrets
  • a project's subject fingerprint key, and its own key for a translation service

At rest, as a whole. Disk or database encryption is the operator's.

Identifiers are stored as keyed hashes, and secret keys as salted SHA-512 hashes. See identifier hashing.

Input and outbound requests#

  • Request bodies are validated against a schema, and unknown properties are dropped.
  • Markup is stripped from plain-text fields on input. The console, the banner and the hosted pages write stored values as text, not as HTML.
  • Consent writes, and server calls that withdraw, erase or link, accept an Idempotency-Key. The same key with a different body is refused.
  • Webhook addresses, scan targets and custom translation endpoints are resolved before use. They are refused when they point at private, loopback or link-local networks. The cloud metadata range is always refused.
  • Requests are rate limited per client address, per key and per person call. The client address is read from X-Forwarded-For only when the connecting proxy is trusted.
Private networks the operator allows

The operator can list networks in PRIVATE_NETWORK_ALLOWLIST, empty by default. A webhook reaches a listed address only when its endpoint's Private network switch is on; translation calls may reach the list. The metadata range is never reachable.

Browser-side protections#

  • The API sets security headers with Helmet. The console may be framed only by itself and PIEDAP domains.
  • Hosted pages are served with a content security policy of default-src 'none'. It allows only inline styles and same-origin form posts, so the pages run no scripts. Any site may show them in a frame.
  • A pinned runtime build can be loaded with Subresource Integrity. Its hash is published in /sdk/v1/runtime/manifest.json.
  • The banner renders inside a shadow root, except when you place it inline in an element of your own page.

Logging and the audit log#

Each request writes one JSON line: the method, the address requested, the status, the time taken, the client's IP address and the request id. Request bodies are not logged. The values of token, key, identityProof, secret, subjectId and search parameters, and notice link tokens, are replaced before writing. A person search in Records sends the identifier in the request body, which is not logged.

The audit log records every state-changing console request, each property export and key reveal, and each read of personal data. It records each call on a person made with a secret key, with the first 12 characters of the identifier's keyed hash. Each entry is also a JSON line on standard output.

The audit log is sealed, not anchored

The audit log is append-only in the database. Each entry is sealed with HMAC-SHA256, under the evidence chain key, over its contents and the seal before it. Recomputing the chain shows a changed, removed or inserted entry. The chain is not anchored.

A console change waits for its entry. A failed write is counted in audit_log_write_failures_total and stays on standard output.

What the operator is responsible for#

  • TLS, network policy and firewalling. The database, Redis, the API port and the metrics endpoint should not be reachable from the internet.
  • Encryption of the database at rest, backups and tested restores.
  • Keeping the secrets in a secret manager, and never changing the ones that must not change.
  • Deciding who in IAM can open each project, and approving live keys.
  • Applying updates and watching security advisories. The repository has no pipeline for dependency or image scanning.
  • Deciding who can read /api/docs, which lists endpoints without sign-in and holds no data.
  • Removing country headers such as X-Country-Code from client requests at the proxy, unless the CDN sets them.

You, as the customer, keep your own copy of the evidence.anchored deliveries.

↑↓ to move, Enter to open