CtrlK
BlogDocsLog inGet started
Tessl Logo

secret-scanner

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

Quality

75%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./skills/secret-scanner/SKILL.md
SKILL.md
Quality
Evals
Security

Secret Scanner

Overview

Scans 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.

Pattern-Based Scanner

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_findings

Git History Scanner

import 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

Gitleaks Integration

# 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 only

Report Formatter

def 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 Hook Setup

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

Quick Reference

TaskToolNotes
Scan directoryCustom regex15+ pattern types
Scan git historygitleaksChecks all commits
Pre-commit checkgitleaks protectBlocks staged secrets
CI integrationgitleaks detectExit 1 on findings
False positives.gitleaksignoreAllowlist specific lines
Repository
ProwlrBot/prowlr-marketplace
Last updated
First committed

Is this your skill?

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.