A local anonymization gate for AI uploads — one static binary. Scan text, files, screenshots and clipboard content for secrets and personal data before they leave your machine — then pseudonymize consistently, and translate AI answers back afterwards. No Docker, no Python, no network.
🇩🇪 Deutsche Version: README.de.md
you ──► maskctl ──► [gitleaks ruleset: secrets? ⛔ hard stop]
[PII detection DE+EN: PERSON_1, IP_3, HOST_2 …]
│
▼
safe to paste into ChatGPT / Claude / Copilot
│
AI answer ──► maskctl --restore ──► original names back (local only)
Every prompt you paste into a cloud AI is an upload. Logs, configs and
screenshots are full of hostnames, IPs, customer names, tokens. maskctl is
the gate in between:
- Secrets block hard. The gitleaks
ruleset runs in-process; if it finds credentials,
maskctlexits non-zero and refuses to continue. Secrets are never "anonymized away" — they must not be uploaded at all. - PII gets pseudonymized, not destroyed. Built-in recognizers (email,
IPs, phone numbers, credit cards with Luhn check, IBANs with checksum
validation, crypto wallets, URLs, person names) plus your own regex
patterns replace entities with stable placeholders (
PERSON_1,IP_3). The same input always maps to the same placeholder, so the AI's answer stays coherent. - Round-trip.
maskctl --restoremaps placeholders in the AI's answer back to the originals — using a local mapping table (0600, never uploaded). - 100 % local, zero dependencies. One static binary. Nothing ever calls out — there is not even an HTTP client in the pipeline.
$ git clone https://github.com/muhittink/maskctl && cd maskctl
$ ./install.sh # builds with Go, or uses a downloaded release binary
$ maskctl --selftestOptional tools (features degrade gracefully without them):
wl-clipboard for --clip, gnome-screenshot or grim+slurp for
--shot, tesseract-ocr + tesseract-ocr-deu for image redaction.
$ maskctl dump.log # file → dump.log.masked + stdout
$ cat error.log | maskctl # stdin → stdout (pipe-friendly)
$ maskctl --clip # anonymize clipboard in place (text or image)
$ maskctl --shot # area screenshot → redacted PNG
$ maskctl diagram.png # image → diagram.redacted.png
$ maskctl --restore < answer.txt # translate AI answer back (local only)
$ maskctl --diff dump.log # show before/after diff on stderrExit codes: 0 ok · 1 secrets found, upload forbidden · 2 error.
$ echo "Node fw-01 (192.0.2.17) down, contact jane.doe@corp.example" | maskctl
Node HOST_1 (IP_1) down, contact EMAIL_1
maskctl: 3 replacements (mapping: ~/.local/state/maskctl/mapping.json)Everything lives in ~/.config/maskctl/. The interesting file is
patterns.yaml (created from config/patterns.example.yaml),
re-read on every run:
score_threshold: 0.4 # ignore low-confidence matches
entities: # which built-in entities to replace
- PERSON
- EMAIL_ADDRESS
- IP_ADDRESS
custom: # your own regex recognizers
- name: internal-domain
entity: DOMAIN
regex: '\b[a-zA-Z0-9][a-zA-Z0-9.-]*\.(?:internal|corp|lan|local)\b'
score: 0.9
- name: de-license-plate # fuzzy pattern, guarded by context words:
entity: LICENSE_PLATE # base score is below the threshold, a nearby
regex: '\b[A-ZÄÖÜ]{1,3}-[A-Z]{1,2} ?\d{1,4}\b'
score: 0.2 # trigger word boosts it (+0.35)
context: [kennzeichen]
allowlist: # matches that are NEVER masked (public IPs,
- 8.8.8.8 # product names mistaken for persons);
- "re:acme[ -]?\\S*" # "re:" entries match as regex (fullmatch)
prefixes: # placeholder prefix per entity type
IP_ADDRESS: IP
DOMAIN: DOMAINCustom patterns support lookbehind/lookahead, (?m), (?-i:…) and are
compiled case-insensitively (like before). They run with a 2-second match
timeout, so a pathological regex fails loudly instead of hanging.
The example config ships recognizers for infrastructure naming (hostnames, VLAN IDs, environment names, internal domains) and a battle-tested generic Linux/IT ruleset — adapt them to your environment.
Custom secret rules: copy
config/gitleaks.toml.example to
~/.config/maskctl/gitleaks.toml to add company-specific token formats to
the secret gate. Keep the [extend] useDefault = true block — without it
your file replaces the entire default ruleset. extend.url is rejected:
maskctl never talks to the network.
| Stage | Component | Guarantee |
|---|---|---|
| 1. Secret scan | gitleaks ruleset, in-process | hard stop on any credential |
| 2. PII detection | built-in Go recognizers + your patterns.yaml | merged spans — no partial redactions |
| 3. Pseudonymization | local mapping table | stable, consistent placeholders across sessions |
| 4. Image redaction | tesseract OCR + same detection + black-box fill | screenshots and images; secrets in images get blacked out too |
| 5. Restore | local mapping table | AI answers translated back, offline |
The mapping table (~/.local/state/maskctl/mapping.json, mode 0600) is the
only place linking placeholders to originals. It never leaves your machine —
treat it like a key.
PERSON detection uses embedded name dictionaries and heuristics ("Herr
Müller", "Dr. Jane Doe", known first name + surname) instead of an NER
model. That keeps maskctl a single dependency-free binary, but recall is
lower than spaCy's: an uncommon first name without a title will slip
through. Compensate with custom: patterns for your naming conventions,
and allowlist: entries for false positives. The secret gate and all
regex-based detection are unaffected by this trade-off.
- One static binary; the pipeline contains no HTTP client, no containers,
no interpreters. gitleaks
extend.urlremote configs are rejected. - Overlapping detection spans are merged before replacement, so no fragment of a detected entity survives.
- User regex patterns run with a hard match timeout (ReDoS guard).
--shot/--clipdelete the unredacted temporary image after redaction.
Found a vulnerability? See SECURITY.md.
Your patterns.yaml, gitleaks.toml and mapping.json keep working
unchanged — placeholders stay stable. The docker artifacts in
~/.config/maskctl/ are obsolete:
$ docker compose -f ~/.config/maskctl/docker-compose.yml down --rmi all
$ rm ~/.config/maskctl/{docker-compose.yml,Dockerfile.analyzer,analyzer-conf.yaml,nlp-conf.yaml,recognizers-conf.yaml}- Pre-commit and CI integration (gate merge requests, not just uploads)
- Team features: shared policy config, audit log
Apache-2.0 © Muhittin Kesikli