Check the staged diff for credentials before push commits

`push` runs `git add --all .`, so anything lying in the project gets
committed, and with `mirror = true` it reaches a public server in the
same breath. It is the one action in mgsh that cannot be undone: a
deleted server repository comes back from an archive, a published
credential does not.

The staged diff is now scanned before the commit is made -- private keys,
GitHub/GitLab/Slack/AWS/PyPI tokens, and credential-shaped assignments --
and a hit is shown with file and line before asking whether to continue.
Declining leaves the changes staged but uncommitted, so removing the file
and adding a .gitignore entry is all it takes.

The hard part is not detection but silence. A scanner that cries wolf
gets answered with a reflexive "y" and stops being a safety net, so
values that are plainly environment references, dotted identifiers,
constant names, template slots or masked stand-ins are filtered out. A
test scans mgsh's own README and mgshrc.example -- both full of
credential-shaped text -- and fails if either would trip the check. It
caught the documentation for this very feature, which is why the README
describes the sample output instead of reproducing it.

For a line that legitimately looks like a credential there is
`mgsh:allow`, which suppresses that one line; `secretscan = off` turns
the check off entirely. Only an explicit "off" does that -- a typo in the
setting leaves the safety net in place, which is what the new falsy()
is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 16:17:30 +02:00
co-authored by Claude Opus 5
parent 915ef1783a
commit 59da8f376c
7 changed files with 447 additions and 2 deletions
+2 -1
View File
@@ -44,6 +44,7 @@ func showConfig() {
{"pushdefault", cfg.PushDefault},
{"editor", cfg.Editor},
{"mirror", cfg.Mirror},
{"secretscan", cfg.SecretScan},
{"remotes", cfg.RemoteNames},
}
@@ -147,7 +148,7 @@ func configKeys() []string {
"base", "githost", "gitport", "gituser", "gitpath", "gitkey",
"gitname", "gitemail", "pushdefault", "editor",
"remoteurl", "remotekey", "remotetype", "remotevisibility",
"remotes", "mirror",
"remotes", "mirror", "secretscan",
}
sort.Strings(keys)
return keys