
Using Local LLMs to Detect Secrets in Commit Messages Before Push
Why commit messages deserve the same review as code
I've seen secret leaks start in places nobody expected: a commit message, a release note, or a merge comment. The code was clean, but the message still contained a token fragment, a password hint, or a pasted config value from a debugging session.
That matters because commit messages stick around. They get pushed, mirrored, searched, indexed, and copied into tickets or release logs. If you treat them as “just text,” you miss a real leak path.
Commit messages also have less structure than source code, which makes them harder for simple scanners to interpret. The same phrase can mean an issue ID, a temporary token, or an actual credential. That ambiguity is where a local LLM can help.
What makes secrets in commit messages easy to miss
Regex scanners are good at obvious patterns:
- AWS access key formats
- JWT-shaped strings
- long hex blobs
- private key markers
They are weak at context. A message like rotate prod password from lastpass may not match a secret pattern at all, but it still deserves attention. The opposite happens too: fix issue with key rotation in config can look suspicious even when it is harmless.
The risk is not just accidental disclosure. A commit message can also drag a secret reference into places that are hard to clean up later, especially if it lands in shared branch history.
Local LLMs vs regex scanners for pre-push checks
I would not replace pattern-based scanning with a model. I would layer them.
A local LLM is useful when you want semantic judgment:
- does this message look like it contains a credential?
- is this a pasted secret, a redacted placeholder, or just a ticket number?
- does the message mention a sensitive value in plain language?
Regex still wins on speed and precision for known formats. The model helps with the messy middle.
The key is to keep the model local. Commit messages often show up again in internal trackers, release notes, and chat logs. Sending them to a remote API just to ask “does this look secret-like?” is a bad trade.
A practical pre-push workflow in JavaScript
A pre-push hook can inspect the commits being pushed, score the messages, and block the push when the risk is high.
Reading the commit range safely
Use the ref updates Git passes into a pre-push hook, then ask Git for only the commit subjects and bodies in that range.
// pre-push-check.js
function readStdin() {
return new Promise((resolve) => {
let data = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => (data += chunk));
process.stdin.on("end", () => resolve(data));
});
}
function git(args) {
return execFileSync("git", args, { encoding: "utf8" });
}
async function main() {
const input = await readStdin();
const lines = input.trim().split("\n").filter(Boolean);
for (const line of lines) {
const [localRef, localSha, remoteRef, remoteSha] = line.split(" ");
if (!remoteSha || /^0+$/.test(remoteSha)) continue;
const range = `${remoteSha}..${localSha}`;
const log = git(["log", "--format=%H%x00%s%x00%b%x00", range]);
console.log(log);
}
}
main().catch((err) => {
console.error(err.message);
process.exit(1);
});
That example is intentionally simple. The important part is the range handling: only inspect the commits that are actually going out.
Scoring messages for secret-like content
You can feed each commit subject and body to a local model with a narrow prompt. Ask for a numeric risk score and a short reason, not a free-form essay.
function buildPrompt(message) {
return [
"You are checking a git commit message for possible secret leakage.",
"Return only JSON with keys: score, reason.",
"Score is 0-100 where 100 means likely secret exposure.",
"Treat issue IDs, config names, and normal release text as low risk unless a secret is explicit.",
"Message:",
message,
].join("\n");
}
I like this shape because it cuts down on model wandering. If you ask for prose, you get prose. If you ask for a small JSON object, you can parse it and act on it.
Failing closed when confidence is high
If the model returns a high score, block the push and show the exact commit hash and reason.
function shouldBlock(result) {
return result.score >= 80;
}
function fail(commitHash, reason) {
console.error(`Blocked push: ${commitHash}`);
console.error(`Reason: ${reason}`);
process.exit(1);
}
That is the only sane default for a push gate. If the check is uncertain, warn and continue only if your team explicitly wants that behavior. Otherwise people learn to ignore the hook.
What the model catches and what it misses
False positives from issue IDs and config values
The model will sometimes flag text like:
fix KEY-2381 in deploy configremove secret from example.envrotate token for staging
Those are not automatically leaks. They are just suspicious enough to review. In practice, that is acceptable if the hook gives a short explanation and an override path for verified false alarms.
False negatives from indirect or encoded secrets
A local LLM can miss:
- base64 blobs with no obvious context
- secrets split across multiple lines
- code words that only your team understands
- messages that refer to a secret without naming it directly
That is why I would not rely on it alone. It is a triage layer, not a guarantee.
Hardening the workflow
Combine LLM checks with pattern-based secret scanning
Use both:
- regex or specialized secret scanners for known formats
- a local LLM for semantic review of commit text
The combination catches more real mistakes than either tool alone. One looks for shape. The other looks for meaning.
Keep the model local and the prompt narrow
Local matters for confidentiality. Narrow matters for reliability.
Do not send the entire repo history. Do not ask the model to “find anything suspicious.” Ask it to inspect the commit messages in the push range and return a bounded score. The more open-ended the prompt, the more noise you get and the less likely developers are to trust the result.
A good pre-push check should be fast enough that people leave it on. If it takes too long, it becomes a bypass target instead of a defense.
Conclusion
Commit messages are part of your attack surface. They are easy to overlook, easy to copy around, and hard to clean up after the fact. A local LLM can help catch secret-like text that regex scanners miss, especially when the danger is semantic rather than syntactic.
The useful pattern is simple: inspect only the push range, keep the prompt narrow, combine model scoring with classic secret detection, and fail closed when confidence is high. That gives you a practical safety net without turning every push into a ceremony.


