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

  1. Structured PII (emails, phones, SSN, AU TFN, credit cards) is detected by regex and replaced with typed HMAC tokens — EMAIL_3c866e6f, PHONE_9a3c5f78, etc.
  2. Declared fields listed in pii_fields / piiFields are pseudonymised regardless of content — PII_a4f2e1c8. Use these for free-form fields the regexes can't catch: names, addresses, internal IDs.
  3. The data payload of every event is walked recursively. Anywhere a declared dotted path matches (e.g. customer.email), the value is replaced with its pseudonym.
  4. 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

.env
WYTNESS_PII_PUBKEY=<base64 X25519 public key, 32 bytes decoded>
WYTNESS_PII_SECRET=<base64 HMAC secret, >= 16 bytes decoded>

Python

agent.py
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

agent.ts
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",
],
});
Note
Dotted paths match against the event's 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).
Important
Pseudonymisation is not retroactive. Events captured before keys were set cannot be pseudonymised or revealed later. Set both keys up before you ingest any real data.

Auto-detected patterns

Regardless of pii_fields, the SDK pseudonymises these patterns wherever they appear in the data payload:

PatternExample inputWhat gets stored
Email addressalice@example.comEMAIL_3c866e6f
US SSN (xxx-xx-xxxx)123-45-6789SSN_58e8c0b9
AU TFN (xxx-xxx-xxx)123-456-789TFN_a1b2c3d4
Credit card (xxxx-xxxx-xxxx-xxxx)4111-1111-1111-1111CARD_7f8e9d0c
Phone number+1-555-867-5309PHONE_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:

terminal
# HMAC secret (32 bytes, base64-encoded)
python3 -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"
# X25519 keypair
python3 -c "
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
import base64
priv = 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:

  1. Go to Keys → PII Reveal Key.
  2. Paste your X25519 private key (base64, 32 bytes decoded).
  3. Navigate to Events or Sessions — pseudonymised events now show a reveal toggle.
  4. Click the toggle to render original values, highlighted in amber.
Tip
The private key stays in your browser memory. It is never persisted to disk and never transmitted to Wytness. Closing the tab clears the key. The dashboard auto-detects whether the token map was produced by the wytness SDK (Python or TypeScript) and decrypts accordingly — same wire format.

Security architecture

PropertyDetail
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 encryptionChaCha20-Poly1305 with a fresh ephemeral X25519 keypair per event. Forward secrecy — compromising one event doesn't expose others.
Browser decryptionPrivate key in browser memory only. Never persisted, never transmitted. Cleared on tab close.
What Wytness can seePseudonyms only. Cannot reverse them (no HMAC secret). Cannot decrypt token maps (no X25519 private key). Zero-knowledge by construction.

Protect sensitive data in your AI audit trail.

Start recording

We set no cookies. Sign-in and preferences use essential first-party browser storage only — no tracking, advertising, or third-party analytics. Privacy Policy

PII ProtectionHow it works
TABLE OF CONTENTS