Detection tiers
What Tier 0 and Tier 1 find - and what detection deliberately does not decide.
What does Tailrace actually find in a string or JSON object?
Detection produces spans (entity, offsets, confidence). It never chooses block or tokenize - that is policy's job. Engines are pluggable behind one Recognizer interface.
Tier 0 (ships in @tailrace/core)
Synchronous, zero runtime dependencies, WebCrypto only. Pattern + validator where applicable.
Secrets: api_key (known prefixes), jwt, private_key, high_entropy_secret (entropy + keyword context), connection_string
Structured PII: email, phone, credit_card (Luhn), iban, ssn, ip_address, url_credentials
Confidence is 1.0 when a validator confirms, 0.8 for pattern-only. Spans below the configured threshold (default 0.6) are dropped.
Honest expectation: Tier 0 is strong on structured shapes and known key prefixes. It is not a free-text NER. It will miss "John lives in Austin" and can false-positive without context gates (the high-entropy recognizer requires a nearby keyword for that reason).
Tier 1 (optional @tailrace/recognizer-ner)
Opt-in ONNX recognizer (Node/Fluid only). Bring your own Privacy Filter weights via modelPath -
Tailrace does not bundle or auto-download the model. Lazy-loaded; if the file is missing or
inference fails, one warning and Tier 0 continues (fail open).
secret from the model maps to a secret entity class and blocks under zero-config.
Other NER classes allow unless you merge nerRecommendedPolicy(). email / phone still follow
Tier 0 tokenize defaults when detected.
Custom recognizers
defineRecognizer({ id, entities, tier, scan }) - arbitrary scan logic. Register via createTailrace({ recognizers: [...] }).
For regex-shaped org IDs, use definePatternRecognizer (static validation, bounded scanning). See Write custom recognizers.
Objects, not stringified blobs
check walks string leaves of JSON (depth-limited, cycle-safe). Spans carry an RFC 6901 JSON Pointer path. Keys are scanned too. Never serialize an object to one string for scanning - offsets and rewrites would break.
Span merging
- Drop low-confidence spans
- Same entity, overlapping → union
- Different entities, overlapping → keep both; policy picks most restrictive action
- Sort by start; apply rewrites right-to-left
Perf gate
Tier 0 on a 4KB mixed fixture: p50 < 5ms in CI. Detection is a commodity; policy and vault get the product effort.
See it in practice
- Playground - paste text, see Tier 0 spans client-side
- Block secrets in Claude Code
- Threat model - what detection does not cover