Files
daymade 8a4c7cfb10 Release v1.7.0: Add repomix-safe-mixer skill
Add new security-focused skill for safely packaging codebases with repomix
by automatically detecting and removing hardcoded credentials.

New skill: repomix-safe-mixer
- Detects 20+ credential patterns (AWS, Supabase, Stripe, OpenAI, etc.)
- Scan → Report → Pack workflow with automatic blocking
- Standalone security scanner for pre-commit hooks
- Environment variable replacement guidance
- JSON output for CI/CD integration

Also updates:
- skill-creator: Simplified path resolution best practices
- marketplace.json: Version 1.7.0, added repomix-safe-mixer plugin
- README.md: Updated to 14 skills, added repomix-safe-mixer documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 16:48:52 +08:00

164 lines
5.4 KiB
Python

#!/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()