How to sanitize runbook markdown before sharing
- Step 1Open the redactor — Go to /markdown-tools/md-secret-redactor and paste the runbook or drop the single
.mdfile (one document per run). - Step 2Pick scope for runbook shape — Most runbook secrets sit in fenced command blocks, so leave
scanAlloff. If your runbook narrates credentials in prose steps, enablescanAllto scan those too. - Step 3Run the sanitize pass — The five patterns apply in order. A
password = ...step becomespassword=[REDACTED]; anAKIA...becomes[REDACTED_AWS_KEY]; a pasted PEM key becomes[REDACTED_PRIVATE_KEY]. - Step 4Manually sweep for unmatched secrets — Search for connection strings (
postgres://user:pass@host), Slack/webhook URLs, and bare provider keys (xoxb-,sk_live_) — none are caught unless keyword-prefixed. - Step 5Confirm structure is intact — Diff the redacted runbook against the original (try md-diff) to confirm only secret values changed and no step was altered.
- Step 6Rotate exposed credentials, then distribute — Any real password that sat in a shared runbook should be rotated. Distribute the sanitized copy to the new rotation or partner.
What the redactor actually detects
The five regex patterns the redactor applies, in the exact order it applies them, taken from lib/markdown/markdown-engine.ts. There are no other patterns — anything not matched here is left untouched.
| Pattern (what it matches) | Example that matches | Replaced with | Order |
|---|---|---|---|
AWS access key id: AKIA + 16 uppercase letters/digits (case-sensitive) | AKIAIOSFODNN7EXAMPLE | [REDACTED_AWS_KEY] | 1 |
Keyword assignment: api_key, api-key, apikey, token, secret, password, passwd, pwd, authorization followed by =/:/space, then an 8+ char value | api_key = abcd12345678 | <keyword>=[REDACTED] (separator normalized to =) | 2 |
Bearer + an 8+ char token | Bearer eyJhbGci... | Bearer [REDACTED] | 3 |
Three-segment JWT: eyJ + 10+ chars, dot, 10+ chars, dot, 10+ chars | eyJhbGci....eyJzdWIi....SflKxw... | [REDACTED_JWT] | 4 |
PEM private-key block: -----BEGIN ... KEY----- ... -----END ... KEY----- | an RSA/EC/OPENSSH key block | [REDACTED_PRIVATE_KEY] | 5 |
Scope: which parts of the document are scanned
The single scanAll option controls scope. Default (off) restricts redaction to fenced `` code blocks only; on scans the entire document. Inline backtick` spans and 4-space indented code are treated as prose, not code blocks.
| Document region | scanAll: false (default) | scanAll: true |
|---|---|---|
| Fenced ``` code block | Scanned | Scanned |
| Prose / paragraph text | Left untouched | Scanned |
Inline backtick code span | Left untouched (counts as prose) | Scanned |
| 4-space indented code block | Left untouched (only ``` fences count) | Scanned |
| Headings, tables, blockquotes | Left untouched | Scanned |
Common runbook secrets and whether the tool catches them
Mapped to the five engine patterns. "Keyword only" means it is redacted only when a recognized keyword (password/token/secret/authorization/api_key) immediately precedes it.
| Runbook secret | Detected? | Notes |
|---|---|---|
password = hunter2longpass | Yes | Matches the keyword pattern; value 8+ chars. |
AKIAIOSFODNN7EXAMPLE | Yes | AWS access-key id pattern, uppercase only. |
Authorization: Bearer <jwt> | Yes | Bearer pattern fires before the JWT pattern. |
Connection string postgres://u:p@host | No | No URL/credential pattern; sweep manually. |
Slack webhook https://hooks.slack.com/... | No | No webhook pattern. |
| Bare base64 secret (no keyword) | Keyword only | Caught only after secret=/password= etc. |
| PEM private key block | Yes | BEGIN/END KEY pattern. |
Cookbook
Sanitization runs against real runbook snippets, verified against the engine. PII anonymized.
Database recovery step with a pasted password
The keyword pattern catches PGPASSWORD because it contains the password keyword, normalizing the separator to =.
Input: ```bash export PGPASSWORD = R3cov3ryPass99 psql -h db.internal -U admin restore.sql ``` Output: ```bash export PGPASSWORD=[REDACTED] psql -h db.internal -U admin restore.sql ```
Connection string is NOT redacted
A credential embedded in a URL has no matching pattern. This is the single most common runbook miss — sweep for ://user:pass@ by hand.
Input: ```bash psql postgres://admin:S3cretPass@db.internal:5432/prod ``` Output (unchanged): ```bash psql postgres://admin:S3cretPass@db.internal:5432/prod ```
Emergency token in a prose step
A recovery step that names a token in a sentence needs scanAll, since default scope is fenced blocks only.
Input: Step 4: call the API with `token = brk-glass-9988xyz` to force a failover. scanAll: false → unchanged scanAll: true → Step 4: call the API with `token=[REDACTED]` to force a failover.
PEM key pasted into an access section
An SSH key someone dropped into the runbook collapses to one placeholder, regardless of key type.
Input: ``` -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA7Yn... -----END RSA PRIVATE KEY----- ``` Output: ``` [REDACTED_PRIVATE_KEY] ```
AWS key id but secret key survives
The 40-char secret access key on the next line has no keyword, so it is not redacted. Add a keyword or sweep manually.
Input: ```ini AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY ``` Output: ```ini AWS_ACCESS_KEY_ID=[REDACTED_AWS_KEY] wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY ```
Edge cases and what actually happens
Credential inside a connection string / URL
Not detectedNo URL pattern exists. postgres://user:pass@host and webhook URLs survive — the top runbook miss. Sweep for :// credentials manually.
Secret in a prose step, scanAll off
PreservedBy design, only fenced code blocks are scanned by default. Enable scanAll for narrated credentials.
Bare AWS secret access key
Not detectedOnly the AKIA id is matched. A 40-char secret key is caught only when keyword-prefixed.
Slack / Stripe / GitHub provider key
Not detectedNo provider-prefix patterns. Redacted only when a keyword precedes them.
Quoted password leaves a stray quote
By designpassword: "x" becomes password=[REDACTED]". Cosmetic; the secret is still removed.
Short value after a keyword
PreservedValues under 8 chars are not redacted (pwd=abc), assumed to be placeholders.
Structure accidentally changed
ExpectedOnly matched values change; headings/steps/fences are preserved. Diff with md-diff to confirm.
Runbook over Free tier limits
RejectedFree is 1 MB / 500,000 chars / 1 file. Split big runbooks with md-splitter or upgrade.
Old credential still valid after redaction
Action requiredRedacting the doc does not invalidate a credential. Rotate anything that was shared in the runbook.
Frequently asked questions
Will it preserve my runbook structure?
Yes. Only matched secret values are replaced; headings, numbered steps, tables, and code fences stay intact.
Does it catch passwords inside connection strings?
No. There is no URL pattern. postgres://user:pass@host is not redacted — sweep for :// credentials by hand.
What does it detect?
AWS AKIA ids, keyword assignments (password/token/secret/passwd/pwd/api_key/authorization + 8+ char value), Bearer tokens, three-segment JWTs, and PEM key blocks.
Does it scan prose steps?
Only if you enable scanAll. By default it scans fenced ``` code blocks, where runbook commands usually live.
Is the runbook uploaded for scanning?
No. Everything runs in your browser, so a runbook with live credentials stays local.
Can I sanitize several runbooks at once?
No. acceptsMultiple is false — one document per run.
How do I find credentials it can't catch?
Manually search for :// connection strings, webhook URLs, and provider prefixes. Pair with md-lint for general hygiene.
Will it redact short placeholder values?
No. The keyword pattern needs an 8+ char value, so pwd=abc is left as-is.
Does it change the separator?
Yes. Keyword matches normalize : or space to =, e.g. password: x becomes password=[REDACTED].
Should I rotate redacted credentials?
Yes. A password that sat in a shared runbook is compromised; rotate it regardless of redaction.
How do I confirm nothing else changed?
Diff the before/after with md-diff to verify only secret values were touched.
Can I add custom secret patterns?
No. The only control is the scanAll boolean — there is no custom-pattern or preset option.
Privacy first
All Markdown processing runs locally in your browser using JavaScript. No file is ever uploaded to JAD Apps servers — only metadata counters are saved for signed-in dashboard stats.