redactkit

Redact secrets out of a log and keep the log readable.

The whole point of the tool, end to end, captured from a real run. Green is your machine. Blue and dashed is everywhere else — a ticket, a chat, a vendor, a model. Only the middle panel ever crosses that line.
  1. On your machine

    Your log, with the live credentials still in it.

    09:14:02 api INFO  auth ok token=ghp_a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5
    09:14:03 api ERROR upstream 500 url=https://svc:hunter2@db.internal/app
    09:14:09 api INFO  retry with token=ghp_a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5
    09:14:11 api WARN  aws AKIAIOSFODNN7EXAMPLE denied

    $ redactkit app.log -m keys.json -o safe.log

  2. ↓ leaves your machine — anywhere else

    The same log, still a log. The same secret gets the same tag wherever it appears; a different secret gets a different tag; the timestamps are untouched.

    09:14:02 api INFO  auth ok token=[GITHUB_TOKEN_1]
    09:14:03 api ERROR upstream 500 url=https://svc:[PASSWORD_1]@db.internal/app
    09:14:09 api INFO  retry with token=[GITHUB_TOKEN_1]
    09:14:11 api WARN  aws [AWS_KEY_1] denied
  3. ↵ back on your machine

    They answer quoting a tag. You turn it back into the real thing, from a key map that never left your disk.

    $ cat reply.txt
    whatever [GITHUB_TOKEN_1] is, it expired yesterday
    $ redactkit --restore -m keys.json < reply.txt
    whatever ghp_a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5a1b2c3d4e5 is, it expired yesterday
    redactkit: restored 1 placeholder

The first and third lines of that log used the same credential, and you can still see that they did. The second used a different one, and you can still see that too. That is the difference between a redacted log and a log full of the word REDACTED: one of them is still worth reading.

A command-line tool. It reads a pipe or a pile of files, writes a version you can paste anywhere, and — this is the part that matters — lets you turn the replies back into the real thing on your own machine. No network calls, no telemetry, no dependencies.

The problem with every other way of doing this

A secret scanner tells you there is a secret at line 42. That is a different job: it finds the leak, and then hands the log back to you exactly as it was. You still cannot share it.

sed 's/ghp_[A-Za-z0-9]*/REDACTED/g' hands you back a log, but every secret becomes the same word. A trace with three different tokens in it collapses into noise, and you can no longer tell which request was which — which is usually the exact thing you were trying to show someone.

The same log, put through both. Neither block is retyped: the one-liner really was run over the file, and so was the tool.
$ sed 's/ghp_[A-Za-z0-9]*/REDACTED/g'
09:14:02 api INFO  auth ok token=REDACTED
09:14:03 api ERROR upstream 500 url=https://svc:hunter2@db.internal/app
09:14:09 api INFO  retry with token=REDACTED
09:14:11 api WARN  aws AKIAIOSFODNN7EXAMPLE denied
Both tokens became the same word, so the trace no longer tells you which request was which. And the password and the cloud key are still sitting there in the clear, because the pattern was never told to look for them.
$ redactkit app.log
09:14:02 api INFO  auth ok token=[GITHUB_TOKEN_1]
09:14:03 api ERROR upstream 500 url=https://svc:[PASSWORD_1]@db.internal/app
09:14:09 api INFO  retry with token=[GITHUB_TOKEN_1]
09:14:11 api WARN  aws [AWS_KEY_1] denied
Every credential is gone, each one has a tag you can talk about, and the repeat is still visibly a repeat.

The tags in the figures above stay stable across every file in a run, so a token in api.log and the same token in worker.log are still recognisably one token. And the timestamps came through untouched, which sounds like a low bar and is not: a redactor that mangles 09:14:02 destroys the only thing that makes a log a log. That case is one of the regression tests.

The round trip

Turning the replies back into real values is the feature the browser tool cannot have, and the reason redactkit exists as a separate thing. It works because the run leaves a key map behind on your disk, and the tool is loud about what that file is.

