first commit
This commit is contained in:
62
docs/observability/monitor/analytics-browser/src/pii/pii.md
Normal file
62
docs/observability/monitor/analytics-browser/src/pii/pii.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# `pii.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements field-level PII scrubbing for `@suaveplan/analytics-browser`. `scrubPII(event, rules)` takes an `AnalyticsEvent` plus an ordered array of `ScrubRule`s and returns a new event with the affected `properties` either redacted, replaced with a deterministic fingerprint, or dropped entirely. Scrubbing runs on the tracker hot path between consent / sampling decisions and the queue write, so it must be synchronous, side-effect-free, and never throw on valid input. The function clones `properties` via `structuredClone` so the original event remains immutable.
|
||||
|
||||
## Features
|
||||
|
||||
- Three strategies: `"redact"` (replace value with `"[REDACTED]"`), `"hash"` (replace with FNV-1a fingerprint), `"drop"` (delete the key).
|
||||
- Dotted-path field resolution — `"user.email"` walks into nested objects.
|
||||
- Order-preserving — rules apply in the order given so later rules see the effect of earlier ones.
|
||||
- Pure / synchronous — no async, no I/O, no `subtle.digest`.
|
||||
- Idempotent — the same input + rules always yields the same output (including the fingerprint).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { scrubPII } from "@suaveplan/analytics-browser/pii";
|
||||
|
||||
const safe = scrubPII(event, [
|
||||
{ field: "email", strategy: "redact" },
|
||||
{ field: "ssn", strategy: "drop" },
|
||||
]);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import type { ScrubRule } from "@suaveplan/analytics-browser/types";
|
||||
import { scrubPII } from "@suaveplan/analytics-browser/pii";
|
||||
|
||||
const piiRules: readonly ScrubRule[] = [
|
||||
{ field: "user.email", strategy: "redact" },
|
||||
{ field: "user.phone", strategy: "hash" },
|
||||
{ field: "payment.cardNumber", strategy: "drop" },
|
||||
];
|
||||
|
||||
function safeTrack(name: string, properties: Record<string, unknown>) {
|
||||
const wrapped = {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
properties,
|
||||
timestamp: new Date().toISOString(),
|
||||
sessionId: "s",
|
||||
anonymousId: "a",
|
||||
};
|
||||
return scrubPII(wrapped, piiRules);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `scrubPII(event: AnalyticsEvent, rules: readonly ScrubRule[]): AnalyticsEvent` — returns a fresh event with scrubbed `properties`. Returns the original event reference when `rules.length === 0`.
|
||||
- Rule shape: `{ field: string; strategy: "redact" | "hash" | "drop" }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The fingerprint is FNV-1a 32-bit, encoded as 8 lowercase hex chars. FNV-1a was chosen over Web Crypto's `subtle.digest` because the latter is asynchronous, which would force the entire scrub pipeline onto a Promise chain and complicate the tracker's hot path. FNV-1a is NOT a cryptographic hash — it is a redaction that preserves cross-event correlation (the same value always fingerprints to the same hex string) without revealing the value itself. If a stronger guarantee is required, downstream code SHOULD scrub via a custom rule sequence and a pre-hashed input value, or implement its own `Sampler`-style hook.
|
||||
Reference in New Issue
Block a user