Skip to content

test(scrub): property-based hardening of the PII scrubber - #19

Merged
Starefossen merged 1 commit into
mainfrom
test/scrub-property-based
Jul 12, 2026
Merged

test(scrub): property-based hardening of the PII scrubber#19
Starefossen merged 1 commit into
mainfrom
test/scrub-property-based

Conversation

@Starefossen

Copy link
Copy Markdown
Member

Part of the test-data quality work discussed under nais/grafana-apm-app#90.

What

Adds fast-check (dev-only) and scrub.property.test.ts: generated PII-shaped identifiers — fnr with plausible date prefixes (incl. D-number/H-number/synthetic variants), emails, raw NAV idents, token-bearing query params — embedded in arbitrary surrounding text, asserting the scrubber's invariants rather than hand-picked examples:

  • [fnr]/[email]/[redacted] redaction wherever the identifier is properly delimited (both fnr forms: contiguous and space-separated)
  • Idempotence: scrubbing twice ≡ scrubbing once, over arbitrary unicode and over guaranteed-PII strings
  • Never throws, always returns a string, on fully arbitrary input
  • scrubUrl: query/fragment always dropped; fnr/UUID/ident path segments masked wherever they sit
  • looksLikePii: flags every generated fnr/email/ident; passes UUIDs (opaque correlation keys) through

On failure fast-check prints the seed + minimal counterexample — add it to scrub.test.ts when fixing.

🔍 Finding (first run)

The properties immediately surfaced a real, previously undocumented limitation — minimal counterexample a41810000000:

A word character glued directly onto an fnr breaks the \b boundary, so e.g. bruker01017012345 passes through unscrubbed. Digits-glued is the deliberate false-positive guard (11 digits inside a longer number are not an fnr); letters-glued is a side effect of \b treating [A-Za-z0-9_] uniformly.

Rather than silently tightening the regex (which trades against false positives on letter+digit identifiers like case numbers), the limitation is pinned by an explicit documenting test. Whether to tighten is a product decision — happy to open a follow-up issue if we want to pursue it.

Notes

Part of nais/grafana-apm-app#90 (test-data quality): fast-check generates
PII-shaped identifiers (fnr incl. D-/H-/synthetic variants, emails, NAV
idents, token params) embedded in arbitrary surrounding text and asserts
the scrubber's invariants: redaction wherever properly delimited,
idempotence, never-throws, query/fragment dropping and path-segment
masking in scrubUrl, and looksLikePii precision (UUIDs pass, identifiers
do not).

First run immediately surfaced a real, previously undocumented limitation:
a word character glued directly onto an fnr breaks the \b boundary, so
'bruker01017012345' passes through unscrubbed. Digits-glued is the
deliberate false-positive guard; letters-glued is a \b side effect. The
limitation is now pinned by an explicit documenting test - tightening it
is a product decision traded against false positives on letter+digit
identifiers.
@Starefossen
Starefossen force-pushed the test/scrub-property-based branch from 6273843 to 2ba7a18 Compare July 12, 2026 18:58
@Starefossen
Starefossen enabled auto-merge July 12, 2026 18:58
@Starefossen
Starefossen added this pull request to the merge queue Jul 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens confidence in the PII scrubber by adding property-based tests that exercise many generated, PII-shaped inputs and assert invariants (redaction, idempotence, and “never throws”), using fast-check as a dev-only dependency.

Changes:

  • Add src/scrub.property.test.ts with property-based tests for scrubString, scrubUrl, and looksLikePii.
  • Add fast-check as a dev dependency and lockfile entries (fast-check, pure-rand).

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/scrub.property.test.ts Introduces fast-check properties to validate scrubber invariants across generated PII-like inputs.
package.json Adds fast-check to devDependencies.
pnpm-lock.yaml Locks fast-check and its transitive dependency pure-rand.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.tuple(surroundingArb, fc.oneof(fnrArb, emailArb), surroundingArb)
.map(([a, pii, b]) => `${a}${pii}${b}`);
fc.assert(
fc.property(fc.oneof(fc.string({ unit: 'binary' }), withPii), (s) => {

it('never throws and always returns a string, whatever the input', () => {
fc.assert(
fc.property(fc.string({ unit: 'binary' }), (s) => {

it('never throws on arbitrary input', () => {
fc.assert(
fc.property(fc.string({ unit: 'binary' }), (s) => {
Merged via the queue into main with commit 1158661 Jul 12, 2026
6 checks passed
@Starefossen
Starefossen deleted the test/scrub-property-based branch July 12, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants