PII Protection
The wrapper pseudonymises PII before any event leaves your runtime. Pseudonyms are derived from HMAC-SHA256 with a customer-held secret, so the same value always produces the same token. An encrypted token map is attached to each event — only the holder of your X25519 private key can reverse it. Wytness is zero-knowledge by construction.
How it works
- Structured PII (emails, phones, SSN, AU TFN, credit cards) is detected by regex and replaced with typed HMAC tokens —
EMAIL_3c866e6f,PHONE_9a3c5f78, etc. - Declared fields listed in
pii_fields/piiFieldsare pseudonymised regardless of content —PII_a4f2e1c8. Use these for free-form fields the regexes can't catch: names, addresses, internal IDs. - The
datapayload of every event is walked recursively. Anywhere a declared dotted path matches (e.g.customer.email), the value is replaced with its pseudonym. - An encrypted token map is attached to each event, mapping pseudonyms back to originals. Encrypted with ChaCha20-Poly1305 using a fresh ephemeral X25519 keypair per event — forward secrecy means compromising one event doesn't expose others.
Setup
Pseudonymisation is on by default — the wrapper SDK requires the X25519 public key and the HMAC secret to init(). Generate both in the Keys page (browser-only, never seen by Wytness), set them as env vars, then declare any custom dotted paths in pii_fields / piiFields.
Env vars
WYTNESS_PII_PUBKEY=<base64 X25519 public key, 32 bytes decoded>WYTNESS_PII_SECRET=<base64 HMAC secret, >= 16 bytes decoded>Python
import wytness_ai # WYTNESS_PII_PUBKEY + WYTNESS_PII_SECRET read from env by default.wytness_ai.init( pii_fields=[ "customer.email", "customer.full_name", "billing.address", ],)TypeScript
import { init } from "@wytness/ai"; // WYTNESS_PII_PUBKEY + WYTNESS_PII_SECRET read from env by default.await init({ piiFields: [ "customer.email", "customer.fullName", "billing.address", ],});data payload — so customer.email matches data.customer.email. Casing matters; in TypeScript, match what you actually emit (camelCase if your AGT-TS entries use camelCase before the canonical transform).Auto-detected patterns
Regardless of pii_fields, the SDK pseudonymises these patterns wherever they appear in the data payload:
| Pattern | Example input | What gets stored |
|---|---|---|
| Email address | alice@example.com | EMAIL_3c866e6f |
| US SSN (xxx-xx-xxxx) | 123-45-6789 | SSN_58e8c0b9 |
| AU TFN (xxx-xxx-xxx) | 123-456-789 | TFN_a1b2c3d4 |
| Credit card (xxxx-xxxx-xxxx-xxxx) | 4111-1111-1111-1111 | CARD_7f8e9d0c |
| Phone number | +1-555-867-5309 | PHONE_2b3c4d5e |
Tokens are deterministic — the same value always produces the same token across sessions, agents, and time. This lets you correlate events involving the same person without ever storing their actual data.
Generating keys
The recommended path is the dashboard Keys page — both keys are generated in your browser using crypto.getRandomValues() + TweetNaCl, downloaded as a bundle, and stored in your secrets manager. The X25519 public key is registered with the Wytness backend so the dashboard knows which event maps to which keypair on reveal.
If you'd rather generate them yourself:
# HMAC secret (32 bytes, base64-encoded)python3 -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())" # X25519 keypairpython3 -c "from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKeyimport base64priv = X25519PrivateKey.generate()pub_bytes = priv.public_key().public_bytes_raw()priv_bytes = priv.private_bytes_raw()print('PII_PUBKEY (SDK env var):', base64.b64encode(pub_bytes).decode())print('PII_PRIVATE (dashboard reveal — keep safe):', base64.b64encode(priv_bytes).decode())"Register the X25519 public key against your org on the Keys page so the dashboard knows which key maps to your events. Store the X25519 private key somewhere you can paste into the dashboard when you need to reveal pseudonyms.
Revealing PII in the dashboard
When pseudonymisation is active, the dashboard shows tokens like EMAIL_3c866e6f by default. To see the originals:
- Go to Keys → PII Reveal Key.
- Paste your X25519 private key (base64, 32 bytes decoded).
- Navigate to Events or Sessions — pseudonymised events now show a reveal toggle.
- Click the toggle to render original values, highlighted in amber.
wytness SDK (Python or TypeScript) and decrypts accordingly — same wire format.Security architecture
| Property | Detail |
|---|---|
| HMAC secret (WYTNESS_PII_SECRET) | Held by you. Used to derive deterministic pseudonyms. Never transmitted to Wytness. |
| X25519 public key (WYTNESS_PII_PUBKEY) | Given to the SDK + registered with Wytness. Used to encrypt the token map. Cannot decrypt. |
| X25519 private key (dashboard reveal) | Held by you. Used to decrypt token maps in the dashboard. Never transmitted to Wytness. |
| Token map encryption | ChaCha20-Poly1305 with a fresh ephemeral X25519 keypair per event. Forward secrecy — compromising one event doesn't expose others. |
| Browser decryption | Private key in browser memory only. Never persisted, never transmitted. Cleared on tab close. |
| What Wytness can see | Pseudonyms only. Cannot reverse them (no HMAC secret). Cannot decrypt token maps (no X25519 private key). Zero-knowledge by construction. |
Related
- SDK API reference — full config + env-var table
- Security model
- Keys & signing — how the customer-held primitives compose with AGT identity
Protect sensitive data in your AI audit trail.
Start recording