Use this skill when the user wants to scan code for secrets, find leaked credentials, check for hardcoded API keys, audit git history for sensitive data, or set up secret detection.
64
75%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./skills/secret-scanner/SKILL.mdScans codebases, files, and git history for accidentally committed secrets: API keys, tokens, passwords, private keys, and credentials. Produces severity-tagged reports without logging the secrets themselves.
import re
from pathlib import Path
from dataclasses import dataclass
@dataclass
class SecretFinding:
file: str
line: int
type_: str
severity: str
preview: str # first 6 chars + *** (never log full secret)
# Common secret patterns
PATTERNS = {
"aws-access-key": (r"AKIA[0-9A-Z]{16}", "CRITICAL"),
"aws-secret-key": (r"(?i)aws.{0,20}secret.{0,20}['\"][0-9a-zA-Z/+]{40}['\"]", "CRITICAL"),
"github-token": (r"gh[pousr]_[A-Za-z0-9_]{36,}", "HIGH"),
"github-app-token": (r"ghs_[A-Za-z0-9_]{36}", "HIGH"),
"stripe-key": (r"sk_(live|test)_[0-9a-zA-Z]{24,}", "CRITICAL"),
"stripe-restricted": (r"rk_(live|test)_[0-9a-zA-Z]{24,}", "HIGH"),
"openai-key": (r"sk-[A-Za-z0-9]{20,}T3BlbkFJ[A-Za-z0-9]{20,}", "CRITICAL"),
"anthropic-key": (r"sk-ant-[A-Za-z0-9\-_]{90,}", "CRITICAL"),
"slack-token": (r"xox[baprs]-([0-9a-zA-Z]{10,48})", "HIGH"),
"private-key-header": (r"-----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY( BLOCK)?-----", "CRITICAL"),
"generic-api-key": (r"(?i)(api_key|apikey)\s*[=:]\s*['\"][A-Za-z0-9+/=_\-]{20,}['\"]", "HIGH"),
"generic-password": (r"(?i)(password|passwd|pwd)\s*[=:]\s*['\"][^'\"]{8,}['\"]", "MEDIUM"),
"jwt-token": (r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+", "MEDIUM"),
"connection-string": (r"(?i)(mongodb|postgres|mysql|redis)://[^:]+:[^@]+@", "HIGH"),
"env-file-cred": (r"(?m)^[A-Z_]+(KEY|SECRET|TOKEN|PASSWORD)\s*=\s*[^\s$]", "MEDIUM"),
}
SKIP_EXTENSIONS = {".pyc", ".png", ".jpg", ".gif", ".svg", ".ico", ".lock"}
SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"}
def scan_file(file_path: Path) -> list[SecretFinding]:
if file_path.suffix in SKIP_EXTENSIONS:
return []
try:
text = file_path.read_text(errors="ignore")
except OSError:
return []
findings = []
lines = text.splitlines()
for i, line in enumerate(lines, 1):
for secret_type, (pattern, severity) in PATTERNS.items():
if re.search(pattern, line):
# Extract first match, show only prefix
m = re.search(pattern, line)
raw = m.group() if m else ""
preview = raw[:6] + "***" if len(raw) > 6 else "***"
findings.append(SecretFinding(
file=str(file_path),
line=i,
type_=secret_type,
severity=severity,
preview=preview,
))
return findings
def scan_directory(directory: str = ".") -> list[SecretFinding]:
root = Path(directory)
all_findings = []
for path in root.rglob("*"):
if any(skip in path.parts for skip in SKIP_DIRS):
continue
if path.is_file():
all_findings.extend(scan_file(path))
return all_findingsimport subprocess
def scan_git_history(repo_path: str = ".", depth: int = 100) -> list[dict]:
"""Scan recent git commits for secrets in diffs."""
log = subprocess.run(
["git", "log", f"-{depth}", "--oneline"],
cwd=repo_path, capture_output=True, text=True
).stdout.strip().splitlines()
findings = []
for line in log:
commit_hash = line.split()[0]
diff = subprocess.run(
["git", "show", commit_hash, "--unified=0"],
cwd=repo_path, capture_output=True, text=True
).stdout
for secret_type, (pattern, severity) in PATTERNS.items():
matches = re.findall(pattern, diff)
if matches:
findings.append({
"commit": commit_hash,
"type": secret_type,
"severity": severity,
"count": len(matches),
})
return findings# Install gitleaks
brew install gitleaks # macOS
# or download from https://github.com/gitleaks/gitleaks/releases
# Scan current repo
gitleaks detect --source . --report-format json --report-path findings.json
# Scan git history
gitleaks detect --source . --log-opts="-n 500" --report-format json
# Pre-commit hook
gitleaks protect --staged # scan staged files onlydef format_report(findings: list[SecretFinding]) -> str:
if not findings:
return "No secrets detected."
by_severity = {"CRITICAL": [], "HIGH": [], "MEDIUM": []}
for f in findings:
by_severity.setdefault(f.severity, []).append(f)
lines = [f"Secret Scan Report — {len(findings)} finding(s)\n"]
for severity in ("CRITICAL", "HIGH", "MEDIUM"):
group = by_severity.get(severity, [])
if group:
lines.append(f"\n{severity} ({len(group)})")
for f in group:
lines.append(f" {f.file}:{f.line} [{f.type_}] {f.preview}")
return "\n".join(lines)# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks| Task | Tool | Notes |
|---|---|---|
| Scan directory | Custom regex | 15+ pattern types |
| Scan git history | gitleaks | Checks all commits |
| Pre-commit check | gitleaks protect | Blocks staged secrets |
| CI integration | gitleaks detect | Exit 1 on findings |
| False positives | .gitleaksignore | Allowlist specific lines |
32f0050
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.