Screenproof Admin KB

Custom PII detection patterns

Teach Screenproof to find identifiers only your organization uses — employee IDs, case numbers, API keys, medical record numbers — with one regular expression per pattern. This page is everything a pattern author needs, whether you type patterns into Settings yourself or push them to a fleet.

On this page: how detection works · Suggested vs. Required · adding a pattern · regex crash course · copy-paste examples · the OCR reality · testing safely · troubleshooting · FAQ

How detection works

When you capture, Screenproof runs Apple's Vision OCR on the image, entirely on this Mac. Six built-in detectors — email, phone, ssn, card, iban, ipv4 — always run over the recognized text and flag findings for blur. Your custom patterns are simply more detectors: each one is a regular expression matched against the same OCR text, line by line. Nothing — not the image, not the text, not your patterns — ever leaves the machine.

Suggested vs. Required

Every pattern (built-in or custom) carries one of two actions. Both blur the finding immediately; they differ in what the reviewer may do before export:

At reviewAt exportUse it for
SuggestedPre-blurred; the reviewer can release (unblur) a finding they judge safe.Exports with whatever the reviewer kept.Convenience detection — things that are usually sensitive but sometimes fine to show (amounts, order numbers, hostnames).
RequiredPre-blurred and unreleasable — no tool in the editor can remove it.Always exported blurred.Compliance — things that must never leave the org readable (credentials, SSNs on managed fleets, patient identifiers).

Rule of thumb: individuals mostly want Suggested (detection as a safety net, human judgment last). Fleet admins enforcing policy want Required for the identifiers auditors care about — it removes the possibility of a wrong click.

Adding a pattern

In the app (individuals)

  1. Open Settings → Privacy. Scroll to Custom Patterns.
  2. Click Add Pattern…. An inline row appears with two fields: a Name (anything except the six built-in names) and the regular expression (ICU syntax — the flavor in the crash course below).
  3. Pick Suggested or Required, then press Return or click Add. The Add button stays disabled — and shows Invalid expression — until the regex actually compiles, so you can't save a broken pattern.

Your patterns take effect on the next capture. Remove one anytime with the ✕ on its row.

For fleets (admins)

Deliver patterns in the PIIPatterns array of your managed-preferences profile (domain app.screenproof) — each entry a dict with Name, Regex (ICU), and Action (blurSuggested or blurMandatory). The Profile Creator builds this interactively (section "3 · PII detection") and validates each regex as you type; a working example ships in the repo as examples/test-managed-preferences.mobileconfig. Full key reference: schema.json.

The union rule, in one sentence: managed and user pattern lists merge — the profile wins for any name it defines, and users can always add their own patterns on top but can never remove, edit, or weaken a managed one.

Built-in names are special. A PIIPatterns entry named email, phone, ssn, card, iban, or ipv4 only overrides that built-in's action (e.g. escalate ssn to Required); its Regex field is ignored — the built-in's OCR-tolerant regex is the single source of truth. In the app, those names are reserved and can't be used for custom patterns.

Regex crash course (just what you need)

Screenproof uses ICU regular expressions (the macOS-native flavor). Writing a pattern for an identifier needs only these pieces:

WriteMeaningExample
EMP-Literal text matches itself. Letters, digits, and - are safe as-is.EMP- matches "EMP-"
\dAny digit 0–9. (\w = letter, digit, or underscore.)\d\d\d matches "042"
[A-Z0-9]Character class: any one character from the set/ranges inside.[A-F] matches "C"
{6} {3,5} {24,}Repeat the previous item exactly 6 times / 3–5 times / 24 or more.\d{6} matches "004821"
? + *Previous item 0-or-1 / 1-or-more / 0-or-more times.\s? = optional whitespace
(?:OPP|ACC)Alternation in a group: either "OPP" or "ACC". Add ? after the group to make the whole group optional.(?:MRN)?\d{7}
(?i)At the start of the pattern: match case-insensitively.(?i)bearer matches "Bearer"
\. \$ \(Escaping: . $ ( ) [ ] { } | ? + * \ ^ have special meanings — put \ before one to match it literally.\$\d+ matches "$500"
\bWord boundary — the edge between a word character and a non-word character. Keeps \b\d{4}\b from matching inside a longer number.

That's the whole toolkit. Lookarounds, backreferences, and other advanced features work (it's full ICU) but no identifier pattern needs them — if you're reaching for one, simplify the pattern instead.

Worked examples (copy-paste ready)

Every regex below is tested against its sample with the exact engine Screenproof uses. Adjust the literal prefixes and digit counts to your own formats.

NameRegexWhat it matchesSample matchAction
Employee IDEMP-\d{6}"EMP-" + exactly 6 digitsEMP-004821Suggested
Case / record ID(?:OPP|ACC)-\d{3}-\d{5}CRM opportunity or account records (the pattern from our own test profile)OPP-006-88412Suggested
Dollar amount\$[0-9][0-9,.]*"$" + a digit, then any run of digits, commas, dots$529,650.00Suggested
Stripe secret keysk_live_[A-Za-z0-9]{24,}Live-mode secret API keyssk_live_4eC39HqLyjWDarjtT1zdp7dcRequired
Bearer token(?i)bearer\s+[A-Za-z0-9._~+/=-]{20,}Authorization headers, any casingBearer eyJhbGciOiJIUzI1NiJ9.abc123Required
AWS access keyAKIA[0-9A-Z]{16}AWS access key IDsAKIAIOSFODNN7EXAMPLERequired
Internal hostname[a-z0-9-]+\.corp\.example\.comAny host on your internal domain (swap in yours)db-prod-03.corp.example.comSuggested
Medical record numberMRN[-\s]?\d{7}"MRN" + 7 digits, separator optional (OCR-tolerant)MRN-0084213Required
Order numberORD-\d{4}-\d{6}Year-prefixed order referencesORD-2026-014823Suggested
License plate (German format — adapt to your region)\b[A-Z]{1,3}-[A-Z]{1,2}\s?\d{1,4}\bDistrict code – letters – up to 4 digitsM-AB 1234Suggested
Customer accountNGB-CUST-\d{7}An internal account format — the shape to copy for your own schemeNGB-CUST-4471902Required
Advanced: borrow the built-ins' OCR trick. The built-in detectors use fuzzy digit classes because OCR sometimes reads 0 as O, 1 as l/I, 8 as B, 5 as S. For a high-stakes Required pattern, replace \d with [0-9OoIlBS] to catch misreads that keep the shape: MRN[-\s]?[0-9OoIlBS]{7}. Over-blurring is the safe failure mode.

The OCR reality: you're matching what Vision read, not what the app rendered

Your pattern runs against OCR output, one recognized line at a time. That has practical consequences:

Also expected: the blur box can extend slightly past the match — Screenproof pads it and grows it to word boundaries so edge glyphs are never left readable.

Testing your pattern safely

Never test detection against real customer data on your screen. The Screenproof repo ships fake-data demo pages under examples/ — crm-opportunity-test.html, hr-payroll-test.html, patient-chart-test.html, bank-dashboard-test.html, saas-support-console-test.html, db-client-query-test.html, law-firm-matter-test.html — every value on them fabricated and clearly marked TEST DATA.

  1. Add your pattern in Settings → Privacy (or install your test profile).
  2. Open a demo page in a browser — e.g. the CRM page, which contains OPP-006-88412 and ACC-001-30977, both caught by the case-ID pattern above. Or paste your own fake sample values into any text editor.
  3. Capture the window with Screenproof and look at the review screen: your pattern's name appears on each finding it produced.
  4. Missed something? Select the region in the editor and use Copy Text to see exactly what OCR read (spacing and misreads included), adjust the regex, and capture again.

Matches show up under the pattern's name, so run one pattern change at a time and you always know which edit did what.

Troubleshooting

SymptomFix
Pattern doesn't match text that's clearly on screenCompare against what OCR actually read: select the region and Copy Text. Check for inserted/missing spaces (allow [-\s]?), letter/digit misreads (fuzzy classes, above), wrong case ((?i)), and digit-count mismatches — \d{6} will never match a 5-digit ID.
Pattern matches far too muchIt's too generic. Add the literal prefix, tighten quantifiers ({7} not +), and add \b at the edges so it can't match inside longer strings.
Profile pattern silently missingAn invalid entry is dropped with a diagnostic (e.g. PIIPatterns[MyPattern]: invalid regex; dropped) and the rest of the policy still loads — one bad pattern never takes down the profile. Check the entry has Name, Regex, and a valid Action, and that the regex compiles (the Profile Creator validates as you type). In the app, the Add button refusing to enable means the same thing: the expression doesn't compile.
Can't edit or remove a pattern (lock icon)By design: it's enforced by your organization's profile, and user settings can never weaken managed policy — see how managed keys work. Ask your MDM admin to change the profile.

FAQ

Do my patterns or the recognized text ever leave the device?

No. OCR is Apple's on-device Vision framework and the regex matching runs in the app. There is no cloud detection service and no telemetry of matches. The only things that ever leave the Mac are the exports you approve and, if your org configured one, audit events to your own webhook.

What's the performance cost of many patterns?

Modest. Each pattern is compiled once per capture and run over the recognized text lines — OCR itself dwarfs the regex work, so dozens of patterns are fine. The one way to hurt yourself is catastrophic backtracking: avoid nesting unbounded quantifiers like (\w+\s*)+. Every pattern on this page uses bounded, linear shapes — stay in that style.

Can I disable a built-in detector?

No. There is deliberately no "off" action — the only override is escalating a built-in from Suggested to Required. That guarantee is what makes the union rule safe: since no entry can turn detection off, a user's own patterns can only ever add protection, never weaken what a profile enforces. A built-in Suggested finding that's genuinely fine to show takes one click to release at review.