#!/usr/bin/env bash set -euo pipefail usage() { cat < Scan every AGENTS.md file in a repo (root and nested) for embedded secrets, API keys, tokens, or connection strings. AGENTS.md is committed content — real credentials in it are a hard-prohibition violation, not a style nit. Placeholders (, \$ENV_VAR, YOUR_TOKEN_HERE, example.com, etc.) are not flagged. Arguments: repo-root Path to the repository root to scan. Exit codes: 0 No findings 1 One or more FAIL findings EOF } if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then usage exit 0 fi if [[ $# -lt 1 ]]; then echo "Error: repo-root is required." >&2 echo "" >&2 usage >&2 exit 1 fi python3 -u - "$1" <<'PYTHON' import sys import os import re repo_root = os.path.abspath(sys.argv[1]) if not os.path.isdir(repo_root): print(f"Error: '{repo_root}' is not a directory.", file=sys.stderr) sys.exit(1) EXCLUDE_DIRS = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build"} def find_agents_md(root): results = [] for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS and not d.startswith(".")] for fname in filenames: if fname == "AGENTS.md": results.append(os.path.join(dirpath, fname)) return sorted(results) PLACEHOLDER_RE = re.compile( r'(?i)(your[_-]|my[_-]|example|xxx+|placeholder|changeme|<[^>]+>|\$\{|\$[A-Z_][A-Z0-9_]*|\.\.\.|redacted)' ) PATTERNS = [ ("AWS access key ID", re.compile(r'AKIA[0-9A-Z]{16}')), ("Private key block", re.compile(r'-----BEGIN [A-Z ]*PRIVATE KEY-----')), ("GitHub token", re.compile(r'gh[pousr]_[A-Za-z0-9]{36,}')), ("Slack token", re.compile(r'xox[baprs]-[A-Za-z0-9-]{10,}')), ("GitLab token", re.compile(r'glpat-[A-Za-z0-9_-]{20,}')), ("Generic API-style secret token", re.compile(r'\bsk-[A-Za-z0-9]{20,}\b')), ( "Credential-bearing connection string", re.compile(r'[a-zA-Z][a-zA-Z0-9+.-]*://[^:@/\s]+:[^@/\s]+@[^\s\'"]+'), ), ( "Assigned secret/password/token literal", re.compile( r'(?i)\b(api[_-]?key|secret|token|password|passwd|pwd|access[_-]?key)\b' r'\s*[:=]\s*[\'"]?([A-Za-z0-9+/_.\-]{12,})[\'"]?' ), ), ] findings = [] def emit_fail(desc, fpath, lineno, why, fix): findings.append((desc, fpath, lineno, why, fix)) for fpath in find_agents_md(repo_root): rel = os.path.relpath(fpath, repo_root) with open(fpath, encoding="utf-8", errors="replace") as f: lines = f.readlines() for i, line in enumerate(lines, start=1): for label, pattern in PATTERNS: m = pattern.search(line) if not m: continue # Scope the placeholder allowlist to the matched secret-candidate # substring only. Checking the whole line would let an unrelated # placeholder-looking token elsewhere on the line (e.g. in a # trailing comment) suppress detection of a real credential. value = m.group(0) if PLACEHOLDER_RE.search(value): continue emit_fail( f"Possible {label}", f"{rel}:{i}", i, "AGENTS.md is committed content; this line matches a real-looking credential pattern rather than a placeholder.", "Remove the embedded credential and replace it with an environment variable reference or placeholder (e.g. $API_KEY, ).", ) break if not findings: sys.exit(0) for desc, fpath, _lineno, why, fix in findings: print(f"FAIL {desc} — {fpath}") print(f" Why: {why}") print(f" Fix: {fix}") print() sys.exit(1) PYTHON