|
1 | 1 | # redact |
2 | 2 |
|
3 | | -Replaces secrets in text with `[REDACTED]`. |
| 3 | +Hide secrets in text. Keeps the same length, so logs and dumps stay readable. |
| 4 | + |
| 5 | +``` |
| 6 | +$ redact 'curl -H "Authorization: Bearer ghp_abcdefghijklmnopqrstuvwxyz0123456789" api.github.com' |
| 7 | +curl -H "Authorization: Bearer ****************************************" api.github.com |
| 8 | +``` |
4 | 9 |
|
5 | 10 | ## Install |
6 | 11 |
|
| 12 | +Grab a prebuilt binary from the [Actions tab](../../actions) (latest `build` run, under "Artifacts"), or build from source: |
| 13 | + |
| 14 | +``` |
| 15 | +go install github.com/inancgumus/redact/cmd/redact@latest |
| 16 | +``` |
| 17 | + |
| 18 | +## Use the CLI |
| 19 | + |
| 20 | +Pass text as an argument: |
| 21 | + |
| 22 | +``` |
| 23 | +$ redact 'user=alice token=ghp_abcdefghijklmnopqrstuvwxyz0123456789 status=ok' |
| 24 | +user=alice token=**************************************** status=ok |
| 25 | +``` |
| 26 | + |
| 27 | +Or pipe a file: |
| 28 | + |
7 | 29 | ``` |
8 | | -go get github.com/inancgumus/redact |
| 30 | +$ redact < ~/.aws/credentials |
| 31 | +[default] |
| 32 | +aws_access_key_id = ******************** |
| 33 | +aws_secret_access_key = **************************************** |
9 | 34 | ``` |
10 | 35 |
|
11 | | -## Use |
| 36 | +Database URLs keep their structure: |
| 37 | + |
| 38 | +``` |
| 39 | +$ redact 'postgresql://app:s3cretP4ss@db.internal:5432/prod' |
| 40 | +postgresql://app:**********@db.internal:5432/prod |
| 41 | +``` |
| 42 | + |
| 43 | +Run with no input to see the help. |
| 44 | + |
| 45 | +### Flags |
| 46 | + |
| 47 | +| Flag | Default | What it does | |
| 48 | +|------|---------|--------------| |
| 49 | +| `--mask` | `*` | Character used to hide each byte of a secret. | |
| 50 | +| `--min-entropy` | `3.5` | How random a value must look before it counts as a secret. Lower means more aggressive. | |
| 51 | +| `--min-submatch` | `2` | How strong a match must be before unknown secrets get hidden. Higher means more cautious. | |
| 52 | + |
| 53 | +### Examples |
| 54 | + |
| 55 | +Custom mask character: |
| 56 | + |
| 57 | +``` |
| 58 | +$ redact --mask=# 'token: ghp_abcdefghijklmnopqrstuvwxyz0123456789' |
| 59 | +token: ######################################## |
| 60 | +``` |
| 61 | + |
| 62 | +Catch a wider net of unknown-looking values in a config file: |
| 63 | + |
| 64 | +``` |
| 65 | +$ redact --min-entropy=2.0 < config.yaml |
| 66 | +``` |
| 67 | + |
| 68 | +## Use as a Go library |
12 | 69 |
|
13 | 70 | ```go |
| 71 | +import "github.com/inancgumus/redact" |
| 72 | + |
14 | 73 | clean := redact.String(text, redact.DefaultOptions) |
15 | 74 | ``` |
16 | 75 |
|
17 | | -Override defaults via `Options`: |
| 76 | +Tune the defaults: |
18 | 77 |
|
19 | 78 | ```go |
20 | 79 | clean := redact.String(text, redact.Options{ |
21 | | - // Placeholder written in place of each detected secret. |
22 | | - // Default: "[REDACTED]". |
23 | | - Mask: "***", |
24 | | - |
25 | | - // Minimum Shannon entropy (bits/char) a captured value must have |
26 | | - // to be treated as a secret by the generic detector. Lower values |
27 | | - // redact more aggressively; raise it to cut false positives on |
28 | | - // low-entropy strings. Default: 3.5. |
29 | | - MinEntropy: 4.0, |
30 | | - |
31 | | - // Minimum number of regex submatches required before a generic |
32 | | - // match is considered for redaction. Default: 2. |
33 | | - MinSubmatch: 2, |
| 80 | + Mask: '#', // Character repeated for each byte. Default '*'. |
| 81 | + MinEntropy: 4.0, // Raise to cut false positives. Default 3.5. |
| 82 | + MinSubmatch: 2, // Raise to be more cautious on unknown patterns. Default 2. |
34 | 83 | }) |
35 | 84 | ``` |
36 | 85 |
|
37 | | -## Catches |
| 86 | +Any zero field falls back to the default. |
| 87 | + |
| 88 | +## What it catches |
38 | 89 |
|
39 | | -Shape-based catches: |
| 90 | +### Patterns by shape |
40 | 91 |
|
41 | 92 | - PEM private keys |
42 | 93 | - JWTs |
43 | | -- `Bearer` auth headers |
44 | | -- `Basic` auth headers |
| 94 | +- `Bearer` and `Basic` auth headers |
45 | 95 | - URL credentials (`user:pass@host`) |
46 | 96 | - `btoa("user:pass")` calls |
47 | 97 | - `password` / `secret` / `credential` assignments |
48 | | -- Query-string secrets (`?token=...`, `?key=...`, etc.) |
49 | | -- Generic high-entropy values near words like `key`, `secret`, `token` |
| 98 | +- Query-string secrets (`?token=...`, `?key=...`) |
| 99 | +- High-entropy values near words like `key`, `secret`, `token` |
50 | 100 |
|
51 | | -Provider-token catches (prefix-anchored): |
| 101 | +### Provider tokens (prefix-anchored) |
52 | 102 |
|
53 | 103 | - AWS access keys (`AKIA`, `ASIA`, `ABIA`, `ACCA`, `A3T...`) |
54 | 104 | - AWS secret keys (keyword-anchored 40-char base64) |
@@ -159,9 +209,9 @@ Provider-token catches (prefix-anchored): |
159 | 209 | - HashiCorp Terraform (`<id>.atlasv1.<...>`) |
160 | 210 | - Telegram bot token |
161 | 211 |
|
162 | | -## Skips |
| 212 | +### What it skips |
163 | 213 |
|
164 | | -- Variable expansions (`${...}`, `$(...)`) |
| 214 | +- Variable expansions like `${SECRET}` or `$(cat secret)` |
165 | 215 | - UUIDs |
166 | | -- Dotted property access (`apiClient.token`) |
167 | | -- Values containing stopwords (`example`, `test`, `placeholder`, etc.) |
| 216 | +- Dotted property access like `apiClient.token` |
| 217 | +- Values containing words like `example`, `test`, `placeholder` |
0 commit comments