redactpdf
README.md
# redactpdf
Sharing a PDF usually means trusting that whatever you
blacked out on screen is actually gone. Often it is not:
the text is still sitting in the file, ready to be copied
out. redactpdf takes the opposite approach. It looks for
sensitive text in a local PDF's text layer, writes a copy
in which every matched character has been physically
replaced, and then re-reads its own output to prove that
nothing it was asked to remove can still be extracted.
If the proof fails, you get an error instead of a file.
Four kinds of sensitive text are detected. Social Security
numbers must satisfy the SSA's own validity rules (area
001-899 except 666, group 01-99, serial 0001-9999), so
random digit groups in that shape are not flagged.
Payment-card numbers are digit runs of 13 to 19 digits,
allowing single spaces or dashes between digits, and must
pass the Luhn check. Email addresses and North-American
phone numbers — `(ddd) ddd-dddd`, `ddd-ddd-dddd`,
`ddd.ddd.dddd`, or `ddd ddd dddd`, with an optional `+1`
prefix — round out the built-in categories. On top of
those, `terms` lets you name literal strings of your own,
matched case-insensitively and counted under their own
key. Omit `detect` to run all four categories.
Detection runs per extracted string — the string operands
of `Tj` and `TJ` show operators, in plain or FlateDecode
content streams — so a match that is split across two
string operands is not seen. That is a stated limitation
of the text-layer model this tool uses, not a silent one.
`check_pdf` reports what would be removed without writing
anything. `redact_pdf` writes the redacted copy — the
input file is never modified — replacing each matched
character with `#` while preserving string lengths, then
verifies the result. Documents run to 10 pages free; past
that, both tools ask for a licence. The licence check in
this build is a format check — a token of 16 to 512
characters from `[A-Za-z0-9_-]` plus `.` separators counts
as well-formed, supplied via the `license` argument or
the `BLACKOUT_LICENSE` environment variable — and the
over-limit error distinguishes a missing licence
(`LICENSE_REQUIRED`) from a malformed one
(`LICENSE_INVALID`). Encrypted PDFs, PDFs built on xref
or object streams, and malformed files are reported as
unsupported rather than passed over in silence.
Both tools answer with a one-line summary, a blank line,
and a fenced JSON block carrying `path`, `outputPath`
(null for `check_pdf`), `pages`, `counts`,
`extractableChars` — for `check_pdf`, the characters that
fall inside a detected match in the input; for a
successful `redact_pdf`, 0 by construction — and
`licenseState`.
## CLI
```bash
npx @reneturcios/redactpdf check_pdf '{"path":"/abs/path/report.pdf"}'
npx @reneturcios/redactpdf redact_pdf '{"path":"/abs/path/report.pdf","terms":["falcon"]}'
npx @reneturcios/redactpdf --version
```
`redact_pdf` writes `<input>-redacted.pdf` (the input
path with its `.pdf` suffix replaced) unless
`output_path` says otherwise; the output may not be the
input itself, and an existing output is only replaced
when `overwrite` is true.
Exit codes: `0` on success, `1` on a runtime error (the
tool's error text is printed), `2` on a usage error.
## MCP server
```bash
npx -y @reneturcios/redactpdf mcp
```
MCP client config:
```json
{
"mcpServers": {
"redactpdf": {
"command": "npx",
"args": ["-y", "@reneturcios/redactpdf", "mcp"]
}
}
}
```
### Tools
- `check_pdf` — Report what would be redacted from a
local PDF — per-category match counts — without writing
anything.
- `path` (string, required) — absolute path to a PDF
on this machine
- `detect` (string array, optional) — any of `ssn`,
`card`, `email`, `phone`; omit for all
- `terms` (string array, optional) — literal strings
to match, case-insensitive
- `redact_pdf` — Write a redacted copy of a local PDF
with detected sensitive text permanently removed and
verified gone. Never modifies the input.
- `path` (string, required) — absolute path to a PDF
on this machine
- `output_path` (string, optional) — defaults to
`<input>-redacted.pdf`; must not be the input
- `detect` (string array, optional) — any of `ssn`,
`card`, `email`, `phone`; omit for all
- `terms` (string array, optional) — literal strings
to match, case-insensitive
- `overwrite` (boolean, optional, default false) —
allow replacing an existing output
- `license` (string, optional) — Pro licence token;
falls back to `BLACKOUT_LICENSE`
## Docker
```bash
docker run -i ghcr.io/cloudcorprecords/redactpdf
docker run --rm -v "$PWD:/w" ghcr.io/cloudcorprecords/redactpdf check_pdf '{"path":"/w/report.pdf"}'
```
## Development
```bash
npm install
npm run build # tsc -> dist/
npm test # tsx --test test/*.test.ts (pure local, no network)
npm run dev # run the CLI from source
```
## License
MIT — Copyright (c) 2026 Rene Turcios
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues