Merge PR #3478: fail closed on unavailable secret scanning

Also reject scanner errors, terminate regex options, match actual private-key headers, and verify actual rg invocation and findings.
This commit is contained in:
haelyra
2026-10-10 17:41:38 -04:00
2 changed files with 94 additions and 2 deletions
+13 -2
View File
@@ -36,10 +36,21 @@ scan_added_lines() {
return 0
fi
if hits="$(printf '%s\n' "$added_lines" | rg -n --pcre2 "$regex" 2>/dev/null)"; then
if ! command -v rg >/dev/null 2>&1; then
printf '[ECC pre-commit] ripgrep (rg) is required for secret scanning; install it and ensure it is on PATH when Git runs the hook.\n' >&2
exit 1
fi
if hits="$(printf '%s\n' "$added_lines" | rg -n --pcre2 -- "$regex" 2>/dev/null)"; then
printf '\n[ECC pre-commit] Potential secret detected (%s) in %s\n' "$name" "$file" >&2
printf '%s\n' "$hits" | head -n 3 >&2
has_findings=1
else
local scan_status=$?
if [[ "$scan_status" -ne 1 ]]; then
printf '[ECC pre-commit] Secret scanner failed (rg exit %s) for %s; commit blocked because scanning could not complete.\n' "$scan_status" "$file" >&2
exit 1
fi
fi
}
@@ -56,7 +67,7 @@ while IFS= read -r file; do
scan_added_lines "$file" "GitHub classic token" 'ghp_[A-Za-z0-9]{36}'
scan_added_lines "$file" "GitHub fine-grained token" 'github_pat_[A-Za-z0-9_]{20,}'
scan_added_lines "$file" "AWS access key" 'AKIA[0-9A-Z]{16}'
scan_added_lines "$file" "private key block" '-----BEGIN (RSA|EC|OPENSSH|DSA|PRIVATE) KEY-----'
scan_added_lines "$file" "private key block" '-----BEGIN (RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----'
scan_added_lines "$file" "generic credential assignment" "(?i)\\b(api[_-]?key|secret|password|token)\\b\\s*[:=]\\s*['\\\"][^'\\\"]{12,}['\\\"]"
done <<< "$staged_files"
+81
View File
@@ -11,6 +11,7 @@ const TOML = require('@iarna/toml');
const repoRoot = path.join(__dirname, '..', '..');
const installScript = path.join(repoRoot, 'scripts', 'codex', 'install-global-git-hooks.sh');
const preCommitHook = path.join(repoRoot, 'scripts', 'codex-git-hooks', 'pre-commit');
const prePushHook = path.join(repoRoot, 'scripts', 'codex-git-hooks', 'pre-push');
const pluginCacheCheckScript = path.join(repoRoot, 'scripts', 'codex', 'check-plugin-cache.js');
const mergeCodexConfigScript = path.join(repoRoot, 'scripts', 'codex', 'merge-codex-config.js');
@@ -164,6 +165,86 @@ if (
passed++;
else failed++;
function runHermeticPreCommit({ content = 'safe text\n', filename = 'sample.txt', scanner = false, env = {} } = {}) {
const tempDir = createTempDir('codex-pre-commit-');
try {
const bashEnv = path.join(tempDir, 'bash-env');
const binDir = path.join(tempDir, 'bin');
const callsPath = path.join(tempDir, 'scanner-calls');
fs.mkdirSync(binDir);
const hookEnv = makeHermeticCodexEnv(tempDir, path.join(tempDir, '.codex'), {
PATH: process.env.PATH,
GIT_CONFIG_NOSYSTEM: '1',
MSYS_NO_PATHCONV: '1',
ECC_SKIP_GIT_HOOKS: '0',
ECC_SKIP_PRECOMMIT: '0',
...env,
});
const initialized = spawnSync('git', ['init', '--quiet'], { cwd: tempDir, env: hookEnv });
assert.strictEqual(initialized.status, 0, initialized.stderr?.toString());
if (content !== null) {
fs.writeFileSync(path.join(tempDir, filename), content);
const staged = spawnSync('git', ['add', '--', filename], { cwd: tempDir, env: hookEnv });
assert.strictEqual(staged.status, 0, staged.stderr?.toString());
}
// Keep real Git/awk available, while hiding every installed ripgrep binary.
fs.writeFileSync(bashEnv, `
git_path="$(command -v git)"
awk_path="$(command -v awk)"
head_path="$(command -v head)"
cat_path="$(command -v cat)"
rg_path="$(command -v rg || true)"
git() { "$git_path" "$@"; }
awk() { "$awk_path" "$@"; }
head() { "$head_path" "$@"; }
cat() { "$cat_path" "$@"; }
unset -f rg
PATH="${toBashPath(binDir)}"
${scanner ? `rg() {
printf '%s\\n' "$*" >> "${toBashPath(callsPath)}"
${scanner === 'real' ? '"$rg_path" "$@"' : scanner === 'finding' ? "printf '1:synthetic-scanner-finding\\n'; return 0;" : scanner === 'error' ? 'return 2;' : 'return 1;'}
}` : ''}
`);
const result = runBash(preCommitHook, {
env: { ...hookEnv, BASH_ENV: toBashPath(bashEnv) },
cwd: tempDir,
preservePath: false,
});
result.scannerCalls = fs.existsSync(callsPath) ? fs.readFileSync(callsPath, 'utf8').trim().split('\n') : [];
return result;
} finally {
cleanup(tempDir);
}
}
for (const [name, options, status, diagnostic] of [
['blocks safe staged text when ripgrep is missing', {}, 1, /ripgrep.*required/i],
['blocks a staged token when ripgrep is missing', { content: `token = "ghp_${'a'.repeat(36)}"\n` }, 1, /ripgrep.*required/i],
['allows a successful scan with no findings', { scanner: true }, 0, null],
['blocks a finding reported by an invoked scanner', { scanner: 'finding' }, 1, /Potential secret detected/],
['blocks an invoked scanner error', { scanner: 'error' }, 1, /scanner failed.*exit 2/i],
['allows safe text through the real scanner', { scanner: 'real' }, 0, null],
['blocks a token through the real scanner', { scanner: 'real', content: `token = "ghp_${'a'.repeat(36)}"\n` }, 1, /Potential secret detected/],
['blocks a private key through the real scanner', { scanner: 'real', content: '-----BEGIN PRIVATE KEY-----\n' }, 1, /private key block/],
['blocks an RSA private key through the real scanner', { scanner: 'real', content: '-----BEGIN RSA PRIVATE KEY-----\n' }, 1, /private key block/],
['allows an empty index without ripgrep', { content: null }, 0, null],
['allows excluded lockfiles without ripgrep', { filename: 'yarn.lock' }, 0, null],
['preserves the pre-commit bypass warning', { env: { ECC_SKIP_PRECOMMIT: '1' } }, 0, /hook bypassed via env/],
['preserves the global bypass warning', { env: { ECC_SKIP_GIT_HOOKS: '1' } }, 0, /hook bypassed via env/],
]) {
if (test(`pre-commit ${name}`, () => {
const result = runHermeticPreCommit(options);
assert.strictEqual(result.status, status, `${result.stdout}\n${result.stderr}`);
if (diagnostic) assert.match(result.stderr, diagnostic);
else assert.strictEqual(result.stderr, '');
if (options.scanner) {
assert.ok(result.scannerCalls.length > 0, 'The scanner must actually execute');
assert.ok(result.scannerCalls.every(call => call.startsWith('-n --pcre2 -- ')), 'Regex must follow the option terminator');
}
})) passed++;
else failed++;
}
function runHermeticPrePush({
failScript = null,
includeCorepack = true,