Skip to content

fix(secrets): stop the editor keeping a decrypted copy of the secrets - #41

Merged
Gerrrt merged 1 commit into
mainfrom
fix/secrets-edit-editor-persistence
Aug 20, 2026
Merged

fix(secrets): stop the editor keeping a decrypted copy of the secrets#41
Gerrrt merged 1 commit into
mainfrom
fix/secrets-edit-editor-persistence

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Found while working #11.

The leak

make secrets-edit ran sops directly. sops decrypts to a temp file and opens $EDITOR on it — and a vim or neovim with undofile set then writes that buffer, the decrypted secrets, into a permanent undodir. sops shreds its own temp file on exit. Nothing shreds the undo file.

Three of them were sitting on the monitoring host:

~/.local/state/nvim/undodir/%tmp%<pid>%observability.sops.yaml   mode 664

holding the live SNMP community strings for pfSense, the APC NMC and iLO, in plaintext, on an unencrypted disk, since the last make secrets-edit. Confirmed by matching the live values from sops -d against the files inside a shell variable — never printed. Shredded, and recorded in docs/security.md's historical exposure table.

Not rotated. The exposure never left the host and never reached git, and rotating SNMP communities means revisiting four devices — including the switch that SECURITY.md already records as refusing to drop its old community.

The fix

scripts/secrets-edit.sh hardens the editor before sops hands it plaintext:

-n                     no swap file
-i NONE                no viminfo / shada
-c 'set noundofile …'  after the user's config has loaded, so it overrides it

Measured rather than assumed — sops joins $EDITOR with the temp path and runs the result through sh -c, so flags and quoting survive:

$1 = -n   $2 = -i   $3 = NONE
$4 = -c   $5 = set noundofile nobackup nowritebackup
$6 = /tmp/895887260/observability.sops.yaml

A baseline headless edit reproduces the undo file; the hardened one produces nothing. Non-vim editors get a warning naming what to check, rather than a false claim of having been hardened.

The same bug was in the runbook

docs/runbooks/back-up-the-age-key.md told you to transcribe a paper backup with vi /dev/shm/restore-test.txt — writing the private key into a persistent undo file that the very next line's shred -u does not remove. The shipped cleanup step gave false assurance.

Both verification flows now use cat >, with the reason stated. Also in that file:

  • a password-manager verification flow, since that is the destination the runbook recommends and it only documented paper
  • §3 no longer lumps end-to-end-encrypted vaults in with plaintext cloud sync — it contradicted the recommendation table two sections earlier. Adds the circular-backup case: vault recovery material that exists only on the host you are insuring

Incidental

sops exits 200 when the editor quits unchanged, which surfaced as make: *** [secrets-edit] Error 200 and read like a broken target. Mapped to success; every other status passes through untouched.

secrets/README.md claimed "the plaintext never lands on disk unencrypted". That was not true until this commit.

Verification

  • shellcheck scripts/*.sh clean; markdownlint-cli2 0 issues; make validate all checks passed
  • gitleaks v8.24.0 detect --no-git — no leaks found
  • make secrets-edit end-to-end against the real secrets file, editor receiving the hardening flags, exit 0 on no-op
  • undodir empty before and after a hardened edit

Refs #11

🤖 Generated with Claude Code

`make secrets-edit` ran `sops` directly. sops decrypts to a temp file and
opens $EDITOR on it, and a vim or neovim with `undofile` set then writes that
buffer — the decrypted secrets — into a permanent undodir. sops shreds its own
temp file on exit; nothing shreds the undo file.

Found in the wild on the monitoring host: three files in
~/.local/state/nvim/undodir/ named %tmp%<pid>%observability.sops.yaml, mode
664, on an unencrypted disk, holding the live SNMP community strings for
pfSense, the APC NMC and iLO. They had been there since the last
`make secrets-edit` and nothing in the repo knew. Shredded, and recorded in
docs/security.md's historical exposure table. Not rotated: the exposure never
left the host and never reached git.

scripts/secrets-edit.sh now silences the editor before sops hands it plaintext
— no undo file, no swap file, no backup, no viminfo/shada — for the vim family,
and warns for anything else rather than pretending to have hardened it.
Measured: sops joins $EDITOR and the temp path and runs them through `sh -c`,
so the flags survive; a baseline edit reproduces the undo file and a hardened
one does not.

The same mechanism was in the age-key runbook, which told you to transcribe a
paper backup with `vi /dev/shm/restore-test.txt` — writing the *private key*
to a persistent undo file that the following `shred -u` does not remove. Both
verification flows now use `cat >`, and the runbook says why. It also gains a
password-manager flow, since that is the destination it recommends, and stops
lumping end-to-end-encrypted vaults in with plaintext cloud sync, which
contradicted its own recommendation two sections earlier.

Incidental, in the same target: sops exits 200 when the editor quits without
changing anything, which surfaced as `make: *** [secrets-edit] Error 200` and
read like a broken target. Mapped to success; every other status passes through.

Refs #11
@Gerrrt
Gerrrt merged commit 57a3d3e into main Aug 20, 2026
3 checks passed
@Gerrrt
Gerrrt deleted the fix/secrets-edit-editor-persistence branch August 20, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant