Security Recipes¶
Ready-to-use scripts for security scanning and remediation.
Quick Security Scan¶
#!/usr/bin/env python
"""Quick security scan of codebase."""
from rejig import Rejig
def security_scan(path: str = "src/") -> int:
rj = Rejig(path)
security = rj.find_security_issues()
print("SECURITY SCAN RESULTS")
print("=" * 60)
print()
if not security:
print("No security issues found!")
return 0
print(security.summary())
print()
# Critical issues
critical = security.critical()
if critical:
print("CRITICAL ISSUES:")
print("-" * 40)
for issue in critical:
print(f" {issue.file_path}:{issue.line_number}")
print(f" Type: {issue.type}")
print(f" {issue.message}")
if issue.code_snippet:
print(f" Code: {issue.code_snippet[:60]}...")
print()
# High issues
high = security.high()
if high:
print("HIGH SEVERITY ISSUES:")
print("-" * 40)
for issue in high:
print(f" {issue.file_path}:{issue.line_number}")
print(f" Type: {issue.type}")
print(f" {issue.message}")
print()
# Return exit code
if critical or high:
return 1
return 0
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
sys.exit(security_scan(path))
Secrets Scanner¶
#!/usr/bin/env python
"""Scan for hardcoded secrets."""
from rejig import Rejig, SecretsScanner
def scan_secrets(path: str = "src/") -> None:
rj = Rejig(path)
scanner = SecretsScanner(rj)
secrets = scanner.find_hardcoded_secrets()
print("SECRETS SCAN")
print("=" * 60)
print()
if not secrets:
print("No hardcoded secrets detected!")
return
print(f"Found {len(secrets)} potential secrets:\n")
# Group by type (SecurityType enum)
by_type = {}
for secret in secrets:
by_type.setdefault(secret.type, []).append(secret)
for secret_type, items in sorted(by_type.items(), key=lambda x: x[0].name):
print(f"{secret_type.name} ({len(items)}):")
for item in items[:5]:
print(f" {item.file_path}:{item.line_number}")
print(f" Severity: {item.severity}")
# Don't print the actual secret value!
print(f" Pattern matched: {item.name}")
if len(items) > 5:
print(f" ... and {len(items) - 5} more")
print()
print("\nRecommendations:")
print(" 1. Move secrets to environment variables")
print(" 2. Use a secrets manager (AWS Secrets Manager, HashiCorp Vault)")
print(" 3. Add detected files to .gitignore if they contain real secrets")
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
scan_secrets(path)
SQL Injection Finder¶
#!/usr/bin/env python
"""Find potential SQL injection vulnerabilities."""
from rejig import Rejig, VulnerabilityScanner
def find_sql_injection(path: str = "src/") -> None:
rj = Rejig(path)
scanner = VulnerabilityScanner(rj)
issues = scanner.find_sql_injection_risks()
print("SQL INJECTION SCAN")
print("=" * 60)
print()
if not issues:
print("No SQL injection patterns detected!")
return
print(f"Found {len(issues)} potential SQL injection points:\n")
for issue in issues:
print(f"{issue.file_path}:{issue.line_number}")
print(f" Code: {issue.code_snippet}")
print(f" Risk: {issue.severity}")
print()
print("Remediation:")
print(" - Use parameterized queries")
print(" - Use ORM methods instead of raw SQL")
print(" - Validate and sanitize all user input")
print()
print("Example fix:")
print(" UNSAFE: cursor.execute(f\"SELECT * FROM users WHERE id = {user_id}\")")
print(" SAFE: cursor.execute(\"SELECT * FROM users WHERE id = %s\", (user_id,))")
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
find_sql_injection(path)
Command Injection Finder¶
#!/usr/bin/env python
"""Find potential command injection vulnerabilities."""
from rejig import Rejig, VulnerabilityScanner
def find_command_injection(path: str = "src/") -> None:
rj = Rejig(path)
scanner = VulnerabilityScanner(rj)
issues = scanner.find_shell_injection_risks()
print("COMMAND INJECTION SCAN")
print("=" * 60)
print()
if not issues:
print("No command injection patterns detected!")
return
print(f"Found {len(issues)} potential command injection points:\n")
for issue in issues:
print(f"{issue.file_path}:{issue.line_number}")
print(f" Code: {issue.code_snippet}")
print(f" Risk: {issue.severity}")
print()
print("Remediation:")
print(" - Use subprocess with shell=False")
print(" - Pass arguments as a list, not a string")
print(" - Avoid os.system(), use subprocess instead")
print()
print("Example fix:")
print(" UNSAFE: os.system(f'ls {user_dir}')")
print(" SAFE: subprocess.run(['ls', user_dir], shell=False)")
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
find_command_injection(path)
Unsafe Deserialization Finder¶
#!/usr/bin/env python
"""Find unsafe pickle/yaml usage."""
from rejig import Rejig, VulnerabilityScanner
def find_unsafe_deserialize(path: str = "src/") -> None:
rj = Rejig(path)
scanner = VulnerabilityScanner(rj)
yaml_issues = scanner.find_unsafe_yaml_load()
pickle_issues = scanner.find_unsafe_pickle()
print("UNSAFE DESERIALIZATION SCAN")
print("=" * 60)
print()
if not yaml_issues and not pickle_issues:
print("No unsafe deserialization patterns detected!")
return
if yaml_issues:
print(f"Unsafe YAML usage ({len(yaml_issues)}):")
print("-" * 40)
for issue in yaml_issues:
print(f" {issue.file_path}:{issue.line_number}")
print(f" {issue.code_snippet}")
print()
print(" Fix: Use yaml.safe_load() instead of yaml.load()")
print()
if pickle_issues:
print(f"Unsafe Pickle usage ({len(pickle_issues)}):")
print("-" * 40)
for issue in pickle_issues:
print(f" {issue.file_path}:{issue.line_number}")
print(f" {issue.code_snippet}")
print()
print(" Fix: Use JSON or other safe serialization formats")
print(" Never unpickle data from untrusted sources")
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
find_unsafe_deserialize(path)
Security Report Generator¶
#!/usr/bin/env python
"""Generate comprehensive security report."""
from rejig import Rejig
def generate_security_report(
path: str = "src/",
output_format: str = "json",
output_file: str = "security-report",
) -> None:
rj = Rejig(path)
print("Generating security report...")
# Supported formats: "json", "markdown", "sarif"
extensions = {"json": "json", "markdown": "md", "sarif": "sarif"}
ext = extensions.get(output_format, "json")
filename = f"{output_file}.{ext}"
result = rj.generate_security_report(filename, format=output_format)
if result.success:
print(f"Report saved to: {filename}")
else:
print(f"Failed: {result.message}")
# Print summary
print()
print("SUMMARY")
print("=" * 40)
print(rj.find_security_issues().summary())
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
fmt = sys.argv[2] if len(sys.argv) > 2 else "json"
generate_security_report(path, fmt)
Pre-commit Security Hook¶
#!/usr/bin/env python
"""Pre-commit hook for security checks."""
import subprocess
import sys
from rejig import Rejig
def get_staged_files() -> list[str]:
"""Get list of staged Python files."""
result = subprocess.run(
["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"],
capture_output=True,
text=True,
)
files = result.stdout.strip().split("\n")
return [f for f in files if f.endswith(".py")]
def check_staged_files() -> int:
"""Check staged files for security issues."""
staged = get_staged_files()
if not staged:
return 0
print(f"Checking {len(staged)} staged files for security issues...")
# Check each file
issues_found = []
for file_path in staged:
rj = Rejig(file_path)
security = rj.find_security_issues()
critical_high = list(security.critical()) + list(security.high())
if critical_high:
issues_found.extend(critical_high)
if issues_found:
print()
print("SECURITY ISSUES FOUND - COMMIT BLOCKED")
print("=" * 50)
for issue in issues_found:
print(f" [{issue.severity}] {issue.file_path}:{issue.line_number}")
print(f" {issue.type}: {issue.message}")
print()
print("Fix these issues before committing.")
print("To bypass (not recommended): git commit --no-verify")
return 1
print("No security issues found.")
return 0
if __name__ == "__main__":
sys.exit(check_staged_files())
Auto-fix Common Issues¶
#!/usr/bin/env python
"""Auto-fix common security issues where safe to do so."""
from rejig import Rejig, SecurityType
def auto_fix_security(path: str = "src/", dry_run: bool = True) -> None:
rj = Rejig(path, dry_run=dry_run)
security = rj.find_security_issues()
print("AUTO-FIX SECURITY ISSUES")
print("=" * 60)
print(f"Mode: {'DRY RUN' if dry_run else 'LIVE'}")
print()
fixed = 0
skipped = 0
for issue in security:
# issue.type is a SecurityType enum member.
if issue.type == SecurityType.UNSAFE_YAML_LOAD:
# Safe to auto-fix: yaml.load -> yaml.safe_load
file_target = issue.to_file_target()
result = file_target.replace_pattern(
r"yaml\.load\(",
"yaml.safe_load(",
)
if result.success:
print(f"FIXED: {issue.file_path}:{issue.line_number}")
print(f" yaml.load -> yaml.safe_load")
fixed += 1
continue
if issue.type == SecurityType.INSECURE_RANDOM:
# Safe to auto-fix: random -> secrets for specific patterns
# This is more complex, skip for now
pass
if issue.type == SecurityType.DEBUG_CODE:
# Can remove debug statements
line = issue.to_line_target()
result = line.delete()
if result.success:
print(f"FIXED: {issue.file_path}:{issue.line_number}")
print(f" Removed debug statement")
fixed += 1
continue
# Most issues need manual review
skipped += 1
print()
print("SUMMARY")
print("-" * 40)
print(f"Fixed: {fixed}")
print(f"Skipped (manual review needed): {skipped}")
if dry_run and fixed > 0:
print()
print("Run with --apply to apply fixes:")
print(f" python {__file__} {path} --apply")
if __name__ == "__main__":
import sys
path = sys.argv[1] if len(sys.argv) > 1 else "src/"
dry_run = "--apply" not in sys.argv
auto_fix_security(path, dry_run)
GitHub Actions Security Workflow¶
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: pip install rejig
- name: Run security scan
id: security
run: |
python -c "
from rejig import Rejig
rj = Rejig('src/')
# Generate SARIF for GitHub
rj.generate_security_report('security.sarif', format='sarif')
# Check for critical/high issues
security = rj.find_security_issues()
critical_high = len(security.critical()) + len(security.high())
print(f'::set-output name=critical_high::{critical_high}')
if critical_high:
print('::error::Found critical/high security issues')
exit(1)
"
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: security.sarif
- name: Comment on PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## Security Issues Found\n\nThis PR introduces security issues that must be resolved before merging.\n\nSee the Security tab for details.'
})