Zero-PII Compliance Guide
How the zero-PII data model maps to GDPR, CCPA and CPRA, SEC Regulation S-ID and SOC 2 readiness evidence, with the mechanism behind each row.
Overview
In identity-signal processing, low-entropy personal identifiers are keyed with HMAC-SHA-256 under a server-held pepper before storage. Values an authorized operation needs again are encrypted. Account authentication details, administrative configuration and submitted account content are separate categories; zero-PII signal processing does not mean the entire platform database contains no plaintext personal data.
This guide maps those mechanisms onto GDPR, CCPA and CPRA, SEC Regulation S-ID and SOC 2 readiness evidence. It is an engineering description, not a legal opinion or certification.
| Property | Implementation and scope |
|---|---|
| Current signal identifiers | HMAC-SHA-256 under a server-held pepper through _shared/identifier-hash.ts; missing keying material fails closed. |
| Reversible operational values | Encrypted under the platform encryption scheme, with access checked by the calling path. decryptPii records a declared purpose; not every encrypted subsystem uses that helper. |
| Application audit network fields | Keyed IP and User-Agent digests, cleared after 90 days. Managed authentication/session records are separate account data. |
| Data subject rights | shield-dsar exports and tombstones the subject-digest records it covers and reports not_covered; hosted relying-party deletion and account-data requests have separate paths. |
| Tenant isolation | Row-level security for client access plus explicit tenant authorization and query scoping in service-role handlers. |
| Transport | TLS between clients, the public edge and the function origin. |
Historical qualification. Earlier rows can retain older digest schemes; a current keyed write does not migrate every historical record. Version 1 denotes legacy unkeyed digests, version 2 keyed digests, and version 3 the case-preserving subject scheme. Compatibility reads preserve historical correlation. The non-enumerability claim concerns keyed digests and depends on the pepper remaining secret. A tenant-supplied subject_ref remains an opaque clear-text correlation handle and must not contain a personal identifier.
Data we store
Personal identifiers in identity signals and account content are different data categories. The public Data handling inventory lists each category's storage form, retention and Privacy Policy sections, without exposing internal relation names. This guide describes the tenant-facing inputs behind that inventory.
| Tenant input | What is kept |
|---|---|
phone | Normalized E.164, then HMAC-SHA-256 under the server-held pepper before persistence. A requested carrier lookup can transmit the number transiently to Twilio, Vonage or Sinch; the normalized result is retained under its keyed digest. |
phone_hash | A supplied 64-character hex digest is keyed again as its own identifier kind before storage. The callback delivered to the tenant can carry the digest as supplied; the durable callback copy uses the keyed form and protected retry material is encrypted. This mode does not correlate with the raw-phone mode for the same number. |
event_type or signal_type, risk_score, source_id | Validated signal type, bounded score and optional source UUID, with selected processing results, times and correlation metadata. These labels and identifiers must not carry personal data. |
subject_ref | The tenant's own opaque correlation handle, retained as supplied for callbacks and opted-in security-event publication. Shape checks refuse phone/email-like values; they do not establish that arbitrary text is anonymous. The tenant must use an opaque value without personal information. |
credential_sha1 | The submitted credential digest is used for the breach-range check, not retained as a credential value on the event or callback metadata. The outcome and breach count can be kept. Do not send a plaintext credential. |
| Additional ingest fields | Known identifier fields are removed recursively from callback metadata. Remaining integration metadata can reach the tenant's receiver and durable protected retry path; field filtering is not proof that every arbitrary string is nonpersonal. Do not put personal values in custom prose or unrecognized fields. |
| Hosted passkey user handle and response | A tenant-and-RP-scoped keyed user digest, random user id, credential id, public key, counter and authenticator metadata. No biometric sample or authenticator private key. Challenges are single use and expire after five minutes. |
| Account and administration content | Authentication contact details and display metadata are account data. SCIM display labels are encrypted per organization membership. Organization labels, provider configuration, support text and uploaded documents can contain the content an administrator supplies; they are not described as anonymous signal data. |
Retention is per category. Signal events have a 90-day purge; application audit IP and user-agent digests are cleared after 90 days. Other evidence, delivery history, credentials and account records have different lifecycles. Expiry can disable use without deleting the row. The public inventory and Privacy Policy explain those distinctions; neither says that every record has an automatic 90-day deletion.
Legacy state remains explicit. Old identifier representations and restricted rollback copies are not retroactively changed by a new keyed write. Existing plaintext profile names can remain until the service-key-only operator backfill preserves auth metadata and encrypted membership labels and clears the profile copy. A clean build or migration replay is not evidence that this manual hosted cleanup ran.
GDPR mapping
Regulation: General Data Protection Regulation (EU) 2016/679.
| Article | Requirement | Implementation |
|---|---|---|
| Art. 5(1)(c), minimisation | Collect only what is necessary | Signal identifiers are keyed before storage. Account authentication, administrative content and historical exceptions remain in the assessment scope; see What we store and the public inventory. |
| Art. 5(1)(e), storage limitation | Keep data no longer than necessary | Enforced where the code enforces it: cached proofs expire on expires_at, BLAST sessions are torn down at expiry by cleanup_expired_records, hosted passkey challenges are single use with a five-minute TTL, and audit-log IP and user-agent digests are nulled after 90 days by anonymize_old_audit_entries (daily, 03:00 UTC). Signal events are deleted 90 days after they are written by shield-retention-purge (daily, 07:55 UTC); other rows are kept until an erasure request. |
| Art. 5(1)(f), integrity and confidentiality | Appropriate technical measures | AES-256-GCM at rest, TLS in transit, keyed digests for identifiers, row-level security per tenant, hybrid post-quantum signatures on artifacts that leave the platform. |
| Art. 6(1)(f), legitimate interest | A lawful basis for processing | Stored signal correlation and scoring operate on keyed digests and metadata. Raw inputs can be processed transiently, including an explicitly requested carrier lookup; the Privacy Policy states the processing purposes and lawful bases. |
| Art. 15, right of access | Export everything held about a subject | shield-dsar action access exports ten tables matching the subject's keyed digest: seven user_hash tables plus shield_cross_references, shield_events and pii_decryption_log. The response carries per-table counts taken from the database and a not_covered list. See DSAR Workflows. |
| Art. 17, right to erasure | Delete everything held about a subject | shield-dsar action erasure tombstones: identifier columns nulled, encrypted columns overwritten with the literal SHREDDED, webhook request and response bodies nulled, records deactivated, and pii_decryption_log.user_hash unlinked while the audit row survives. No encryption key is destroyed. |
| Art. 25, data protection by design | Privacy built into the data model | Identifiers go through hashIdentifier() before any write; operational PII goes through the vault's encrypt() or encryptAndHash(). Both fail closed when their key is absent. |
| Art. 30, records of processing | Maintain processing records | pii_decryption_log records successful reads through the vault helper with purpose, function, field and timestamp. Other encrypted subsystems have their own checked paths. shield_audit_log records administrative and security-relevant actions. |
| Art. 32, security of processing | Encryption, pseudonymisation, testing | AES-256-GCM (NIST SP 800-38D), keyed HMAC-SHA-256 pseudonymisation, ML-DSA plus ECDSA signing, tenant key rotation with a 24-hour grace window. |
| Art. 33 and 34, breach notification | Notify within 72 hours | Keying and encryption reduce exposure for the fields they protect. Account content, legacy data and possible key exposure still require assessment; these controls do not waive breach-notification obligations. |
| Art. 35, DPIA | Impact assessment | Assess signal metadata, keyed identifiers, encrypted operational values, plaintext account and administrative content, legacy representations and the configured recipients. The public data inventory names those categories. |
Where the export stops. Four tables cannot be matched to a subject digest at all, because they key on a different namespace: shield_rp_users, shield_rp_credentials, shield_rp_challenges and shield_attestation_state. Both DSAR actions name them in not_covered with the tenant's row count and the remedy, rather than omitting them silently. Hosted relying-party data is erased through shield-passkey-rp action delete_user.
CCPA and CPRA mapping
Regulation: California Consumer Privacy Act (Cal. Civ. Code 1798.100 to 1798.199.100), as amended by CPRA.
| Section | Requirement | Implementation |
|---|---|---|
| 1798.100, right to know | Disclose the categories and the specific pieces collected | shield-dsar action access returns tables, record_counts, total_records and the row data itself, plus not_covered for what this path cannot reach. |
| 1798.105, right to delete | Delete on request | shield-dsar action erasure returns affected per table, shredded_tables and shredded_field_count, each count read back from the database after the statement ran. |
| 1798.110, categories and purposes | Disclose sources and business purpose | Reads through decryptPii carry a purpose and function into pii_decryption_log. The public inventory and Privacy Policy disclose broader categories, sources and purposes; this helper is not the whole processing record. |
| 1798.121, limit sharing | Limit the sharing of personal information | Signal correlation uses keyed digests. Requested carrier lookups transmit a number transiently to the configured provider; account delivery and tenant-directed callbacks or security-event publication have their own recipients and purposes. See the Privacy Policy and data inventory. |
| 1798.130, verification | Verify the requester | DSAR requests require a Supabase user JWT and is_tenant_admin on the target tenant, and are deduplicated against a completed request for the same subject and action within the last hour. |
| 1798.150, private right of action | Statutory damages for unencrypted PII | Operational PII is AES-256-GCM encrypted at rest with the key held outside the database. |
CPRA additions.
| Section | Requirement | Implementation |
|---|---|---|
| 1798.100(d), retention | Retain only as long as the disclosed purpose needs | Stated plainly: the platform enforces the 90-day audit-log scrub, the expiry paths above and the DSAR pipeline. The ttl_seconds on a purpose policy is a recorded expectation and is not enforced at decryption time. |
| 1798.185(a)(15), cybersecurity audits | Risk assessment for high-risk processing | pii_decryption_log and shield_audit_log provide the access record, shield-compliance-report aggregates decryptions by purpose and function over a period, and Settings, PII Migration Tooling shows how much plaintext is left per column. |
SEC Regulation S-ID
Regulation: SEC Regulation S-ID (17 CFR 248.201 and 248.202), Identity Theft Red Flags.
Reg S-ID applies to covered financial institutions and creditors, which PasskeyBridge is not. This section maps platform mechanisms onto the program elements such an institution has to document, so a customer in scope can cite what the platform does. It is not legal advice.
| Red flag category | Requirement | Platform mechanism |
|---|---|---|
| Suspicious documents | Detect altered or forged identification | shield-vc-verify validates the credential signature and checks revocation status. See StatusList2021 Revocation. |
| Suspicious personal information | Detect inconsistent identity data | shield-cross-reference binds a SIM signal digest to a credential subject digest and scores the match, all on keyed digests. |
| Unusual account activity | Detect patterns indicating identity theft | Signal ingest classifies each event as hard, soft or unknown. The nine hard types are sim_swap, sim_swap_detected, port_out, number_port, number_porting, device_compromise, ss7_intercept, account_takeover and scope_poisoning. |
| Alerts from service providers | Act on warnings from others | The Universal Ingest API, Okta Event Hooks, carrier webhooks and the SSE and CAEP receiver all accept third-party signals. |
| Program element | Platform mechanism |
|---|---|
| Identify relevant red flags | The signal classifier is the list a program can cite: hard signals map to takeover and porting red flags, soft signals to velocity, geography, device and credential-leak indicators. |
| Detect red flags | Signal ingest with in-request scoring, returning the playbook result and a correlation id on the same call. |
| Respond to red flags | Playbook actions run in parallel per signal, and a hard signal also revokes agent delegates and A2A negotiations inline. The four-branch cascade across every identity subsystem is shield-cascade, an admin action on Enterprise. |
| Update the program | Alert rules and thresholds are editable in the Observability tab, and the Intelligence tab holds the review queue for items flagged for a person to look at. |
SOC 2 readiness evidence
Status: readiness. Evidence is collected and under review. No CPA firm is engaged, so nothing on this page is a certification, an attestation or an audit opinion. What follows is the evidence a reviewer can pull, mapped to the AICPA trust service criteria.
| Criteria | Controls | Evidence in the platform |
|---|---|---|
| Security (CC6) | Logical access control, encryption | Row-level security per tenant, AES-256-GCM at rest, TLS in transit, API keys looked up by digest with scope and tenant binding, two-layer rate limiting |
| Availability (A1) | Monitoring and incident response | shield-alerting threshold rules with Slack and PagerDuty dispatch, shield_alert_history with acknowledgement tracking, cron heartbeats per scheduled function |
| Processing integrity (PI1) | Complete and accurate processing | shield_audit_log for every mutation, hash-chained audit and event ledgers with a signed checkpoint every five minutes, HMAC webhook signatures, nonce-based replay protection |
| Confidentiality (C1) | Protect confidential information | Purpose-bound decryption with an audit row per access, keyed identifier digests, tombstoning erasure |
| Privacy (P1 to P8) | Notice, consent, retention, disclosure | Purpose codes recorded per access, purpose policies recorded per tenant, the DSAR handler for access and erasure |
| Evidence source | What it holds | Retention |
|---|---|---|
shield_audit_log | Administrative and security-relevant actions, with actor, result and correlation id | Kept; the actor IP and user-agent values are cleared after 90 days |
pii_decryption_log | Successful reads through the purpose-coded vault helper | Kept |
shield_dsar_requests | Every access and erasure request with counts and notes | Kept |
shield_alert_history | Alert firings and acknowledgements | Kept; no retention job exists for this table |
shield_scim_provisioning_log | SCIM lifecycle operations | Kept |
shield_entropy_pool and shield_chain_checkpoints | Reseed provenance and the signed chain heads | Kept |
Key rotation history is in shield_audit_log under the actions tenant_key.provision, tenant_key.rotate and rotation.tenant_key_rotated, each carrying the old and new key ids.
Compliance architecture summary
The protection depends on the category and the compromised capability. The Data handling inventory makes those boundaries visible.
A database copy. Protected signal fields yield keyed digests, encrypted values and public cryptographic material. The same database can also contain plaintext account and administrative content, opaque correlation handles and historical representations. Do not describe a whole-database compromise as exposing only anonymous data. The pepper and encryption credentials must remain separately protected.
Database access plus key access. An actor who can read ciphertext and obtain its decryption material can recover that value. The vault audit is written by the calling helper, not a database trigger: decryption outside that helper leaves no vault access row. SCIM label reads and other encrypted subsystems use their own authorization paths, so this log is not a complete census of every plaintext read.
A compromised function. Its effective database and secret privileges determine what it can read. The vault helper records a declared purpose but does not enforce its policy table or throttle decryptions. Its audit insert is best effort. Purpose-policy limits therefore support review and do not prevent a compromised caller from reading at volume.
Use the category-specific storage and retention descriptions when assessing these cases. Neither a passing schema inventory nor a clean privacy canary proves that every free-form value is nonpersonal or that a hosted legacy cleanup has completed.