The same run again, this time watching what it says to you and what it leaves on disk. Nothing here is retyped: it is the tool's own output and the file mode the operating system reports.
$ redactkit app.log -m keys.json -o safe.log
redactkit: wrote keys.json (mode 600) -- it holds the real secrets, keep it local
redactkit: 4 replacements, 3 distinct values
  GITHUB_TOKEN x2, AWS_KEY x1, PASSWORD x1

$ stat -c %A keys.json
-rw-------

The summary goes to standard error, so piping the tool into something else still gets you a clean log and still tells you what happened. The key map is written readable by you alone, and the tool says out loud, every single time, that the file holds the real secrets.

keys.json holds the original secrets in the clear. redactkit writes it mode 0600 and says so on stderr every single time. It never leaves your machine, because redactkit makes no network calls at all — there is nowhere for it to go.

So you can hand safe.log to your colleague, your vendor, your issue tracker or your language model, let them answer in terms of the tags, and still be the only person who can turn a tag back into a credential.

Free or paid

The browser tool stays free forever, and it is not a crippled demo: it is the same detectors, and it always will be, because they are generated from the same source. What you pay for is everything you cannot do by pasting into a text box.

 Browser toolredactkit
The full detector tableYesYes
Runs with nothing uploadedYesYes
Numbered placeholdersYesYes
Pipe it: npm test 2>&1 | redactkitNoYes
Many files, one stable numberingNoYes
Reversible key map, kept localNoYes
Exit code for CI and pre-commitNoYes
JSON output for your own scriptsNoYes
Per-project policy fileNoYes
Source you can read and changeView source~400 lines, no deps

This used to be a paid product. It is free now.

For most of this project's life there was a price on this page — $29 for one developer, $99 for a team — sitting above an order form that asked you to email me so I could quote you a wallet transfer by hand. Not one person ever did. That is a measurement, not a complaint: an offer you cannot complete in a minute, from a seller you have never heard of, is not an offer. Leaving it up once two real checkouts existed elsewhere on this site was the part that was actually wrong, so it is gone.

redactkit is free now, MIT licensed, and whole — the same build the figures above were captured from, not a trimmed demo. Take it and never come back.

Download redactkit → No dependencies, Node 18 or newer. No account, no email, nothing to accept.

No licence server, no activation, no phone-home — there never was one, and now there is nothing for one to check. You may read every line, change anything, vendor it into your own repo, and keep using it if this project stops existing, which it might.

If you want the engine as a dependency rather than a command, npm install logscrub is the same detectors, generated from the same source file.

What is for sale here, and why it is not this

Two things, both with a checkout you can finish without ever talking to me: the scanner regression suite, which is a subscription to the upkeep of the two corpora that measure tools like this one, and the starter kit, which is the harness this whole project runs on. What I sell is maintenance and the record behind it. A one-off download was never the right thing to charge for, and pretending otherwise cost me nothing except the credibility of this page.

Your log is not always UTF-8

The browser tool takes what you paste, and a clipboard only ever holds text something else already decoded. A command-line tool reads files, which is a harder job: a log PowerShell wrote with > is UTF-16, a log from an older Windows or Japanese system is in a legacy encoding, and a dump someone handed you may not be text at all. Read every one of those as UTF-8 and the result looks like success in the worst way — either nothing matches and the file reports clean, or the credentials are found correctly while the log around them is quietly replaced with question marks.

So redactkit reads the bytes first and asks what they are, using the same sniffer the browser tool uses, generated from the same page.

A log written by PowerShell, a compressed dump, and a log written in a legacy encoding — each handed to the tool exactly as it sits on disk. Every line below came out of a real run against those bytes.
$ xxd ps.log | head -1
00000000: fffe 3000 3900 3a00 3100 3400 3a00 3000  ..0.9.:.1.4.:.0.

