First release. Pure-Rust, in-WASM, regex-only, declares no
permissions. Suitable as a first redaction pass after
text.extract before any cloud-LLM step.
Detection categories:
EMAIL RFC-5321-ish local@domain, IDN-aware.
PHONE International (+CC …) and DE national
(030 …, 0151-…) shapes, 7..20 raw digits.
IBAN Word-bounded [A-Z]{2}\d{2}[A-Z0-9]{11,30}.
Structural only — MOD-97 checksum
deliberately skipped so partial / truncated
tokens in running text still get redacted.
BIC 8 or 11 uppercase alnum.
IPV4 Four 0..255 octets, dot-separated.
GERMAN_TAX_ID 11 consecutive digits, word-bounded.
CUSTOM Operator-supplied bare terms from the
newline-separated `custom_terms` input,
matched whole-word case-insensitive.
Token shape: ⟦TYPE_N⟧ — U+27E6 / U+27E7 mathematical white
square brackets. Distinct from any plain ASCII `[…]` already
present in source text (Markdown links, legal citations,
code blocks) so a reviewer never has to guess which `[…]`
is a redaction.
Outputs:
anonymized text Input with PII replaced by ⟦TYPE_N⟧.
Counter restarts at 1 per type so the
tokens stay operator-readable.
report json { redactions: [{type, token, original,
offset}…], counts: { TYPE: n, … } }.
Full original-text reconstruction is
possible from this — the GDPR
Art. 32(1)(a) "ability to undo"
requirement.
Quality bar (7 unit tests):
* email round-trip
* IBAN + BIC don't eat each other
* three phone-number shapes redact
* IPv4 only matches valid 0..255 octets
* custom_terms case-insensitive
* no double-redaction on overlapping patterns
* per-category counter resets correctly
Built artefact: target/wasm32-wasip2/release/text_anonymize.wasm
(~180 KiB stripped).
NER for free-text names / organisations / locations is the
v0.2.0 plan once a benchmarked ONNX model is selected; the
operator's `custom_terms` field is the v0.1.0 escape hatch.
Signed-off-by: flemming-it <sf@flemming.it>
2.8 KiB
2.8 KiB
text.anonymize
Regex-based PII anonymization for plain text. Detects and
replaces personally-identifiable patterns with stable tokens
of the shape ⟦TYPE_N⟧, and emits a JSON report describing
every redaction so a downstream verify step can audit
completeness.
Capability
text.anonymize@0.1.0
Inputs
| Name | Type | Description |
|---|---|---|
text |
text | UTF-8 string to redact. |
custom_terms |
text | Optional. Newline-separated list of extra bare terms to redact whole-word (case-insensitive). |
Outputs
| Name | Type | Description |
|---|---|---|
anonymized |
text | The input text with PII replaced by ⟦TYPE_N⟧ tokens. |
report |
json | { redactions: [{ type, token, original, offset }…], counts: { TYPE: n, … } }. |
Categories detected
| Type | Pattern |
|---|---|
EMAIL |
RFC-5321-ish local@domain, IDN domains OK. |
PHONE |
International (+49 …) or German national (030 …, 0151-…) shape. |
IBAN |
Word-bounded [A-Z]{2}\d{2}[A-Z0-9]{11,30}. Structural only — checksum deliberately not computed. |
BIC |
Word-bounded uppercase alnum, 8 or 11 chars. |
IPV4 |
Four 0..255 octets, dot-separated. |
GERMAN_TAX_ID |
11 consecutive digits, word-bounded. |
CUSTOM |
Every line of custom_terms matched whole-word, case-insensitive. |
No NER yet. v0.2.0 will add a small ONNX model for name / organisation detection once we have a benchmarked candidate.
Permissions
None. Pure-Rust, regex-only, in-WASM. No filesystem, no network, no LLM call.
Example flow
name: redact-case-file
inputs:
document: bytes
steps:
- id: extract
use: text.extract@^0
with:
document: $inputs.document
- id: redact
use: text.anonymize@^0
with:
text: $extract.extracted.pages[*].text
custom_terms: |
Mustermann
Musterfrau
outputs:
redacted: $redact.anonymized
audit_report: $redact.report
Build
cargo build --release --target wasm32-wasip2
# Output: target/wasm32-wasip2/release/text_anonymize.wasm