Security and data protection
What personal data PIEDAP Consent holds, how it is protected, who is responsible for what, and the gaps a reviewer should know about.
For security reviewers and Data Protection Officers who are assessing PIEDAP Consent as a vendor, or before running it themselves.
Summary#
PIEDAP Consent is built to hold identifiers only as hashes. A readable copy is kept with a record only if a project asks for one, and then encrypted. Secrets are encrypted by field or stored as hashes. Projects are kept apart by the session, by the key and by database constraints. Consent evidence is sealed, signed and anchored, so a change after the fact can be detected.
The software does not secure a deployment by itself. TLS, network isolation, encryption at rest, backups and the protection of the deployment's keys belong to whoever runs it. Access inside a project is all or nothing. The audit log is not tamper-evident. Rights requests and the breach register are deleted only when a project is closed, and the audit log never is.
PIEDAP Consent holds no certification against SOC 2, ISO/IEC 27001 or any other framework. No independent penetration test has been carried out.
What this paper can and cannot tell you#
The paper describes the controls in the software as it ships. They are implemented in code. Where a control is covered by automated tests, the text says so.
It cannot give you an attestation. There is no audit report to hand to your assessor, and no test report from an outside firm. A reviewer has to judge the design from the description here and, on a self-hosted deployment, from the code.
Much of your real exposure depends on how the deployment is run, not on the software. This paper therefore separates three parties throughout: the software, the operator and you.
Who is responsible for what#
| Party | Role under the DPDP Act | Responsible for |
|---|---|---|
| You, the organisation that asks for consent | Data Fiduciary | Purposes, wording, which identifiers are recorded, how long evidence is kept, who has access to the project |
| The operator of the deployment | Data Processor, acting on your instructions | Running the software, holding the keys, TLS, the network, backups, updates, logs |
| The person who answers the notice | Data Principal | Nothing. Their rights are served through you |
| Partners you name on a purpose | Your own processors or other fiduciaries | Nothing in this product. It never sends them data |
If you run the deployment yourself, you are both the Data Fiduciary and the operator. There is then no processor for the consent records, and every operator duty in this paper is yours.
What personal data is held#
| Data | How it is stored | Why |
|---|---|---|
| The person's identifier: a random browser id, or your choice of email, phone, user id, device id or token | A keyed hash only, unless the project keeps an encrypted readable copy | To attach a decision to a person and find it again |
| Decisions per purpose and partner, and the form version | In clear, sealed | Evidence of consent |
| Country and subdivision | Short codes | To choose the law that applies |
| IP address | A salted hash. The address is not stored with the record | Provenance of the record |
| Browser string and page address | In clear, cut to 500 characters | Provenance of the record |
Extra metadata from your integration |
In clear, up to 8 KB | Whatever you send |
| Parent or guardian verification | Relationship, method, time, and a keyed hash of the reference | Evidence of verifiable consent |
| A rights request | The identifier as a keyed hash, the contact encrypted, the note and timeline in clear | To track and answer the request |
| Cookie and storage names, third-party hosts | In clear, without values | The cookie list |
| How consent was taken in a branch, by phone or on paper | Channel, method, your evidence reference and a document SHA-256, sealed. Staff and site references as keyed hashes | Evidence of how consent was obtained |
| A notice campaign's people | Identifiers as keyed hashes only. Your reference encrypted. Answers in clear, sealed daily | Evidence that a notice was sent and answered |
| Processor tasks | Partner, action, purposes and dates, with no identifier | Accountability for passing on withdrawals and erasures |
| Actions by your team | Audit log: user id, email, time, IP address, browser, action, and reads of personal data | Accountability |
| Request log | One line per request, with the client's IP address | Operation |
The product does not collect some things at all. The runtime sends no cookie values, form inputs or page content. Location stops at country and, where needed, subdivision. There is no contact list, because the product sends no email or SMS. Your team's accounts and sessions belong to the PIEDAP IAM service, not to this product.
How identifiers are protected#
An identifier is stored as an HMAC-SHA256 under a pepper. The pepper is a secret of the deployment, held outside the database. The hashes in a copy of the database alone cannot be matched against a list of known identifiers.
A keyed hash is not anonymous data. Whoever holds the pepper and a candidate identifier can test it. The protection is against a database leak, not against the operator.
You still need to know whom a record is about. Each project has a fingerprint key of its own. The subject fingerprint is a second HMAC of the same identifier under that key. You recompute it from your own identifier, so the service does not need to hold the identifier in readable form.
There are four places where a readable identifier can exist:
- The encrypted copy, when Store a reversible subject identifier is on. Webhooks then include the identifier, and the delivery log holds it for 30 days.
- A rights request filed on the hosted form. The identifier the person typed, and the reply address, go to you by webhook. They sit in the delivery log for 30 days.
- Whatever your integration puts in
metadata. - The reference you give each person in a notice campaign, encrypted, when you choose to send one. It also appears in
campaign.withdrawnwebhooks.
You control all four. A person search in Records sends the identifier in the request body, which is not logged.
How secrets are held#
The deployment holds its secrets as environment variables, outside the database. The most important are the encryption key, the subject hash pepper, the evidence chain keys and the receipt signing key.
Inside the database, sensitive values are protected by field:
- Readable identifiers, request contacts, identity secrets, webhook signing secrets and fingerprint keys are encrypted with AES-256-GCM.
- Secret keys are stored only as a SHA-512 hash salted with a deployment secret. A lost key cannot be recovered.
- Email confirmation tokens are stored hashed.
Two of the deployment secrets can never be changed. Every stored identifier hash depends on the pepper. There is no re-encryption tool for the encryption key. A reviewer should ask how these two are generated, stored and backed up, because losing either makes records unreadable or unfindable.
When no dedicated chain key or signing key is set, both are derived from another deployment secret, in the end the encryption key. A production deployment should set dedicated values and keep them in a secret manager. The API refuses to start in production without an encryption key.
How access is controlled#
Your team. The console has no accounts of its own: the PIEDAP IAM service signs people in. The API verifies the session cookie's signature and loads the session from Redis. It takes the organisation, account and project from that session, never from request parameters. State-changing requests need a header that a cross-site form cannot send.
Your sites and servers. A publishable key is public. It is bound to a list of allowed origins, and a live key answers only those. A secret key is sent from your server, and calls on a person as a whole need a scope you grant per key. A secret key sent from a browser is refused, and the console marks it Exposed.
A consent check needs its own scope, consent:check. Each identifier checked counts against the key's budget (600 a minute unless you change it), which limits how fast a copied key can test identifiers. Give the scope to a key used only for checks, and revoke any key you suspect was copied.
A notice campaign link lets whoever holds it answer for one person. Link tokens are stored as HMACs. An encrypted copy is kept for 7 days so the links can be downloaded, then cleared.
The operator. New keys are test keys. A key becomes live only when the operator approves it, through endpoints that take a separate platform key.
The database. Foreign keys include the project id. A row cannot point at another project's data, even if a query forgot to filter.
Roles. Every console route checks the permission it needs against the person's role: owner, privacy officer, grievance officer, content editor, developer or auditor. Until an owner turns roles on, everyone who can open the project has every permission. Erasure, exports and key reveals can also wait for a second person. The audit log shows who did what.
How evidence is protected#
Each change to a consent record is sealed with HMAC-SHA256 and linked to the state before it. Receipts and daily anchors are signed with Ed25519. Each day's events are combined into a Merkle root that is sent to your webhooks. Chain replay, signatures and inclusion proofs are covered by unit tests, including tampering cases.
The effect is that an edit made in the database, outside the product, shows up when the record is checked. A seal fails, or the chain is reported as not keyed. Whoever holds both the chain key and the signing key can still create new, validly sealed records. Anchors that you keep outside the deployment limit that to days not yet anchored. Proving consent sets out the argument in full.
Network, input and outbound calls#
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. Both must sit on a private network.
Input. Request bodies are validated against a schema, and unknown properties are dropped. Markup is stripped from plain-text fields. The console, the banner and the hosted pages write stored values as text, not as HTML.
Outbound. The API calls addresses that customers type in: webhook endpoints, scan targets and custom translation endpoints. Each is resolved before use and refused when it points at a private, loopback or link-local network. The cloud metadata range is always refused. For webhooks and translation endpoints, the address is checked again at connection time, and redirects are not followed.
Private networks. On a deployment you run, the operator can list networks in PRIVATE_NETWORK_ALLOWLIST. A webhook endpoint reaches an address there only when its own Private network switch is on; translation calls may reach the list without a switch. The list is empty by default, a range wider than /8 is refused, and the metadata range is never reachable.
Rate limits. Requests are limited per client address, per key and per person call. Counters are shared through Redis when it is connected. The client address is read from X-Forwarded-For only when the connecting proxy is trusted.
In the browser. Hosted pages are served with a content security policy of default-src 'none' and run no scripts. A pinned runtime build can be loaded with Subresource Integrity. The banner renders inside a shadow root, unless you place it inline.
Logging and accountability#
There are two records of activity, and they differ in strength.
The request log is one JSON line per request, on standard output. It holds the method, the address requested, the status, the time taken, the client's IP address and a 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. The operator decides where the log is kept and for how long.
The audit log records every state-changing console request: who, when, from which address, on what, and the result. It records reads of personal data: opening a record or a rights request, a person search, a receipt and a download of the log. It records property exports, key reveals, and each call on a person made with a secret key, with the first 12 characters of the identifier's keyed hash.
The audit log is append-only in the database: a trigger refuses changes and deletions, though the table's owner can still turn it off. Each entry is sealed with HMAC-SHA256 to the one before it, so a changed, removed or inserted entry shows when the log is verified. It is not anchored daily as consent evidence is. The reply waits until its entry is written. A reply whose entry could not be stored is marked X-Audit-Log: not-stored, counted and kept on standard output.
Each entry is also one JSON line on standard output. A SIEM can pull the log with a key that holds audit:read, and you can download it. Your SIEM copy is the independent record.
Retention and deletion#
| Data | How long it is kept |
|---|---|
| Consent records and their events | While the consent is in force, then for the project's Keep evidence for period: 1, 3, 7 or 10 years, 7 by default |
| Identifying details on an erased record | Removed at once. For a record under the DPDP Act erased within a year of its last processing, removed when the year is up |
| Test records | 30 days after their last change |
| Webhook deliveries and settled outbox events | 30 days |
| Idempotency records | 24 hours |
| Rights requests, the breach register, daily anchors, notice campaigns | Until the project is closed |
| The audit log | Not deleted by the product; the database refuses deletions |
| Processor tasks | As long as their record |
| Request log | Set by the operator |
| Everything else in a closed project | Deleted 30 days after an owner schedules the closure, or after the operator's PROJECT_DELETION_DAYS. Legal holds postpone it |
| Backups | Outside the product. Backups keep deleted data until they expire |
Evidence past its period is deleted only when the operator has turned retention enforcement on. Otherwise records stay. The job never deletes a record less than a calendar year after its last processing.
A rights request keeps the contact the person gave, encrypted, for as long as its row stays. If your policy sets a limit for requests or the breach register, the operator removes old rows in the database. Old audit entries are archived and removed by the table's owner; see Download the audit log. At the end of a contract an owner exports the data and closes the project, which can be cancelled until its date; see Close a project.
Outside services and data location#
The software needs no outside service to record consent. It can make these calls:
| Call | What is sent |
|---|---|
| Your webhook endpoints | The event. For consent events, the identifier type and fingerprint, and the identifier only if the project stores a readable copy |
| The translation service you chose | The form or purpose wording. No personal data about visitors |
| The IAB vendor list | Nothing. It is a download |
| The sites you ask it to scan | Ordinary page requests from a headless browser |
| The PIEDAP IAM service | An API token, to be checked |
Location is worked out from a database inside the API, or from headers set by the operator's CDN. No outside geolocation service is called.
Everything the product stores is in one PostgreSQL database. Sessions and rate-limit counters are in Redis.
The country the data sits in is where the deployment runs. The operator states it in DEPLOYMENT_REGION, DEPLOYMENT_COUNTRY and DEPLOYMENT_PROVIDER. Every project sees it in Settings, and the DPDP Act check notes it against section 16. Whoever hosts the deployment is the operator's sub-processor.
Gaps a reviewer should weigh#
These are stated in the documentation, and listed here together.
| Gap | What it means for you |
|---|---|
| No certification and no independent penetration test | You assess the design yourself |
| No TLS setting for the database and Redis connections | The operator must keep both on a private network |
| The audit log is not anchored | Each entry is sealed, but there is no daily anchor. Stream it to your SIEM for an independent copy |
| Retention enforcement is off by default | Ask the operator whether it is on |
| One signing key at a time | Save the public key set. A changed key breaks the online check of older receipts |
| The pepper and the encryption key cannot be rotated | Their storage and backup matter more than usual |
/api/docs is served without sign-in |
It lists endpoints and holds no data. The operator can block the path |
| No pipeline for dependency or image scanning in the repository | Watching advisories is the operator's |
| Private networks can be opened for webhooks | Only networks the operator lists, and only for endpoints with Private network on |
Questions for the operator#
- Are the region and country in Settings where the deployment runs? Where are the backups, and is any data moved outside India?
- Is the database encrypted at rest, and is a restore tested on a schedule?
- Are the pepper, the chain keys and the signing key dedicated values, held in a secret manager?
- Who can reach the database, Redis, the API port and the metrics endpoint?
- Who in IAM can open our project?
- Is retention enforcement on, and how long are the request logs kept?
- What is
PROJECT_DELETION_DAYS, and how long do backups keep a closed project's data? - How do you learn about security advisories for the dependencies, and how fast do you apply updates?
- Which sub-processors host the deployment?
Record the answers in your contract. Data processing gives you the facts to put in it.