Never Commit a Secret to Your Knowledge Base

Never commit a password to your knowledge base — and if you already have, scrubbing it from the working tree isn't enough. Here's the store-pointers-not-values rule and the Git history purge.

On this page
  1. Why a knowledge base is uniquely bad for secrets
  2. The right pattern: pointers, not values
  3. Prevention: a guard that refuses the commit
  4. ⚠️ Recovery: you already committed a secret
  5. The swap-in box
  6. What you have now

You’re building a knowledge base that documents your whole lab. The temptation to paste “the Proxmox token, so it’s handy” into a note is enormous. Resist it completely, from your very first commit — because a knowledge base is the worst place to keep a secret. This is a ⚠️-flagged post in the series for a reason: this is a mistake that’s genuinely painful to undo.

The rule

Never commit a secret value to your vault. Store the value in a purpose-built secret store; put only a pointer — the variable name and its location — in your notes. Git never forgets, so a secret committed once is a secret you must rotate.

First: make these values your own

The examples use stand-ins — replace them with your own. /root/.myofficelab-secrets → wherever you keep your secret store; PROXMOX_API_TOKEN_ID/_SECRET → your actual variable names; CHANGE_ME and the-leaked-secret-value → your real values (which live in the store, never in a note). Never paste a real secret into an example just because a placeholder is sitting there.


Why a knowledge base is uniquely bad for secrets

The whole point of the vault is that it gets copied and projected everywhere. That’s a feature for knowledge and a catastrophe for credentials. This isn’t a niche concern — hard-coded credentials are catalogued as CWE-798 by MITRE, and OWASP’s Secrets Management Cheat Sheet gives the same guidance this post does: keep secrets out of source and reference them from a dedicated store. Trace where a single note travels:

One committed secret → five exposure surfacesnote.md (secret)Git historylaptop mountbackup mirrorpublished wikisearch indexbuildahomelab.dev

Delete the secret from the note tomorrow and it still lives in Git history, in last night’s backup, and possibly in a search index that already embedded it. One paste, five places to clean.


The right pattern: pointers, not values

Keep credentials in a purpose-built store and reference them by name. In my lab the store is a single root-only file on one host, and every note that needs a credential names the variable, never the value.

The secret store (root-only, never in Git)

# /root/.myofficelab-secrets  —  chmod 600, outside any repo
PROXMOX_API_TOKEN_ID=youruser@pve!app-token
PROXMOX_API_TOKEN_SECRET=CHANGE_ME
GRAFANA_API_KEY=CHANGE_ME
What actually goes in the note

## Proxmox API access

Token lives as PROXMOX_API_TOKEN_ID / PROXMOX_API_TOKEN_SECRET
in /root/.myofficelab-secrets on the ops host. PVEAdmin role.
Do not paste the secret here.
For application credentials, go one better

If an app reads the vault-documented service, store its secret in an OS keystore (libsecret / GNOME Keyring on Linux, Keychain on macOS) or a dedicated manager like Vaultwarden. The note still only records the variable name and which store holds it.

Value in the note vs. pointer to the storenote.md — the trap## Proxmox APItoken: aG7x9-K2mP-Qw…↑ now in Git foreverleaks to every copynote.md — the pattern## Proxmox APIPROXMOX_API_TOKEN inthe secret storesafe to mirror + publishthe value lives in a purpose-built store; the note only names it

Legitimate access resolves the pointer in seconds; the note stays safe to mount, mirror, publish, and index.


Prevention: a guard that refuses the commit

Make it hard to commit a secret by accident. A pre-commit hook that greps staged content for obvious secret shapes turns a silent mistake into a blocked commit.

1Add a secret-scanning pre-commit hook5 min
Create .git/hooks/pre-commit in the vault

cat > .git/hooks/pre-commit <<'EOF'
#!/bin/bash
# Block commits that look like they contain secrets.
if git diff --cached -U0 | grep -nEi \
'(api[_-]?key|token|secret|password|passwd|BEGIN [A-Z ]*PRIVATE KEY)[[:space:]]*[:=]' \
| grep -v 'CHANGE_ME'; then
echo "✋ Possible secret in staged changes — commit blocked."
echo "   Store the value in your secret store; commit only a variable-name pointer."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
A hook is a seatbelt, not a vault

This catches the obvious mistakes — an assignment that looks like a credential. It won’t catch a bare high-entropy string with no label. Use a dedicated scanner like gitleaks for real coverage; the hook is your cheap first line.


⚠️ Recovery: you already committed a secret

If a secret is already in your history, deleting it in a new commit is not enough — it’s still one command away in the history. You must rewrite history and rotate the credential. GitHub’s own guidance on removing sensitive data says exactly this: purge every commit and treat the credential as compromised.

1Rotate the credential firstvaries

Before touching Git, invalidate the exposed secret at its source — regenerate the API token, change the password. Assume it leaked. This is the step people skip and regret.

2Purge it from all history with git-filter-repo10 min

git-filter-repo is the modern, recommended tool for rewriting history:

On a fresh clone of the vault

# Install (Debian/Ubuntu): sudo apt-get install -y git-filter-repo
# Replace every occurrence of the leaked value across ALL commits
echo 'the-leaked-secret-value==>REDACTED' > /tmp/replacements.txt
git filter-repo --replace-text /tmp/replacements.txt

To remove an entire file that should never have been tracked:

Purge a whole file from history

git filter-repo --path secrets.env --invert-paths
3Force-update the canonical repo and re-clone everywhere10 min
Push the rewritten history

git push --force --all
git push --force --tags

Every existing clone and mount now has divergent history — re-clone them fresh. Anyone who pulled the bad history must discard their copy. This blast radius is exactly why prevention beats recovery.

The full copy-paste recovery sequence is in the paired playbook: Vault Secret Store + History Scrub.


The swap-in box

On other tools

GitHub/GitLab hosted: enable native push protection / secret scanning — it blocks known secret formats at push time. Any Git host: gitleaks as a pre-commit hook or CI step is tool-agnostic. Secret store choice: a root-only file is the minimum; HashiCorp Vault, Bitwarden/Vaultwarden, or your OS keystore are all valid — the note-side rule (pointer, never value) is identical regardless.


What you have now

A vault that is safe to copy, mirror, publish, and index — because the one class of content that must never spread is structurally kept out of it. With the substrate solid and secure, the next post makes the vault smart: turning a flat pile of pages into a connected graph, modeling knowledge as a brain.


Related posts:

Comments

Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.