A small, local-first CLI that strips secrets and customer-identifying data out of
support notes, logs, configs (HCL/JSON/YAML/.env), and general text — so the
result is safer to paste into AI tools like Cursor, ChatGPT, Codex, Claude Code,
or other agent-based coding assistants.
It is built around one idea: don't blindly replace everything with
<REDACTED>. Sensitive values are swapped for stable, typed placeholders
(<HOST_1>, <PRIVATE_IP_2>, <REDACTED_VAULT_TOKEN_1>), so you keep the
topology, relationships, and debugging context while losing the sensitive bits.
10.42.1.15 failed to connect to 10.42.1.16 on port 4647
↓
<PRIVATE_IP_1> failed to connect to <PRIVATE_IP_2> on port 4647
This tool reduces risk but does not guarantee that text is safe to share externally. Always review sanitized output before pasting into third-party tools.
It is a regex/heuristic tool, not a security product. It can miss novel secret formats and can occasionally over-redact. Treat it as a first pass, not a guarantee.
Requires Python 3.11+. No third-party runtime dependencies.
# from the project root
python3 -m venv .venv
source .venv/bin/activate
pip install -e . # provides the `sanitize` command
# (for tests) pip install -e ".[dev]"You can also run it without installing:
python -m sanitizer.cli input.txtsanitize input.txt # sanitized text to stdout
sanitize input.txt --output sanitized.txt
sanitize input.txt --report # count summary to stderr
sanitize input.txt --json-report # machine-readable summary to stderr
sanitize input.txt --profile strict
sanitize input.txt --profile infra-safe
sanitize input.txt --profile case-summary
sanitize input.txt --customer "Acme Corp" --customer "Globex"
cat input.txt | sanitize # read from stdinReports go to stderr, sanitized text to stdout, so redirection stays
clean: sanitize notes.txt --report > clean.txt writes only the sanitized text
to the file and prints the report to your terminal.
| Flag | Purpose |
|---|---|
-o, --output FILE |
Write sanitized text to a file instead of stdout. |
--profile NAME |
strict, infra-safe (default), or case-summary. |
--report |
Print a count summary to stderr. |
--json-report |
Print a JSON summary to stderr. |
--customer NAME |
Literal org/customer name to redact as <ORG_n> (repeatable). |
--allow TERM |
Term to never treat as a hostname (repeatable). |
--config FILE |
Path to a .sanitizer.yml config (overrides auto-discovery). |
--no-config |
Skip auto-discovery of a .sanitizer.yml file. |
--debug |
Include original values in the report (local use only; off by default). |
| Profile | Use it when… | Redacts | Preserves |
|---|---|---|---|
strict |
You're unsure the content is safe. | Secrets, tokens, emails, URLs, IPs, hostnames, orgs, UUIDs, phone numbers. | Ports, timestamps, error text. |
infra-safe |
You want infra/debugging context intact. | Secrets, tokens, creds, emails, URLs, hostnames, customer names, IPs (as stable placeholders). | Timestamps, ports, protocols, status codes, error messages, cloud regions, generic service names. |
case-summary |
Pasting a case narrative into an AI assistant. | Customer identity, emails, hostnames, URLs, tokens, secrets, phone numbers. | Product names (Nomad, Consul, Vault, Terraform…), versions, ports, the technical narrative. |
Input:
Customer Acme Corp reported failures from nomad-client-22.prod.acme.internal.
Reporter: jane.doe@acme.example.com
server at 10.42.1.15:4647 refused; egress to 35.91.12.8:443 throttled.
vault token hvs.CAESIJ1example0token0value0only0fake00000
AWS_ACCESS_KEY_ID=AKIA1234567890ABCDEF
sanitize --profile infra-safe --customer "Acme Corp":
Customer <ORG_1> reported failures from <HOST_1>.
Reporter: <REDACTED_EMAIL_1>
server at <PRIVATE_IP_1>:4647 refused; egress to <PUBLIC_IP_1>:443 throttled.
vault token <REDACTED_VAULT_TOKEN_1>
AWS_ACCESS_KEY_ID=<REDACTED_AWS_ACCESS_KEY_1>
Run it yourself against the bundled samples:
sanitize examples/case-notes.txt --profile infra-safe --customer "Acme Corp" --report
sanitize examples/nomad-client.log --profile infra-safe --report
sanitize examples/vault-config.hcl --report- Secrets/tokens: Vault (
hvs./hvb./s.), GitHub, Slack, JWTs, Bearer tokens, and genericpassword=/token=/secret=/client_secret=assignments. - Cloud credentials: AWS access keys (
AKIA/ASIA), labeled AWS secret keys, and PEM private-key blocks (redacted whole). - HashiCorp Nomad/Consul: ACL tokens (
NOMAD_TOKEN/CONSUL_HTTP_TOKENenv vars and Consultokens { agent = … }block roles), Serf gossip encryption keys (theencryptfield), and cluster-identifying resource IDs (alloc_id,node_id,eval_id,deployment_id) which become readable, correlated placeholders like<ALLOC_ID_1>. Tryexamples/consul-agent.hclandexamples/nomad-scheduler.log. - Identity: emails, phone numbers, URLs, customer/org names (via
--customer). - Network: private vs public IPv4/IPv6 (classified with the
ipaddressmodule), CIDR ranges (prefix preserved), hostnames/FQDNs.
Placeholders are deterministic within a run: the same original value always maps to the same placeholder, so repeated hosts/IPs stay correlated.
Drop a .sanitizer.yml in your repo root (or anywhere up the tree from the
input file) and it's picked up automatically; or point at one with
--config path/to/.sanitizer.yml. Use --no-config to ignore it.
customer_names: # redacted as <ORG_n>
- Acme Corp
- Globex
allowlist: # never treated as hostnames
- my-special-01
extra_patterns: # custom detectors; `label` is optional
- name: INTERNAL_CASE_ID
regex: "CASE-[0-9]+"
label: CASE_ID # -> <CASE_ID_n> (defaults to REDACTED_<NAME>)Config values are merged with --customer / --allow flags. To stay
dependency-free the loader parses the small YAML subset shown above (top-level
keys, - scalar lists, and - key: value mapping lists); see
examples/sanitizer.yml.example. Anything more elaborate isn't supported.
-
Open
sanitizer/patterns.pyand add aDetector:INTERNAL_CASE_ID = Detector( name="INTERNAL_CASE_ID", # shows up in --report pattern=re.compile(r"\bCASE-[0-9]+\b"), label="REDACTED_CASE_ID", # output is <REDACTED_CASE_ID_n> )
To redact only part of a match (and keep a surrounding label), set
group=to the capture-group index of the value. -
Add it to
REGEX_DETECTORSin the right order (more specific patterns first). -
Enable it for the relevant profiles in
sanitizer/profiles.py. -
Add a test under
tests/.
Detectors that need real logic (IP classification, hostname heuristics) live in
sanitizer/engine.py.
pip install -e ".[dev]" # or: pip install pytest
pytest- Runs entirely locally — input is never sent to any external service. The tool has no network code and no third-party runtime dependencies (standard library only).
- Raw input is never logged; original values are kept out of reports and out of
Findingreprs by default.--debugopts in to showing originals locally. - Every value in
examples/andtests/is fake and non-identifying — invented tokens, RFC 1918 / documentation IPs, and fictional names (Acme Corp,Globex) and domains (*.example.com,*.acme.internal). - You must still review the output before sharing it externally.
Released under the MIT License.