rust-sanitize 0.15.0

Deterministic one-way data sanitization engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# rust-sanitize

[![CI](https://github.com/kayelohbyte/rust-sanitize/actions/workflows/ci.yml/badge.svg)](https://github.com/kayelohbyte/rust-sanitize/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/rust-sanitize.svg)](https://crates.io/crates/rust-sanitize)
[![docs.rs](https://docs.rs/rust-sanitize/badge.svg)](https://docs.rs/rust-sanitize)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/rust-1.86%2B-blue.svg)]()

Scrub sensitive data from logs and configs before sharing them — with support teams, vendors, or AI tools.

`rust-sanitize` replaces API keys, emails, IPs, passwords, tokens, and other secrets with structurally plausible substitutes. Replacements are **one-way**: no mapping file is stored, there's no restore mode, and nothing sensitive persists after the run.

Works as a **CLI**, a **Rust library**, and an **MCP server** — so AI assistants like Claude and Cursor can sanitize files on your behalf before raw content ever reaches the model.

> **Scope:** `rust-sanitize` targets **structured secret patterns** — API keys, tokens, credentials, emails, IPs, and other typed values in logs and configs. It is **not** a general-purpose anonymization tool and does **not** perform free-text NLP redaction of prose PII (names, addresses buried in sentences). For that, reach for a dedicated anonymization/NER library.

---

## MCP: Clean Before the LLM Sees It

Install the MCP server and your AI assistant can sanitize files directly — secrets stay inside the audited Rust process and never enter the context window.

**Step 1 — Install the binaries**

Download `sanitize` and `sanitize-mcp` for your platform from the [Releases](https://github.com/kayelohbyte/rust-sanitize/releases) page and place them on your `$PATH` (e.g. `/usr/local/bin/`). No Deno or Node required — the runtime is embedded.

**Step 2 — Register with your AI tool**

**Claude Code:**

```bash
claude mcp add rust-sanitize /usr/local/bin/sanitize-mcp \
  -e SANITIZE_BIN=/usr/local/bin/sanitize
```

**Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "rust-sanitize": {
      "command": "/usr/local/bin/sanitize-mcp",
      "env": { "SANITIZE_BIN": "/usr/local/bin/sanitize" }
    }
  }
}
```

Once connected, the assistant can sanitize inline text or files, scan for leaks without modifying anything, or produce a pre-structured prompt ready for incident triage or config review. See [docs/mcp.md](docs/mcp.md) for the full tool reference, all parameter examples, and setup instructions for Cursor, Neovim, and OpenCode.

---

## Install

**CLI from crates.io:**

```bash
cargo install rust-sanitize
```

**From source:**

```bash
git clone https://github.com/kayelohbyte/rust-sanitize.git
cd rust-sanitize
cargo build --release
# Binary: target/release/sanitize
```

> **Windows:** Requires the MSVC linker. Install [Visual Studio Build Tools]https://visualstudio.microsoft.com/visual-cpp-build-tools/ and select the **Desktop development with C++** workload.

**As a Rust library:**

```bash
cargo add rust-sanitize
```

**MCP binary:** Download `sanitize-mcp` for your platform from the [Releases](https://github.com/kayelohbyte/rust-sanitize/releases) page — no Deno or Node required, the runtime is embedded.

---

## Quick Start

![Zero-config scan: secrets in a log replaced with structurally plausible substitutes](docs/demos/out/01-quickstart.gif)

> More demos — dry-run & CI gate, app bundles, stdin piping, structured field rules — in [docs/demos]docs/demos/. All recordings are scripted and regenerable.

### No setup — scan immediately

When you run `sanitize` with no secrets file or app bundle, the built-in patterns activate automatically. They cover the most common secrets: API keys (AWS, GCP, GitHub, Stripe, Slack, OpenAI, Anthropic, HuggingFace, and more), JWTs, emails, IPv4/IPv6, UUIDs, MAC addresses, PEM headers, credential URLs, and password/secret key=value pairs.

```bash
sanitize server.log
```

Output goes to `server-sanitized.log` next to the source. Use `-o /path/to/output` to override, or `-o -` for stdout.

On first run, the same balanced pattern set is written to `~/.config/sanitize/secrets.yaml` so you can extend it for repeat use. See [Configuration](docs/cli-reference.md#configuration) for the full layered config model, env vars, and project-level `.sanitize.yaml`.

```bash
# Dry-run — see what would be replaced without writing anything:
sanitize server.log -n

# CI gate — fail if secrets are detected:
sanitize config.yaml --fail-on-match
```

### Template-based setup — start from a preset

```bash
sanitize template balanced       # mirrors the built-in runtime defaults
sanitize template aggressive     # balanced + entropy detection + broad token patterns
sanitize template k8s -o k8s.secrets.yaml
```

Each preset writes a ready-to-edit `secrets.template.<preset>.yaml` covering one use case. Available presets: `balanced` (default), `aggressive`, `generic`, `web`, `k8s`, `database`, `aws`.

```bash
sanitize server.log -s secrets.template.balanced.yaml
```

`aggressive` also matches broad hostnames, short container IDs, and high-entropy token patterns — recommended when sharing logs with an LLM.

### App bundles — zero config for common applications

Built-in bundles for 28 applications pair a secrets pattern set with a structured field profile so field-level sanitization works out of the box, no authoring required.

```bash
sanitize /etc/gitlab/gitlab.rb --app gitlab
sanitize nginx.conf docker-compose.yml --app nginx,docker-compose
sanitize values.yaml --app kubernetes

# See all available bundles:
sanitize apps
```

Built-in bundles: `ansible`, `aws-cli`, `bruno`, `circleci`, `datadog`, `dataiku`, `django`, `docker-compose`, `elasticsearch`, `fstab`, `github-actions`, `gitlab`, `grafana`, `har`, `heroku`, `insomnia`, `kubernetes`, `laravel`, `mongodb`, `mysql`, `nginx`, `postgresql`, `postman`, `rails`, `redis`, `splunk`, `spring-boot`, `terraform`.

---

## Common Workflows

### Multiple files and archives in one pass

```bash
sanitize server.log config.yaml backup.zip -s patterns.yaml
# Produces: server-sanitized.log  config-sanitized.yaml  backup.sanitized.zip

# Send all outputs to a directory:
sanitize server.log config.yaml backup.zip -s patterns.yaml -o /tmp/clean/
```

### Pipe from another command

Stdin is sanitized and sent to stdout automatically.

```bash
grep "error" server.log | sanitize -s patterns.yaml
mysqldump mydb | sanitize | gzip > dump.sql.gz

# Mix stdin with file inputs (stdin → stdout; files → per-file siblings):
cat extra.log | sanitize - config.yaml -s patterns.yaml
```

When reading from stdin and the format can't be inferred from a filename, use `-f` to specify it: `-f yaml`, `-f json`, `-f csv`, `-f log`, etc.

### CI secrets gate

```bash
# Fail the build if secrets are detected:
sanitize config.yaml -s patterns.yaml --fail-on-match

# Same with encrypted patterns file:
SANITIZE_PASSWORD="..." sanitize config.yaml -s patterns.enc --encrypted-secrets --fail-on-match

# Stream per-match findings as NDJSON for jq or SIEM ingest:
sanitize server.log -s patterns.yaml --dry-run --findings | \
  jq 'select(.type=="file") | .pattern_counts'
```

### Archive entry filtering

Filter which entries inside an archive are processed. `*` matches within a single directory segment, `**` crosses directory boundaries, trailing `/` matches a subtree.

```bash
# Keep only the config directory:
sanitize backup.zip --only 'config/' -s patterns.yaml

# Keep all JSON, drop the secrets file:
sanitize backup.zip --only '**/*.json' --exclude config/secrets.json -s patterns.yaml

# Independent filters per archive in one command:
sanitize a.zip --only 'config/' b.tar.gz --only '**/*.log' -s patterns.yaml
```

### Structured field rules (`--profile`)

Replace specific named fields in YAML, JSON, TOML, CSV, `.env`, and INI files. Comments, indentation, key ordering, and unmatched values are preserved exactly.

```yaml
# fields.yaml
- processor: yaml
  extensions: [".yaml", ".yml"]
  fields:
    - pattern: "*.password"
      category: "custom:password"
    - pattern: "*.username"
      category: email

- processor: jsonl
  extensions: [".jsonl", ".ndjson", ".log"]
  options:
    skip_invalid: "true"   # pass non-JSON lines through unchanged
  fields:
    - pattern: "*.email"
      category: email
    - pattern: "*.ip"
      category: ipv4
```

```bash
sanitize config.yaml server.log --profile fields.yaml -s patterns.yaml
```

When `--profile` is active, values discovered in structured fields are automatically written back to the patterns file as literals so the streaming scanner can match them in other files too. See [Structured Processing](docs/structured-processing.md) for the full field pattern syntax and two-phase pipeline.

### Encrypted patterns file

```bash
# Encrypt once:
sanitize encrypt patterns.yaml patterns.yaml.enc --password

# Use interactively:
sanitize data.log -s patterns.enc --encrypted-secrets -p

# Non-interactive (CI / pipes):
SANITIZE_PASSWORD="my-password" sanitize data.log -s patterns.enc --encrypted-secrets
# Or read from a file:
sanitize data.log -s patterns.enc --encrypted-secrets -P /run/secrets/pw
```

### Allowlist — pass specific values through unchanged

```bash
sanitize data.log -s patterns.yaml --allow localhost --allow "*.internal" --allow "192.168.1.*"
```

For project-stable allowlists, add `kind: allow` entries directly to your patterns file:

```yaml
- pattern: "*.internal"
  kind: allow
- pattern: "192.168.1.*"
  kind: allow
```

### Deterministic mode

Same seed + same input produces identical replacements across runs and machines — useful when correlating sanitized data across multiple files or sharing a reproducible dataset with a team.

```bash
sanitize data.csv -s patterns.yaml -d
```

### Shannon entropy detection

Catch high-entropy tokens not covered by any pattern — useful for novel API keys, obfuscated secrets, or anything that wasn't anticipated when the patterns file was written.

```bash
sanitize server.log -s patterns.yaml --entropy-threshold 4.5

# Dry-run prints a calibration histogram to help tune the threshold:
sanitize server.log -s patterns.yaml -n --entropy-threshold 4.5
```

### LLM-ready output

Sanitize and produce a structured prompt in one step:

```bash
sanitize server.log -s patterns.yaml --llm                  # incident triage
sanitize nginx.conf --app nginx --llm review-config         # configuration review
sanitize nginx.conf --app nginx --llm review-security       # security posture review
```

The prompt includes a `## Files Analyzed` manifest and embeds sanitized content inline (`<content>` blocks). For large file sets or agentic LLMs that can read files with their own tools, add `--output` to switch to **reference mode** — files are written to disk and the prompt lists their absolute paths instead:

```bash
sanitize logs/ -s patterns.yaml --llm review-security --output /tmp/sanitized/
```

---

## Supported Formats

| Format | Detection |
|--------|-----------|
| Plain text / log | Default fallback for all files |
| JSON | Profile match or `{`/`[` heuristic |
| NDJSON / JSON Lines | Profile match or multi-line `{` heuristic; streaming — bounded memory for GB-scale log files |
| YAML | Profile match or `---`/`- `/`: ` heuristic |
| TOML | Profile match or `[section]` heuristic |
| XML | Profile match or `<?xml`/`<` heuristic |
| CSV / TSV | Profile match only |
| `.env` | Profile match only |
| INI / `.conf` | Profile match only |
| Key-value | Profile match only |
| Log lines (mixed) | Profile match only |
| Tar | `.tar` extension |
| Tar.gz / .tgz | `.tar.gz` / `.tgz` extension |
| Zip | `.zip` extension |

---

## Library

```bash
cargo add rust-sanitize
```

```rust
use rust_sanitize::category::Category;
use rust_sanitize::generator::HmacGenerator;
use rust_sanitize::store::MappingStore;
use std::sync::Arc;

// Deterministic generator seeded with a fixed 32-byte key.
let generator = Arc::new(HmacGenerator::new([42u8; 32]));
let store = MappingStore::new(generator, None);

// One-way replacement.
let sanitized = store.get_or_insert(&Category::Email, "alice@corp.com").unwrap();
assert!(sanitized.contains("@corp.com"));
assert_eq!(sanitized.len(), "alice@corp.com".len());

// Same input → same output within a run.
let again = store.get_or_insert(&Category::Email, "alice@corp.com").unwrap();
assert_eq!(sanitized, again);
```

See [Library API Reference](docs/api-reference.md) for the full module-by-module API.

### Feature flags

The default build includes the CLI binary and every processor. Library-only consumers can trim dependencies by disabling default features and opting back into just what they need:

```toml
# Core only — HMAC/random generators, mapping store, streaming scanner,
# and the always-on JSON/YAML/TOML/INI/env/key-value/log-line processors.
# Drops clap, ureq, walkdir, ctrlc, rpassword, zip, tar, flate2, csv, csv-core, quick-xml.
rust-sanitize = { version = "0.13", default-features = false }

# Add archive (zip/tar/tar.gz) and/or the CSV + XML processors as needed.
rust-sanitize = { version = "0.13", default-features = false, features = ["archive", "structured"] }
```

| Feature | Pulls in | Enables |
|---------|----------|---------|
| `cli` *(default)* | `clap`, `ureq`, `walkdir`, `ctrlc`, `rpassword` | The `sanitize` binary; implies `archive` + `structured` |
| `archive` *(default)* | `zip`, `tar`, `flate2` | `ArchiveProcessor` (zip / tar / tar.gz) |
| `structured` *(default)* | `csv`, `csv-core`, `quick-xml` | `CsvProcessor` and `XmlProcessor` |

JSON, YAML, TOML, INI, `.env`, key-value, and log-line processing — plus the regex/literal streaming scanner — are always built. The format-preserving structured editors parse with small byte-span crates (`jiter` for JSON/JSONL, `saphyr-parser` for YAML, `toml_edit` for TOML); these are always on and not behind a feature flag.

---

## Security Model

Replacements are one-way by design. No reverse mapping is stored or recoverable from sanitized output alone. The `MappingStore` forward map lives only in process memory and is zeroized on drop.

Key properties:

- **Encryption at rest** — secrets files use AES-256-GCM (PBKDF2-HMAC-SHA256, 600 000 iterations). Plaintext files are also supported.
- **Zeroization** — HMAC keys, secret entries, mapping-store keys, and decrypted blobs are zeroized on drop.
- **Regex hardening** — per-pattern automaton and DFA size limits (1 MiB each) prevent ReDoS and unbounded memory growth.
- **Defensive limits** — input size caps, recursion depth limits, node-count caps, and pattern-count limits bound every parser.
- **Zero `unsafe`** — thread safety through `DashMap` and `Arc`; `Send + Sync` bounds verified at compile time.

See [SECURITY.md](SECURITY.md) for the full threat model and mitigations.

---

## Documentation

| Document | Description |
|----------|-------------|
| [MCP Reference]docs/mcp.md | MCP server setup, all tool parameters, JSON examples, IDE configs (Cursor, Neovim, OpenCode), and namespace-based multi-tenant setup. |
| [CLI Reference]docs/cli-reference.md | Full `sanitize` command reference including all flags, subcommands, secrets file format, and examples. |
| [Structured Processing]docs/structured-processing.md | `--profile` usage, field patterns, two-phase pipeline, format preservation, and processor options. |
| [Supported Categories]docs/categories.md | All 18 built-in replacement categories with strategies and examples, plus custom categories. |
| [Pluggable Strategies]docs/strategies.md | The `Strategy` trait, 6 built-in strategies, and guide to writing custom strategies. |
| [Library API Reference]docs/api-reference.md | Module-by-module public API tables. |
| [Defensive Limits & Streaming]docs/defensive-limits.md | Streaming chunking model, archive processing flow, and all defensive size/depth/count limits. |
| [Architecture]ARCHITECTURE.md | Internal architecture, data flow, module map, concurrency model, and streaming design. |
| [Security]SECURITY.md | Security properties, threat mitigations, encryption details, and zeroization strategy. |
| [Contributing]CONTRIBUTING.md | Build instructions, test suite, fuzz targets, linting, and PR guidelines. |
| [Changelog]CHANGELOG.md | Release history and version notes. |

---

## Limitations

- **No restore.** Replacements are one-way by design. No undo, decrypt-output, or reverse-mapping capability.
- **Structured-to-scanner handoff.** When `--profile` is active, discovered values are appended to your secrets file as `kind: literal` entries so the scanner can find them in other files. Use `--no-structured-handoff` to suppress the write if needed.
- **Structured processor size limit.** Files over 256 MiB (or `--max-structured-size`) fall back to the streaming scanner, which replaces raw bytes without document awareness. In practice this only affects large serialized data dumps, not real config files.
- **Deterministic mode caveats.** Identical output requires the same secrets file and the same seed. Changing either produces completely different replacements.
- **Zeroization scope.** Covers secrets, HMAC keys, and mapping-store keys. Incidental copies the Rust compiler creates during optimization passes are not covered — an inherent limitation of safe Rust zeroization.
- **Large archive sequential fallback.** Zip and tar archives whose total uncompressed content exceeds 256 MiB are processed sequentially rather than in parallel to avoid unbounded memory use.
- **Binary detection.** Entries detected as binary are skipped by default. Use `--include-binary` to override.

---

## Security Disclosure

Do not open a public issue for security-sensitive findings. Report privately via a security advisory on the repository or via the maintainer contact in `Cargo.toml`. Include a description, reproduction steps, and potential impact. Maintainers will acknowledge within 5 business days and provide a fix or mitigation timeline within 30 days.

---

## Stability

This project follows [Semantic Versioning](https://semver.org/). As of 0.8.0, the public library API and CLI interface are considered stable. Breaking changes will be avoided but may occur in minor releases until 1.0.0. The MSRV is **1.86** (stable toolchain), declared under `rust-version` in `Cargo.toml` and enforced in CI.

See [CHANGELOG.md](CHANGELOG.md) for release history.

---

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full text.