
Repomix Safe Mixer
- 680 installs
- 1.3k repo stars
- Updated August 4, 2026
- daymade/claude-code-skills
repomix-safe-mixer is a Claude Code skill that packages codebases with repomix while automatically detecting and removing hardcoded credentials so developers can share reference packages without leaking API keys, tokens,
About
repomix-safe-mixer is a daymade/claude-code-skills utility for safe repomix runs. Before packing, it scans for hardcoded secrets—API keys, database credentials, tokens—reports findings, and removes or blocks unsafe content so distributed bundles do not leak credentials. Developers reach for it when creating reference packages for LLM context, open-source drops, or vendor handoffs where repomix is the packager of choice. The skill activates when users mention repomix packaging with security concerns or when sharing code externally. It adds a mandatory secret gate on top of repomix’s concat workflow, turning ad hoc packs into audited artifacts suitable for collaboration.
- Automatically scans for hardcoded API keys, tokens, and database credentials before repomix packaging
- Blocks packaging and reports exact file and line locations when secrets are found
- Runs complete workflow: scan → report → pack using safe_pack.py
- Prevents accidental credential exposure when creating shareable reference packages
- Integrates directly with repomix for secure distribution of code to LLMs or collaborators
Repomix Safe Mixer by the numbers
- 680 all-time installs (skills.sh)
- Ranked #450 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/daymade/claude-code-skills --skill repomix-safe-mixerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 680 |
|---|---|
| repo stars | ★ 1.3k |
| Last updated | August 4, 2026 |
| Repository | daymade/claude-code-skills ↗ |
How do you package a repo with repomix without leaking secrets?
Safely package codebases with repomix without leaking hardcoded credentials.
Who is it for?
Developers sharing repomix-packed code with agents, vendors, or public repos who need automated secret stripping before distribution.
Skip if: Runtime secret management with vaults, production deploy hardening, or teams not using repomix to bundle repositories.
When should I use this skill?
A developer packages code with repomix for distribution, reference context, or external sharing and mentions credential or security concerns.
What you get
Sanitized repomix bundle plus a reported list of detected and removed hardcoded credentials.
- Sanitized repomix bundle
- Secret scan report
Files
Repomix Safe Mixer
Overview
Safely package codebases with repomix by automatically detecting and removing hardcoded credentials.
This skill prevents accidental credential exposure when packaging code with repomix. It scans for hardcoded secrets (API keys, database credentials, tokens), reports findings, and ensures safe packaging.
When to use: When packaging code with repomix for distribution, creating shareable reference packages, or whenever security concerns exist about hardcoded credentials in code.
Core Workflow
Standard Safe Packaging
Use safe_pack.py from this skill's scripts/ directory for the complete workflow: scan → report → pack.
python3 scripts/safe_pack.py <directory>What it does: 1. Scans directory for hardcoded credentials 2. Reports findings with file/line details 3. Blocks packaging if secrets found 4. Packs with repomix only if scan is clean
Example:
python3 scripts/safe_pack.py ./my-projectOutput if clean:
🔍 Scanning ./my-project for hardcoded secrets...
✅ No secrets detected!
📦 Packing ./my-project with repomix...
✅ Packaging complete!
Package is safe to distribute.Output if secrets found:
🔍 Scanning ./my-project for hardcoded secrets...
⚠️ Security Scan Found 3 Potential Secrets:
🔴 supabase_url: 1 instance(s)
- src/client.ts:5
Match: https://your-project-ref.supabase.co
❌ Cannot pack: Secrets detected!Options
Custom output file:
python3 scripts/safe_pack.py \
./my-project \
--output package.xmlWith repomix config:
python3 scripts/safe_pack.py \
./my-project \
--config repomix.config.jsonExclude patterns from scanning:
python3 scripts/safe_pack.py \
./my-project \
--exclude '.*test.*' '.*\.example'Force pack (dangerous, skip scan):
python3 scripts/safe_pack.py \
./my-project \
--force # ⚠️ NOT RECOMMENDEDStandalone Secret Scanning
Use scan_secrets.py from this skill's scripts/ directory for scanning only (without packing).
python3 scripts/scan_secrets.py <directory>Use cases:
- Verify cleanup after removing credentials
- Pre-commit security checks
- Audit existing codebases
Example:
python3 scripts/scan_secrets.py ./my-projectJSON output for programmatic use:
python3 scripts/scan_secrets.py \
./my-project \
--jsonExclude patterns:
python3 scripts/scan_secrets.py \
./my-project \
--exclude '.*test.*' '.*example.*' '.*SECURITY_AUDIT\.md'Detected Secret Types
The scanner detects common credential patterns including:
Cloud Providers:
- AWS Access Keys (
AKIA...) - Cloudflare R2 Account IDs and Access Keys
- Supabase Project URLs and Anon Keys
API Keys:
- Stripe Keys (
sk_live_...,pk_live_...) - OpenAI API Keys (
sk-...) - Google Gemini API Keys (
AIza...) - Generic API Keys
Authentication:
- JWT Tokens (
eyJ...) - OAuth Client Secrets
- Private Keys (
-----BEGIN PRIVATE KEY-----) - Turnstile Keys (
0x...)
See references/common_secrets.md for complete list and patterns.
Handling Detected Secrets
When secrets are found:
Step 1: Review Findings
Examine each finding to verify it's a real credential (not a placeholder or example).
Step 2: Replace with Environment Variables
Before:
const SUPABASE_URL = "https://your-project-ref.supabase.co";
const API_KEY = "<hardcoded-anon-key-DO-NOT-COMMIT>";After:
const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL || "https://your-project-ref.supabase.co";
const API_KEY = import.meta.env.VITE_API_KEY || "your-api-key-here";
// Validation
if (!import.meta.env.VITE_SUPABASE_URL) {
console.error("⚠️ Missing VITE_SUPABASE_URL environment variable");
}Step 3: Create .env.example
# Example environment variables
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_API_KEY=your-api-key-here
# Instructions:
# 1. Copy this file to .env
# 2. Replace placeholders with real values
# 3. Never commit .env to version controlStep 4: Verify Cleanup
Run scanner again to confirm secrets removed:
python3 scripts/scan_secrets.py ./my-projectStep 5: Safe Pack
Once clean, package safely:
python3 scripts/safe_pack.py ./my-projectPost-Exposure Actions
If credentials were already exposed (e.g., committed to git, shared publicly):
1. Rotate credentials immediately - Generate new keys/tokens 2. Revoke old credentials - Disable compromised credentials 3. Audit usage - Check logs for unauthorized access 4. Monitor - Set up alerts for unusual activity 5. Update deployment - Deploy code with new credentials 6. Document incident - Record what was exposed and actions taken
Common False Positives
The scanner skips common false positives:
Placeholders:
your-api-key,example-key,placeholder-value<YOUR_API_KEY>,${API_KEY},TODO: add key
Test/Example files:
- Files matching
.*test.*,.*example.*,.*sample.*
Comments:
- Lines starting with
//,#,/*,*
Environment variable references (correct usage):
process.env.API_KEYimport.meta.env.VITE_API_KEYDeno.env.get('API_KEY')
Use --exclude to skip additional patterns if needed.
Integration with Repomix
This skill works with standard repomix:
Default usage (no config):
python3 scripts/safe_pack.py ./projectWith repomix config:
python3 scripts/safe_pack.py \
./project \
--config repomix.config.jsonCustom output location:
python3 scripts/safe_pack.py \
./project \
--output ~/Downloads/package-clean.xmlThe skill runs repomix internally after security validation, passing through config and output options.
Example Workflows
Workflow 1: Package a Clean Project
# Scan and pack in one command
python3 scripts/safe_pack.py \
~/workspace/my-project \
--output ~/Downloads/my-project-package.xmlWorkflow 2: Clean and Package a Project with Secrets
# Step 1: Scan to discover secrets
python3 scripts/scan_secrets.py ~/workspace/my-project
# Step 2: Review findings and replace credentials with env vars
# (Edit files manually or with automation)
# Step 3: Verify cleanup
python3 scripts/scan_secrets.py ~/workspace/my-project
# Step 4: Package safely
python3 scripts/safe_pack.py \
~/workspace/my-project \
--output ~/Downloads/my-project-clean.xmlWorkflow 3: Audit Before Commit
# Pre-commit hook: scan for secrets
python3 scripts/scan_secrets.py . --json
# Exit code 1 if secrets found (blocks commit)
# Exit code 0 if clean (allows commit)Resources
References:
references/common_secrets.md- Complete credential pattern catalog
Scripts:
scripts/scan_secrets.py- Standalone security scannerscripts/safe_pack.py- Complete scan → pack workflow
Related Skills:
repomix-unmixer- Extracts files from repomix packagesskill-creator- Creates new Claude Code skills
Security Note
This skill detects common patterns but may not catch all credential types. Always:
- Review findings manually
- Rotate exposed credentials
- Use .env.example templates
- Validate environment variables
- Monitor for unauthorized access
Not a replacement for: Secret scanning in CI/CD, git history scanning, or comprehensive security audits.
Security scan passed
Scanned at: 2026-06-13T19:48:10.902948
Tool: gitleaks + pattern-based validation
Content hash: 9f34d49a4c03cabe19ae3529e3d343312467a200f99413eafa97d3b668bd0580
Common Secret Patterns Reference
This document catalogs common credential types detected by the security scanner.
Table of Contents
- Cloud Provider Credentials
- Database Credentials
- API Keys and Tokens
- Authentication Secrets
- Common False Positives
---
Cloud Provider Credentials
AWS Credentials
AWS Access Key ID:
- Pattern:
AKIA[0-9A-Z]{16} - Example:
AKIAIOSFODNN7EXAMPLE - Location: Often in
.env, config files, or infrastructure code - Risk: Full AWS account access
AWS Secret Access Key:
- Pattern:
[0-9a-zA-Z/+=]{40} - Context: Usually follows
aws_secretor similar variable names - Example:
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - Risk: Account compromise, data breach, cost abuse
Cloudflare R2
R2 Account ID:
- Pattern:
[0-9a-f]{32}(in R2 URLs) - Example:
89ff427005e1767943b5ac257905a280inhttps://89ff427005e1767943b5ac257905a280.r2.cloudflarestorage.com - Risk: Account identification, targeted attacks
R2 Access Keys:
- Similar to AWS S3 credentials
- Pattern: Standard access key + secret key pair
- Risk: Bucket access, file manipulation, cost abuse
---
Database Credentials
Supabase
Project URL:
- Pattern:
https://[a-z]{20}.supabase.co - Example:
https://your-project-ref.supabase.co - Risk: Project identification
Anon/Public Key:
- Pattern: JWT token starting with
eyJ - Example:
<hardcoded-anon-key-DO-NOT-COMMIT> - Risk: Public data access, edge function invocation, quota abuse
Service Role Key:
- Pattern: JWT token (longer than anon key)
- Risk: CRITICAL - Full database admin access, bypasses RLS
PostgreSQL
Connection String:
- Pattern:
postgresql://user:password@host:port/database - Risk: Direct database access
---
API Keys and Tokens
Stripe
Publishable Key:
- Pattern:
pk_(live|test)_[0-9a-zA-Z]{24,} - Example:
pk_live_51AbC...(truncated for security) - Risk: Low (public by design, but reveals account)
Secret Key:
- Pattern:
sk_(live|test)_[0-9a-zA-Z]{24,} - Example:
sk_live_51AbC...(truncated for security) - Risk: CRITICAL - Payment processing, refunds, customer data
OpenAI / Gemini / LLM Providers
OpenAI API Key:
- Pattern:
sk-[A-Za-z0-9]{48} - Risk: API abuse, cost accumulation
Google Gemini API Key:
- Pattern:
AIza[0-9A-Za-z_-]{35} - Risk: API abuse, quota exhaustion
OpenRouter API Key:
- Pattern:
sk-or-v1-[0-9a-f]{64} - Risk: API abuse via OpenRouter
Cloudflare Turnstile
Site Key:
- Pattern:
0x[0-9A-F]{22} - Example:
0x4AAAAAABvH03QZ3BpnHR7p - Risk: Low (public by design), but enables testing
Secret Key:
- Pattern:
0x[0-9A-F]{40} - Risk: Bot protection bypass
---
Authentication Secrets
JWT Tokens
Format:
- Pattern:
eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+ - Three base64url-encoded parts separated by dots
- Risk: Session hijacking, impersonation
OAuth Secrets
Client Secret:
- Pattern: Variable, often
[0-9a-zA-Z_-]{20,} - Context: Near
client_id,oauth,app_secret - Risk: Application impersonation
Private Keys
RSA/EC Private Keys:
- Pattern:
-----BEGIN (RSA|EC|OPENSSH|DSA) PRIVATE KEY----- - Risk: CRITICAL - Complete identity compromise
---
Common False Positives
Example/Placeholder Values
Safe to ignore when matching:
- Strings containing:
example,placeholder,test,demo,sample - Template variables:
<YOUR_API_KEY>,${API_KEY},${...} - Documentation examples:
xxx,yyy,zzz - TODO markers:
TODO,FIXME,CHANGEME
Environment Variable References
Safe patterns (these are correct usage):
// JavaScript/TypeScript
const apiKey = process.env.API_KEY;
const apiKey = import.meta.env.VITE_API_KEY;
// Python
api_key = os.getenv('API_KEY')
api_key = os.environ.get('API_KEY')
// Deno
const apiKey = Deno.env.get('API_KEY');Comments
Lines starting with comment markers are often documentation:
//- JavaScript/TypeScript#- Python/Shell/YAML/* */- Multi-line comments
---
Detection Strategies
Context-Aware Scanning
Look for credential indicators:
- Variable names:
API_KEY,SECRET,TOKEN,PASSWORD,PRIVATE_KEY - Assignment operators:
=,:,=> - Quote patterns:
"...",'...', `...`
File Type Priorities
High Risk:
.env,.env.local,.env.production- Configuration files:
config.json,settings.py - Infrastructure code:
.tf,.yaml(Terraform, K8s)
Medium Risk:
- Source code:
.js,.ts,.py,.go - Documentation:
.md(may contain examples)
Low Risk:
- Test files:
*.test.js,*.spec.ts - Example files:
*.example.*
---
Remediation Patterns
Convert to Environment Variables
Before (hardcoded):
const SUPABASE_URL = "https://your-project-ref.supabase.co";
const SUPABASE_KEY = "<hardcoded-anon-key-DO-NOT-COMMIT>";After (environment variables):
const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL || "https://your-project-ref.supabase.co";
const SUPABASE_KEY = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY || "your-anon-key-here";
// Validation
if (!import.meta.env.VITE_SUPABASE_URL) {
console.error("Missing VITE_SUPABASE_URL environment variable");
}Create .env.example
# Supabase Configuration
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=your-anon-key-here
# API Keys
GEMINI_API_KEY=your-gemini-key
OPENROUTER_API_KEY=your-openrouter-key
# Important: Copy this to .env and replace with real values
# Never commit .env to version control!---
Post-Exposure Actions
If credentials are exposed:
1. Rotate Immediately - Generate new credentials 2. Revoke Old Credentials - Disable compromised keys 3. Audit Usage - Check for unauthorized access 4. Monitor - Set up alerts for unusual activity 5. Update Code - Deploy with new credentials 6. Notify - If public exposure, notify security team
---
References
#!/usr/bin/env python3
"""
Safe packaging workflow for repomix.
Scans for secrets, reports findings, and optionally packs after user confirmation.
"""
import os
import sys
import subprocess
import json
from pathlib import Path
def run_secret_scan(directory: Path, exclude_patterns: list = None):
"""Run secret scanner and return findings."""
script_dir = Path(__file__).parent
scan_script = script_dir / 'scan_secrets.py'
cmd = [sys.executable, str(scan_script), str(directory), '--json']
if exclude_patterns:
cmd.extend(['--exclude'] + exclude_patterns)
result = subprocess.run(cmd, capture_output=True, text=True)
try:
findings = json.loads(result.stdout) if result.stdout.strip() else []
except json.JSONDecodeError:
print(f"Error: Could not parse scan results", file=sys.stderr)
print(f"Scanner output: {result.stdout}", file=sys.stderr)
sys.exit(1)
return findings
def print_findings_report(findings: list):
"""Print human-readable findings report."""
if not findings:
print("✅ No secrets detected!\n")
return
print(f"\n⚠️ Security Scan Found {len(findings)} Potential Secrets:\n")
# Group by type
by_type = {}
for finding in findings:
type_name = finding['type']
if type_name not in by_type:
by_type[type_name] = []
by_type[type_name].append(finding)
# Print by type
for secret_type in sorted(by_type.keys()):
count = len(by_type[secret_type])
print(f"🔴 {secret_type}: {count} instance(s)")
for finding in by_type[secret_type][:3]: # Show first 3
print(f" - {finding['file']}:{finding['line']}")
print(f" Match: {finding['match']}")
if len(by_type[secret_type]) > 3:
print(f" ... and {len(by_type[secret_type]) - 3} more\n")
else:
print()
def run_repomix(directory: Path, output_file: Path = None, config_file: Path = None):
"""Run repomix to package the directory."""
cmd = ['repomix']
if config_file and config_file.exists():
cmd.extend(['--config', str(config_file)])
if output_file:
cmd.extend(['--output', str(output_file)])
# Change to directory before running repomix
result = subprocess.run(cmd, cwd=directory, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error: repomix failed", file=sys.stderr)
print(result.stderr, file=sys.stderr)
sys.exit(1)
print(result.stdout)
return result
def main():
if len(sys.argv) < 2:
print("Usage: safe_pack.py <directory> [--output file.xml] [--config repomix.config.json] [--force] [--exclude pattern1 pattern2 ...]")
print("\nOptions:")
print(" --output <file> Output file path for repomix")
print(" --config <file> Repomix config file")
print(" --force Skip confirmation, pack anyway (dangerous!)")
print(" --exclude <patterns> Patterns to exclude from secret scanning")
print("\nExamples:")
print(" safe_pack.py ./my-project")
print(" safe_pack.py ./my-project --output package.xml")
print(" safe_pack.py ./my-project --exclude '.*test.*' '.*\.example'")
print(" safe_pack.py ./my-project --force # Dangerous! Skip scan")
sys.exit(1)
directory = Path(sys.argv[1]).resolve()
if not directory.is_dir():
print(f"Error: {directory} is not a directory", file=sys.stderr)
sys.exit(1)
# Parse arguments
output_file = None
config_file = None
force = '--force' in sys.argv
exclude_patterns = []
if '--output' in sys.argv:
output_idx = sys.argv.index('--output')
if output_idx + 1 < len(sys.argv):
output_file = Path(sys.argv[output_idx + 1])
if '--config' in sys.argv:
config_idx = sys.argv.index('--config')
if config_idx + 1 < len(sys.argv):
config_file = Path(sys.argv[config_idx + 1])
if '--exclude' in sys.argv:
exclude_idx = sys.argv.index('--exclude')
exclude_patterns = [
arg for arg in sys.argv[exclude_idx + 1:]
if not arg.startswith('--') and arg != str(directory)
]
print(f"🔍 Scanning {directory} for hardcoded secrets...\n")
# Step 1: Scan for secrets
findings = run_secret_scan(directory, exclude_patterns)
# Step 2: Report findings
print_findings_report(findings)
# Step 3: Decision point
if findings:
if force:
print("⚠️ WARNING: --force flag set, packing anyway despite secrets found!\n")
else:
print("❌ Cannot pack: Secrets detected!")
print("\nRecommended actions:")
print("1. Review the findings above")
print("2. Replace hardcoded credentials with environment variables")
print("3. Run scan_secrets.py to verify cleanup")
print("4. Run this script again")
print("\nOr use --force to pack anyway (NOT RECOMMENDED)")
sys.exit(1)
# Step 4: Pack with repomix
print(f"📦 Packing {directory} with repomix...\n")
run_repomix(directory, output_file, config_file)
print("\n✅ Packaging complete!")
if findings:
print("\n⚠️ WARNING: Package contains secrets (--force was used)")
print(" DO NOT share this package publicly!")
else:
print(" Package is safe to distribute.")
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Security scanner for detecting hardcoded credentials in code.
Scans a directory for common credential patterns and reports findings.
"""
import os
import re
import sys
import json
from pathlib import Path
from typing import List, Dict, Tuple
# Common secret patterns (regex)
SECRET_PATTERNS = {
'aws_access_key': r'(?i)AKIA[0-9A-Z]{16}',
'aws_secret_key': r'(?i)(?:aws_secret|aws.{0,20}secret).{0,20}[=:]\s*["\']?([0-9a-zA-Z/+=]{40})["\']?',
'supabase_url': r'https://[a-z]{20}\.supabase\.co',
'supabase_anon_key': r'eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*',
'stripe_key': r'(?:sk|pk)_(live|test)_[0-9a-zA-Z]{24,}',
'cloudflare_api_token': r'(?i)(?:cloudflare|cf).{0,20}(?:token|key).{0,20}[=:]\s*["\']?([a-zA-Z0-9_-]{40,})["\']?',
'turnstile_key': r'0x[0-9A-F]{22}',
'generic_api_key': r'(?i)(?:api[_-]?key|apikey).{0,20}[=:]\s*["\']?([0-9a-zA-Z_\-]{20,})["\']?',
'r2_account_id': r'[0-9a-f]{32}(?=\.r2\.cloudflarestorage\.com)',
'jwt_token': r'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}',
'private_key': r'-----BEGIN (?:RSA|EC|OPENSSH|DSA) PRIVATE KEY-----',
'oauth_secret': r'(?i)(?:client_secret|oauth).{0,20}[=:]\s*["\']?([0-9a-zA-Z_\-]{20,})["\']?',
}
# File extensions to scan
SCANNABLE_EXTENSIONS = {
'.ts', '.tsx', '.js', '.jsx', '.py', '.md', '.json', '.yaml', '.yml',
'.env', '.env.example', '.env.local', '.env.production', '.env.development',
'.sh', '.bash', '.zsh', '.sql', '.go', '.java', '.rb', '.php', '.cs'
}
# Directories to skip
SKIP_DIRS = {
'node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build',
'.next', '.nuxt', 'vendor', 'target', 'bin', 'obj', '.terraform'
}
class SecretFinding:
"""Represents a detected secret."""
def __init__(self, file_path: str, line_num: int, pattern_name: str,
matched_text: str, line_content: str):
self.file_path = file_path
self.line_num = line_num
self.pattern_name = pattern_name
self.matched_text = matched_text
self.line_content = line_content.strip()
def to_dict(self) -> Dict:
return {
'file': self.file_path,
'line': self.line_num,
'type': self.pattern_name,
'match': self.matched_text[:50] + '...' if len(self.matched_text) > 50 else self.matched_text,
'context': self.line_content[:100] + '...' if len(self.line_content) > 100 else self.line_content
}
def scan_file(file_path: Path, base_dir: Path) -> List[SecretFinding]:
"""Scan a single file for secrets."""
findings = []
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line_num, line in enumerate(f, 1):
for pattern_name, pattern in SECRET_PATTERNS.items():
matches = re.finditer(pattern, line)
for match in matches:
# Skip common false positives
if should_skip_match(line, match.group()):
continue
findings.append(SecretFinding(
file_path=str(file_path.relative_to(base_dir)),
line_num=line_num,
pattern_name=pattern_name,
matched_text=match.group(),
line_content=line
))
except Exception as e:
print(f"Warning: Could not scan {file_path}: {e}", file=sys.stderr)
return findings
def should_skip_match(line: str, match: str) -> bool:
"""Check if a match should be skipped (likely false positive)."""
# Skip example/placeholder values
placeholders = [
'your-', 'example', 'placeholder', 'xxx', 'yyy', 'zzz',
'test-', 'demo-', 'sample-', '<YOUR_', '${', 'TODO'
]
line_lower = line.lower()
match_lower = match.lower()
for placeholder in placeholders:
if placeholder in match_lower or placeholder in line_lower:
return True
# Skip if in a comment
if re.search(r'^\s*(?://|#|/\*|\*)', line):
return True
return False
def scan_directory(directory: Path, exclude_patterns: List[str] = None) -> List[SecretFinding]:
"""Scan a directory recursively for secrets."""
findings = []
exclude_patterns = exclude_patterns or []
for root, dirs, files in os.walk(directory):
# Skip excluded directories
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
root_path = Path(root)
# Skip if matches exclude pattern
if any(re.search(pattern, str(root_path)) for pattern in exclude_patterns):
continue
for file in files:
file_path = root_path / file
# Only scan relevant file types
if file_path.suffix not in SCANNABLE_EXTENSIONS:
continue
# Skip if matches exclude pattern
if any(re.search(pattern, str(file_path)) for pattern in exclude_patterns):
continue
file_findings = scan_file(file_path, directory)
findings.extend(file_findings)
return findings
def print_report(findings: List[SecretFinding], directory: Path):
"""Print a human-readable report."""
if not findings:
print("✅ No secrets detected!")
return
print(f"\n⚠️ Found {len(findings)} potential secrets in {directory}:\n")
# Group by file
by_file = {}
for finding in findings:
if finding.file_path not in by_file:
by_file[finding.file_path] = []
by_file[finding.file_path].append(finding)
# Print grouped
for file_path in sorted(by_file.keys()):
print(f"📄 {file_path}")
for finding in by_file[file_path]:
print(f" Line {finding.line_num}: {finding.pattern_name}")
print(f" Match: {finding.matched_text[:80]}")
print(f" Context: {finding.line_content[:80]}")
print()
def main():
if len(sys.argv) < 2:
print("Usage: scan_secrets.py <directory> [--json] [--exclude pattern1 pattern2 ...]")
print("\nExamples:")
print(" scan_secrets.py ./my-project")
print(" scan_secrets.py ./my-project --json")
print(" scan_secrets.py ./my-project --exclude '.*test.*' '.*example.*'")
sys.exit(1)
directory = Path(sys.argv[1]).resolve()
if not directory.is_dir():
print(f"Error: {directory} is not a directory", file=sys.stderr)
sys.exit(1)
# Parse arguments
json_output = '--json' in sys.argv
exclude_patterns = []
if '--exclude' in sys.argv:
exclude_idx = sys.argv.index('--exclude')
exclude_patterns = [arg for arg in sys.argv[exclude_idx + 1:] if not arg.startswith('--')]
# Scan
findings = scan_directory(directory, exclude_patterns)
# Output
if json_output:
print(json.dumps([f.to_dict() for f in findings], indent=2))
else:
print_report(findings, directory)
# Exit code
sys.exit(1 if findings else 0)
if __name__ == '__main__':
main()
Related skills
How it compares
Use repomix-safe-mixer for repomix-specific bundles with secret stripping; use general security audit skills for full appsec review beyond packaging.
FAQ
What secrets does repomix-safe-mixer detect?
repomix-safe-mixer scans for hardcoded API keys, database credentials, tokens, and similar secrets before repomix packs the tree. It reports findings and ensures removed or blocked values do not appear in the final bundle.
When should repomix-safe-mixer run?
repomix-safe-mixer should run whenever you package code with repomix for distribution, reference packages, or LLM context sharing. Invoke it if security concerns exist about exposing credentials in the packed output.
How does repomix-safe-mixer differ from plain repomix?
repomix-safe-mixer adds an automated secret detection and removal gate before repomix concatenates the repository. Plain repomix will include hardcoded secrets unless you manually scrub files first.