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>
This commit is contained in:
daymade
2025-10-26 16:47:35 +08:00
parent de8b803283
commit 8a4c7cfb10
7 changed files with 977 additions and 8 deletions

View File

@@ -5,8 +5,8 @@
"email": "daymadev89@gmail.com"
},
"metadata": {
"description": "Professional Claude Code skills for GitHub operations, document conversion, diagram generation, statusline customization, Teams communication, repomix utilities, skill creation, CLI demo generation, LLM icon access, Cloudflare troubleshooting, UI design system extraction, professional presentation creation, and YouTube video downloading",
"version": "1.6.0",
"description": "Professional Claude Code skills for GitHub operations, document conversion, diagram generation, statusline customization, Teams communication, repomix utilities, skill creation, CLI demo generation, LLM icon access, Cloudflare troubleshooting, UI design system extraction, professional presentation creation, YouTube video downloading, and secure repomix packaging",
"version": "1.7.0",
"homepage": "https://github.com/daymade/claude-code-skills"
},
"plugins": [
@@ -139,6 +139,16 @@
"category": "utilities",
"keywords": ["youtube", "yt-dlp", "video-download", "audio-extraction", "mp3", "download", "youtube-dl"],
"skills": ["./youtube-downloader"]
},
{
"name": "repomix-safe-mixer",
"description": "Safely package codebases with repomix by automatically detecting and removing hardcoded credentials before packing. Use when packaging code for distribution, creating reference packages, or when the user mentions security concerns about sharing code with repomix",
"source": "./",
"strict": false,
"version": "1.0.0",
"category": "security",
"keywords": ["repomix", "security", "credentials", "secrets-scanning", "safe-packaging", "secret-detection", "code-security"],
"skills": ["./repomix-safe-mixer"]
}
]
}

View File

