| name | issueguard-real-time-secret-leak |
| description | Scan text for leaked secrets using a two-stage pipeline: regex candidate extraction followed by contextual classification to eliminate false positives. Use when the user says 'scan for secrets', 'check for leaked credentials', 'find API keys in this text', 'detect hardcoded secrets', 'audit issue text for sensitive data', or 'prevent secret leaks'. |
IssueGuard: Two-Stage Secret Leak Detection
This skill enables Claude to detect accidentally leaked secrets (API keys, tokens, passwords, credentials) in unstructured text such as GitHub issue reports, logs, code snippets, configuration files, and documentation. It applies the IssueGuard two-stage pipeline: first, regex-based candidate extraction to catch anything that looks like a secret, then contextual classification to separate real secrets from false positives (placeholders, redacted values, dummy keys, example strings). This approach achieves 92.70% F1-score, dramatically outperforming regex-only scanners like TruffleHog (69.69% F1) and Gitleaks (63.60% F1) which suffer from massive false positive rates.
When to Use
- When the user asks to scan a GitHub issue body, bug report, or support ticket for accidentally leaked secrets
- When reviewing text that contains logs, stack traces, or config snippets before posting publicly
- When the user pastes text and asks "is there anything sensitive in here?"
- When building or auditing a pre-submission hook that checks issue content for credentials
- When the user wants to implement a secret detection pipeline in their own codebase
- When triaging a batch of issue reports or comments for secret exposure incidents
- When the user asks to build regex patterns for detecting specific secret types (AWS keys, GitHub tokens, etc.)
Key Technique: Regex Extraction + Contextual Classification
Stage 1 — Regex Candidate Extraction. The first stage casts a wide net using regular expressions matched against known secret formats. IssueGuard uses 761 regex patterns covering API keys, OAuth tokens, private keys, database connection strings, and more. This stage deliberately prioritizes recall over precision — it achieves ~100% recall but only ~6.8% precision. The goal is to never miss a real secret, even if that means flagging many non-secrets. Each regex match produces a candidate with its surrounding text.
Stage 2 — Contextual Classification. Each candidate is passed through a classifier that examines a 200-character context window around the matched string. The original IssueGuard uses a fine-tuned CodeBERT model for this, but the core insight is applicable without ML: by examining context (variable names like example_key, surrounding text like # replace with your key, placeholder patterns like xxx or REDACTED), you can reliably distinguish real secrets from benign strings. The binary decision is: Secret (a real, exploitable credential) vs. Non-sensitive (placeholder, redacted value, dummy key, documentation example).
Why this matters. Pure regex scanning is nearly useless for unstructured text — at 6.8% precision, over 93% of alerts are false positives. The contextual second stage is what makes the pipeline practical. When implementing this for users, Claude should replicate both stages: broad pattern matching followed by context-aware filtering.
Step-by-Step Workflow
-
Collect the input text. Read the full content to be scanned — issue body, log output, config file, or any unstructured text the user provides. Preserve the original formatting; secrets often appear in code blocks, inline code, or raw text.
-
Apply regex candidate extraction. Scan the text against patterns for known secret formats. At minimum, cover these categories:
- AWS access keys (
AKIA[0-9A-Z]{16})
- AWS secret keys (40-character base64 strings near AWS context)
- GitHub tokens (
ghp_[A-Za-z0-9]{36}, gho_, ghu_, ghs_, ghr_)
- GitLab tokens (
glpat-[A-Za-z0-9\-]{20,})
- Generic API keys (high-entropy strings 20+ characters following keywords like
key, token, secret, password, apikey, api_key)
- Private keys (
-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----)
- Database connection strings (URIs with embedded credentials:
://user:pass@host)
- JWT tokens (
eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,})
- Slack tokens (
xox[bpas]-[A-Za-z0-9-]+)
- Generic high-entropy strings (32+ hex or base64 chars adjacent to secret-indicating keywords)
-
Extract context window for each candidate. For every regex match, capture 200 characters of surrounding text (100 before, 100 after). This context is critical for classification.
-
Classify each candidate contextually. Examine the context window and the candidate string itself. Mark as Non-sensitive if any of these indicators are present:
- Placeholder patterns:
xxx, your_key_here, REPLACE_ME, <token>, INSERT, example, dummy, test, fake, sample
- Redaction patterns:
***, REDACTED, [hidden], ... truncation
- Documentation context: the surrounding text is explaining how to set a variable, not providing a real value
Concrete Examples
Example 1: Scanning a GitHub issue body for leaked AWS credentials
User: "Check this issue text for any secrets before I post it"
I'm getting a 403 error when trying to access S3. Here's my config:
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region = us-east-1
The bucket policy looks correct. Any ideas?
Approach:
- Regex extraction finds two candidates:
AKIAIOSFODNN7EXAMPLE (matches AKIA[0-9A-Z]{16}) and wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY (40-char base64 near AWS context)
- Contextual classification checks both against known dummy values
AKIAIOSFODNN7EXAMPLE is the AWS documentation example key — Non-sensitive
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY is the AWS documentation example secret — Non-sensitive
Output:
Secret Scan Results: 0 confirmed secrets found
Stage 1 (regex): 2 candidates extracted
Stage 2 (classification): 0 confirmed, 2 filtered as non-sensitive
Details:
- AKIA...MPLE → AWS Access Key format → Known AWS example key (Non-sensitive)
- wJal...EKEY → AWS Secret Key format → Known AWS example secret (Non-sensitive)
Verdict: Safe to post. Both values are well-known AWS documentation examples.
Example 2: Detecting a real leaked GitHub token in a bug report
User: "Scan this for secrets"
Getting auth failures with the GitHub API. Here's the curl command I ran:
curl -H "Authorization: token ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890" \
https://api.github.com/repos/myorg/myrepo/issues
Response: {"message":"Bad credentials","documentation_url":"..."}
Approach:
- Regex extraction finds
ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890 (matches ghp_[A-Za-z0-9]{36})
- Context window: appears in an
Authorization header in a curl command — this is a real usage, not documentation
- No placeholder indicators (no
example, your_token, REPLACE, etc.)
- Entropy is high (mixed case alphanumeric, 40 chars)
- Classification: Secret — HIGH severity
Output:
SECRET DETECTED — 1 confirmed secret found
Stage 1 (regex): 1 candidate extracted
Stage 2 (classification): 1 confirmed secret
[HIGH] GitHub Personal Access Token
Value: ghp_aBcD...7890
Location: Line 3 (in Authorization header)
Context: Used in curl command as bearer token — real credential, not a placeholder
Remediation:
1. Do NOT post this text as-is
2. Revoke this token immediately: GitHub → Settings → Developer settings → Personal access tokens
3. Generate a new token with minimum required scopes
4. Use environment variables: -H "Authorization: token $GITHUB_TOKEN"
Example 3: Building a secret scanner script for a CI pipeline
User: "Help me write a Python script that scans issue text for secrets using the IssueGuard approach"
Approach:
- Implement Stage 1: a dictionary of compiled regex patterns by secret type
- Implement Stage 2: context window extraction + heuristic classification (placeholder detection, entropy calculation)
- Return structured results
Output (key implementation):
import re
import math
from dataclasses import dataclass
@dataclass
class SecretCandidate:
secret_type: str
value: str
start: int
end: int
context: str
severity: str
SECRET_PATTERNS = {
"AWS Access Key": (re.compile(r"AKIA[0-9A-Z]{16}"), "HIGH"),
"GitHub Token": (re.compile(r"gh[pousr]_[A-Za-z0-9]{36,}"), "HIGH"),
"GitLab Token": (re.compile(r"glpat-[A-Za-z0-9\-]{20,}"), "HIGH"),
"Slack Token": (re.compile(r"xox[bpas]-[A-Za-z0-9\-]+"), "MEDIUM"),
"JWT": (re.compile(r"eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+"), "MEDIUM"),
"Private Key": (re.compile(r"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"), "HIGH"),
"DB Connection String": (re.compile(r"(?:mongodb|postgres|mysql|redis)://\S+:\S+@\S+"), "HIGH"),
"Generic High-Entropy Secret": (re.compile(
r"(?:api[_-]?key|secret|token|password|passwd|credential)[\s]*[=:]\s*['\"]?([A-Za-z0-9/+=]{20,})['\"]?"
, re.IGNORECASE), "MEDIUM"),
}
PLACEHOLDER_INDICATORS = re.(
,
re.IGNORECASE,
)
KNOWN_DUMMY_SECRETS = {
,
,
}
() -> :
s:
freq = {}
c s:
freq[c] = freq.get(c, ) +
length = (s)
-((count / length) * math.log2(count / length) count freq.values())
() -> :
ctx_start = (, start - window)
ctx_end = ((text), end + window)
text[ctx_start:ctx_end]
() -> [SecretCandidate]:
candidates = []
secret_type, (pattern, severity) SECRET_PATTERNS.items():
pattern.finditer(text):
value = .group()
context = extract_context(text, .start(), .end())
value KNOWN_DUMMY_SECRETS:
PLACEHOLDER_INDICATORS.search(context):
secret_type == :
secret_value = .group() .lastindex value
shannon_entropy(secret_value) < :
candidates.append(SecretCandidate(
secret_type=secret_type,
value=value,
start=.start(),
end=.end(),
context=context,
severity=severity,
))
candidates
Best Practices
- Do: Always run both stages. Regex alone produces ~93% false positives on unstructured text. The contextual classification stage is not optional — it is what makes the results useful.
- Do: Extract at least 200 characters of surrounding context for each candidate. Without context, you cannot distinguish
api_key = "AKIAIOSFODNN7EXAMPLE" (documentation) from a real key in a log dump.
- Do: Partially redact detected secrets in your output (show first 4 and last 4 characters). Avoid displaying the full secret, since Claude's output may itself be logged.
- Do: Maintain a list of known dummy/example credentials (AWS example keys, Stripe test keys, etc.) and filter them explicitly.
- Avoid: Relying solely on entropy thresholds. Some real secrets have moderate entropy (short passwords), and some benign strings have high entropy (UUIDs, commit hashes). Entropy is a supplementary signal, not a primary classifier.
- Avoid: Reporting private keys detected inside
-----BEGIN ... -----END blocks without checking if they are part of documentation showing the format of a key. Check if the content between the markers has real key material or is truncated/placeholder text.
Error Handling
- Malformed regex matches: Some patterns may match partial strings at text boundaries. Always validate that the matched string has the expected length and character set before classifying.
- Extremely large input text: For texts over 100KB, scan in chunks with overlapping windows (at least 200 chars overlap) to avoid splitting a secret across chunk boundaries.
- Unicode and encoding issues: Secrets are ASCII, but surrounding text may contain Unicode. Ensure regex patterns operate on the raw text without encoding errors. Use
re.ASCII flag where appropriate.
- Ambiguous classifications: When context is insufficient to determine if a candidate is real or a placeholder, err on the side of caution and report it with a note that manual verification is needed. A false positive is better than a missed secret.
- Nested code blocks: Secrets inside markdown code fences or HTML
<code> tags are still secrets. Do not skip content inside code blocks — these are actually where secrets most commonly appear in issue reports.
Limitations
- Novel secret formats: The regex stage can only catch secrets matching known patterns. Custom or proprietary token formats that don't match standard patterns will be missed. Users should extend the pattern list for their specific services.
- Obfuscated secrets: Base64-encoded or otherwise transformed secrets embedded in larger strings may evade regex extraction. The scanner does not decode or decompose encoded content.
- Context window edge cases: When a secret appears at the very start or end of the text, the 200-character context window is truncated, potentially reducing classification accuracy.
- Not a replacement for secret scanning in git: This technique targets unstructured text in issues and comments. For committed code, dedicated tools like
git-secrets, truffleHog, or gitleaks operating on git history are more appropriate.
- No ML model inference: Claude applies the two-stage logic heuristically rather than running the actual fine-tuned CodeBERT model. For production systems processing high volumes, deploying the IssueGuard backend (FastAPI + CodeBERT) will yield higher accuracy, especially on edge cases.
Reference