That is the trap. Read as UTF-8, every character in this log is separated from the next by a zero byte, so nothing matches a credential pattern and the file reports clean while the credential sits in it in the clear.

  1. decoded, not skipped
    $ redactkit ps.log
    redactkit: ps.log: decoded as UTF-16 LE; output is UTF-8
    redactkit: 1 replacement, 1 distinct value
      GITHUB_TOKEN x1
    09:14:02 deploy INFO  starting release
    09:14:04 deploy INFO  token=[GITHUB_TOKEN_1]
  2. refused, not scanned
    $ redactkit dump.gz
    redactkit: dump.gz: This is gzip, not text. A pattern scanner reads nothing useful inside it, so a clean result would be meaningless. Decompress or export it to text first.
      If you know what it really is, say so: --encoding LABEL
    $ echo $?
    2
  3. refused, then read
    $ redactkit old.log
    redactkit: old.log is not valid UTF-8, so it was written in a legacy encoding (11 of 64 bytes are outside ASCII).
    $ redactkit --encoding windows-1251 old.log
    Ошибка входа token=[GITHUB_TOKEN_1]

Refusing is the right default because this tool writes files. Reading legacy bytes as UTF-8 finds the credential correctly and destroys the log around it in the same pass, and you would only discover that after saving the result over the original. An error message costs you a minute; a lossy read costs you the log.

What it does not do

Read this part before you rely on it. The tool is worth more to you if you know where it stops — so here is where it stops, shown rather than promised.

A log written on purpose to contain the hard cases, run through the shipping tool, with its real output line by line. Two of these lines are the tool failing you, and they are on this page because you should know which two before you rely on it.

caught left alone on purpose went through, wholly or in part. Every one of these rows is re-run and re-checked whenever this page is built, so if a change to the detectors made one of them stale, the build fails instead of the page lying.

The same limits in words, because a figure is not searchable and a reader should be able to find them in the README too:

When the secret is inside something else

Every rule above reads the text in front of it. So a credential that arrives base64 encoded — a Kubernetes secret echoed into a deploy log, a JWT payload, a config blob — matches nothing, and the tool used to print nothing was replaced over it and exit clean. In a pre-commit hook that is not a missed warning; it is the hook letting the commit through.

redactkit now decodes each encoded run it did not already redact, re-scans it with the same rules, and tells you by line what is inside. It never rewrites the blob.

One deployment log with a base64 payload in it, handed to the shipping tool twice. The only difference between the two runs is whether the second look is on. Both transcripts, and both exit codes, came out of a real run while this page was built.
  1. what every scanner sees
    $ redactkit --check --no-review wrapped.log
    redactkit: nothing was replaced; 31 of 34 rules ran. That does not mean the text is clean -- read it yourself before you share it.
      not looked for: phone, uuid, hexblob
    $ echo $?
    0
  2. what redactkit says now
    $ redactkit --check wrapped.log
    redactkit: nothing was replaced; 31 of 34 rules ran. That does not mean the text is clean -- read it yourself before you share it.
      not looked for: phone, uuid, hexblob
    redactkit: SECOND LOOK -- 1 thing the redactor could not act on. Read it before you share this.
      line 2  a base64 run decodes to a GITHUB_TOKEN  (detector: GitHub tokens)
          R0lUSFVCX1RPS0VOPWdocF9lNGY1...
      The blob itself was left alone on purpose: it may be a certificate or a config you need whole.
    $ echo $?
    1

The blob is left in the output untouched, on purpose — it may be a certificate or a config you need whole, and a tool that guesses at rewriting encoded data will eventually corrupt something you cannot get back. What changes is that you are told, by line, and that the exit code stops a commit instead of waving it through.

Only decoded runs count by default. The other thing a redactor can tell you — the long random strings and UUIDs it decided not to touch — is behind --review-all, because nearly every real log has a build hash or a request id in it and gating a commit on those would make the hook useless. The precision a rule needs is set by what happens when it fires, not by how good the rule is.

The free tool publishes the same limits, and they are pinned by tests that fail on purpose if a code change quietly makes one of them stale. That is the actual reason to trust either of them: not that I say they are careful, but that the claims are checked by something that can go red.

What you get