@@ -6,15 +6,15 @@
[![简体中文](https://img.shields.io/badge/语言-简体中文-red)](./README.zh-CN.md)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Skills](https://img.shields.io/badge/skills-13-blue.svg)](https://github.com/daymade/claude-code-skills)
[![Version](https://img.shields.io/badge/version-1.6.0-green.svg)](https://github.com/daymade/claude-code-skills)
[![Skills](https://img.shields.io/badge/skills-14-blue.svg)](https://github.com/daymade/claude-code-skills)
[![Version](https://img.shields.io/badge/version-1.7.0-green.svg)](https://github.com/daymade/claude-code-skills)
[![Claude Code](https://img.shields.io/badge/Claude%20Code-2.0.13+-purple.svg)](https://claude.com/code)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)
[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/daymade/claude-code-skills/graphs/commit-activity)
</div>
Professional Claude Code skills marketplace featuring 13 production-ready skills for enhanced development workflows.
Professional Claude Code skills marketplace featuring 14 production-ready skills for enhanced development workflows.
## 📑 Table of Contents
@@ -456,6 +456,31 @@ Download YouTube videos and audio using yt-dlp with robust error handling and au
---
### 13. **repomix-safe-mixer** - Secure Repomix Packaging
Safely package codebases with repomix by automatically detecting and removing hardcoded credentials before packing.
**When to use:**
- Packaging code with repomix for distribution or sharing
- Creating reference packages from proprietary codebases
- Security concerns about accidentally exposing credentials
- Pre-commit security checks for hardcoded secrets
- Auditing codebases for credential exposure
**Key features:**
- 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
- Exclude patterns for false positive handling
**🎬 Live Demo**
*Coming soon*
---
## 🎬 Interactive Demo Gallery
Want to see all demos in one place with click-to-enlarge functionality? Check out our [interactive demo gallery](./demos/index.html) or browse the [demos directory](./demos/).
@@ -471,8 +496,8 @@ Combine **markdown-tools** for document conversion and **mermaid-tools** for dia
### For Team Communication
Use **teams-channel-post-writer** to share knowledge and **statusline-generator** to track costs while working.
### For Repository Management
Use **repomix-unmixer** to extract and validate repomix-packed skills or repositories.
### For Repository Management & Security
Use **repomix-unmixer** to extract and validate repomix-packed skills or repositories. Use **repomix-safe-mixer** to package codebases securely, automatically detecting and blocking hardcoded credentials before distribution.
### For Skill Development
Use **skill-creator** (see [Essential Skill](#-essential-skill-skill-creator) section above) to build, validate, and package your own Claude Code skills following best practices.
@@ -504,6 +529,7 @@ Each skill includes:
- **cli-demo-generator**: See `cli-demo-generator/references/vhs_syntax.md` for VHS syntax and `cli-demo-generator/references/best_practices.md` for demo guidelines
- **ppt-creator**: See `ppt-creator/references/WORKFLOW.md` for 9-stage creation process and `ppt-creator/references/ORCHESTRATION_OVERVIEW.md` for automation
- **youtube-downloader**: See `youtube-downloader/SKILL.md` for usage examples and troubleshooting
- **repomix-safe-mixer**: See `repomix-safe-mixer/references/common_secrets.md` for detected credential patterns
## 🛠️ Requirements
@@ -518,6 +544,7 @@ Each skill includes:
- **ccusage** (optional, for statusline cost tracking)
- **pandas & matplotlib** (optional, for ppt-creator chart generation)
- **Marp CLI** (optional, for ppt-creator Marp PPTX export): `npm install -g @marp-team/marp-cli`
- **repomix** (for repomix-safe-mixer): `npm install -g repomix`
## ❓ FAQ

315
repomix-safe-mixer/SKILL.md Normal file
View File

@@ -0,0 +1,315 @@
---
name: repomix-safe-mixer
description: Safely package codebases with repomix by automatically detecting and removing hardcoded credentials before packing. Use when packaging code for distribution, creating reference packages, or when the user mentions security concerns about sharing code with repomix.
---
# 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.
```bash
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**:
```bash
python3 scripts/safe_pack.py ./my-project
```
**Output 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://ghyttjckzmzdxumxcixe.supabase.co
❌ Cannot pack: Secrets detected!
```
### Options
**Custom output file**:
```bash
python3 scripts/safe_pack.py \
./my-project \
--output package.xml
```
**With repomix config**:
```bash
python3 scripts/safe_pack.py \
./my-project \
--config repomix.config.json
```
**Exclude patterns from scanning**:
```bash
python3 scripts/safe_pack.py \
./my-project \
--exclude '.*test.*' '.*\.example'
```
**Force pack (dangerous, skip scan)**:
```bash
python3 scripts/safe_pack.py \
./my-project \
--force # ⚠️ NOT RECOMMENDED
```
## Standalone Secret Scanning
Use `scan_secrets.py` from this skill's `scripts/` directory for scanning only (without packing).
```bash
python3 scripts/scan_secrets.py <directory>
```
**Use cases**:
- Verify cleanup after removing credentials
- Pre-commit security checks
- Audit existing codebases
**Example**:
```bash
python3 scripts/scan_secrets.py ./my-project
```
**JSON output for programmatic use**:
```bash
python3 scripts/scan_secrets.py \
./my-project \
--json
```
**Exclude patterns**:
```bash
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**:
```javascript
const SUPABASE_URL = "https://ghyttjckzmzdxumxcixe.supabase.co";
const API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
```
**After**:
```javascript
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
```bash
# 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 control
```
### Step 4: Verify Cleanup
Run scanner again to confirm secrets removed:
```bash
python3 scripts/scan_secrets.py ./my-project
```
### Step 5: Safe Pack
Once clean, package safely:
```bash
python3 scripts/safe_pack.py ./my-project
```
## Post-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_KEY`
- `import.meta.env.VITE_API_KEY`
- `Deno.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):
```bash
python3 scripts/safe_pack.py ./project
```
**With repomix config**:
```bash
python3 scripts/safe_pack.py \
./project \
--config repomix.config.json
```
**Custom output location**:
```bash
python3 scripts/safe_pack.py \
./project \
--output ~/Downloads/package-clean.xml
```
The skill runs repomix internally after security validation, passing through config and output options.
## Example Workflows
### Workflow 1: Package a Clean Project
```bash
# Scan and pack in one command
python3 scripts/safe_pack.py \
~/workspace/my-project \
--output ~/Downloads/my-project-package.xml
```
### Workflow 2: Clean and Package a Project with Secrets
```bash
# 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.xml
```
### Workflow 3: Audit Before Commit
```bash
# 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 scanner
- `scripts/safe_pack.py` - Complete scan → pack workflow
**Related Skills**:
- `repomix-unmixer` - Extracts files from repomix packages
- `skill-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.

View File

@@ -0,0 +1,252 @@
# Common Secret Patterns Reference
This document catalogs common credential types detected by the security scanner.
## Table of Contents
- [Cloud Provider Credentials](#cloud-provider-credentials)
- [Database Credentials](#database-credentials)
- [API Keys and Tokens](#api-keys-and-tokens)
- [Authentication Secrets](#authentication-secrets)
- [Common False Positives](#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_secret` or 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: `89ff427005e1767943b5ac257905a280` in `https://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://ghyttjckzmzdxumxcixe.supabase.co`
- Risk: Project identification
**Anon/Public Key**:
- Pattern: JWT token starting with `eyJ`
- Example: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
- 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
// 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):
```javascript
const SUPABASE_URL = "https://ghyttjckzmzdxumxcixe.supabase.co";
const SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
```
**After** (environment variables):
```javascript
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
```bash
# 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
- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [AWS Credentials Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
- [GitHub Secret Scanning Patterns](https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning)

View File

@@ -0,0 +1,163 @@
#!/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()

View File

@@ -0,0 +1,201 @@
#!/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()

View File

@@ -81,9 +81,10 @@ Files not intended to be loaded into context, but rather used within the output
- **Forbidden**: Absolute paths to user directories (`/home/username/`, `/Users/username/`, `/mnt/c/Users/username/`)
- **Forbidden**: Personal usernames, company names, department names, product names
- **Forbidden**: OneDrive paths, cloud storage paths, or any environment-specific absolute paths
- **Forbidden**: Hardcoded skill installation paths like `~/.claude/skills/` or `/Users/username/Workspace/claude-code-skills/`
- **Allowed**: Relative paths within the skill bundle (`scripts/example.py`, `references/guide.md`)
- **Allowed**: Standard placeholders (`~/workspace/project`, `username`, `your-company`)
- **Best practice**: Use generic examples and placeholders; all paths should reference bundled skill files or use standard environment-agnostic patterns
- **Best practice**: Reference bundled scripts using simple relative paths like `scripts/script_name.py` - Claude will resolve the actual location
##### Versioning