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 review | At export | Use it for | |
|---|---|---|---|
| Suggested | Pre-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). |
| Required | Pre-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)
- Open Settings â Privacy. Scroll to Custom Patterns.
- 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).
- 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.
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:
| Write | Meaning | Example |
|---|---|---|
EMP- | Literal text matches itself. Letters, digits, and - are safe as-is. | EMP- matches "EMP-" |
\d | Any 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" |
\b | Word 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.
| Name | Regex | What it matches | Sample match | Action |
|---|---|---|---|---|
| Employee ID | EMP-\d{6} | "EMP-" + exactly 6 digits | EMP-004821 | Suggested |
| Case / record ID | (?:OPP|ACC)-\d{3}-\d{5} | CRM opportunity or account records (the pattern from our own test profile) | OPP-006-88412 | Suggested |
| Dollar amount | \$[0-9][0-9,.]* | "$" + a digit, then any run of digits, commas, dots | $529,650.00 | Suggested |
| Stripe secret key | sk_live_[A-Za-z0-9]{24,} | Live-mode secret API keys | sk_live_4eC39HqLyjWDarjtT1zdp7dc | Required |
| Bearer token | (?i)bearer\s+[A-Za-z0-9._~+/=-]{20,} | Authorization headers, any casing | Bearer eyJhbGciOiJIUzI1NiJ9.abc123 | Required |
| AWS access key | AKIA[0-9A-Z]{16} | AWS access key IDs | AKIAIOSFODNN7EXAMPLE | Required |
| Internal hostname | [a-z0-9-]+\.corp\.example\.com | Any host on your internal domain (swap in yours) | db-prod-03.corp.example.com | Suggested |
| Medical record number | MRN[-\s]?\d{7} | "MRN" + 7 digits, separator optional (OCR-tolerant) | MRN-0084213 | Required |
| Order number | ORD-\d{4}-\d{6} | Year-prefixed order references | ORD-2026-014823 | Suggested |
| License plate (German format â adapt to your region) | \b[A-Z]{1,3}-[A-Z]{1,2}\s?\d{1,4}\b | District code â letters â up to 4 digits | M-AB 1234 | Suggested |
| Customer account | NGB-CUST-\d{7} | An internal account format â the shape to copy for your own scheme | NGB-CUST-4471902 | Required |
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:
- Anchors (
^$) rarely help. A "line" is whatever Vision decided is one line â a table row can OCR as one long line containing labels, values, and the neighboring column. Anchoring to its start or end anchors to an accident of layout. Prefer\bword boundaries; skip anchors. - Patterns never match across lines. An identifier that wraps onto a second line won't be caught as one match. Not usually a problem for short IDs; worth knowing for long tokens.
- Tolerate spaces where OCR inserts them. Kerning and column gaps make OCR occasionally read
MRN-0084213asMRN 0084213, or split grouped digits. Where your format has a separator, allow alternatives:[-\s]?instead of a hard-, or\s?between groups. - Ultra-short patterns over-match.
\d{3}matches ZIP fragments, ports, line numbers, and half of every dashboard. A pattern needs a distinctive literal prefix (EMP-,MRN,sk_live_) or a distinctive shape (\d{3}-\d{2}-\d{4}) to be useful. - Start specific, widen carefully. Write the strictest regex that matches your real examples, test it on a capture, and only loosen (an optional space, a fuzzy digit class, a wider count) when you see a real miss. Widening first means drowning in false blurs and losing trust in the review screen.
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.
- Add your pattern in Settings â Privacy (or install your test profile).
- Open a demo page in a browser â e.g. the CRM page, which contains
OPP-006-88412andACC-001-30977, both caught by the case-ID pattern above. Or paste your own fake sample values into any text editor. - Capture the window with Screenproof and look at the review screen: your pattern's name appears on each finding it produced.
- 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
| Symptom | Fix |
|---|---|
| Pattern doesn't match text that's clearly on screen | Compare 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 much | It'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 missing | An 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.