"""007 Full Audit -- Comprehensive 6-phase security audit orchestrator. Executes the complete 007 security audit pipeline: Phase 1: Surface Mapping -- file inventory, entry points, dependencies Phase 2: Threat Modeling Hints -- identify components for STRIDE analysis Phase 3: Security Checklist -- run all scanners, compile results Phase 4: Red Team Scenarios -- template-based attack scenarios Phase 5: Blue Team Recs -- hardening recommendations per finding Phase 6: Verdict -- compute score and emit final verdict Generates a comprehensive Markdown report saved to data/reports/ and prints a summary to stdout. Usage: python full_audit.py --target /path/to/project python full_audit.py --target /path/to/project --output markdown python full_audit.py --target /path/to/project --phase 3 --verbose python full_audit.py --target /path/to/project --output json """ import argparse import json import os import re import sys import time from datetime import datetime, timezone from pathlib import Path # --------------------------------------------------------------------------- # Imports from the 007 config hub (same directory) # --------------------------------------------------------------------------- sys.path.insert(0, str(Path(__file__).resolve().parent)) from config import ( # noqa: E402 BASE_DIR, DATA_DIR, REPORTS_DIR, SCANNABLE_EXTENSIONS, SKIP_DIRECTORIES, SCORING_WEIGHTS, SCORING_LABELS, SEVERITY, LIMITS, ensure_directories, get_verdict, get_timestamp, log_audit_event, setup_logging, calculate_weighted_score, ) # --------------------------------------------------------------------------- # Import scanners # --------------------------------------------------------------------------- sys.path.insert(0, str(Path(__file__).resolve().parent / "scanners")) import secrets_scanner # noqa: E402 import dependency_scanner # noqa: E402 import injection_scanner # noqa: E402 import quick_scan # noqa: E402 import score_calculator # noqa: E402 # --------------------------------------------------------------------------- # Logger # --------------------------------------------------------------------------- logger = setup_logging("007-full-audit") # ========================================================================= # RED TEAM SCENARIO TEMPLATES # ========================================================================= # Mapping from finding type/pattern -> attack scenario template. _RED_TEAM_TEMPLATES: dict[str, dict] = { # --- Secrets --- "secret": { "title": "Credential Theft via Leaked Secret", "persona": "External attacker / Insider", "scenario": ( "Attacker discovers leaked credential ({pattern}) in {file} " "and uses it to gain unauthorized access to the associated " "service or resource. Depending on the credential scope, " "the attacker may escalate to full account takeover." ), "impact": "Unauthorized access, data exfiltration, lateral movement", "difficulty": "Easy (if credential is in public repo) / Medium (if private)", }, # --- Injection --- "code_injection": { "title": "Remote Code Execution via Code Injection", "persona": "Malicious user / Compromised agent", "scenario": ( "Attacker crafts malicious input targeting {pattern} in {file}. " "The injected code executes in the server context, allowing " "arbitrary command execution, data access, or system compromise." ), "impact": "Full server compromise, data breach, service disruption", "difficulty": "Medium", }, "command_injection": { "title": "System Compromise via Command Injection", "persona": "Malicious user / API abuser", "scenario": ( "Attacker injects OS commands through {pattern} in {file}. " "The shell executes attacker-controlled commands, enabling " "file access, reverse shells, or privilege escalation." ), "impact": "Full system compromise, lateral movement", "difficulty": "Medium", }, "sql_injection": { "title": "Data Breach via SQL Injection", "persona": "Malicious user / Bot", "scenario": ( "Attacker crafts SQL payload targeting {pattern} in {file}. " "The malformed query bypasses authentication, extracts sensitive " "data, modifies records, or drops tables." ), "impact": "Data breach, data loss, authentication bypass", "difficulty": "Easy to Medium", }, "prompt_injection": { "title": "AI Manipulation via Prompt Injection", "persona": "Malicious user / Compromised data source", "scenario": ( "Attacker injects adversarial prompt through {pattern} in {file}. " "The LLM follows injected instructions, potentially exfiltrating " "data, bypassing safety controls, or performing unauthorized actions." ), "impact": "Data leakage, unauthorized actions, reputation damage", "difficulty": "Easy to Medium", }, "xss": { "title": "User Account Takeover via XSS", "persona": "Malicious user", "scenario": ( "Attacker injects JavaScript through {pattern} in {file}. " "The script executes in victim browsers, stealing session tokens, " "redirecting users, or performing actions on their behalf." ), "impact": "Session hijacking, credential theft, phishing", "difficulty": "Easy", }, "ssrf": { "title": "Internal Network Scanning via SSRF", "persona": "External attacker", "scenario": ( "Attacker manipulates server-side request through {pattern} in {file}. " "The server makes requests to internal services, cloud metadata endpoints, " "or other internal resources on the attacker's behalf." ), "impact": "Internal network exposure, cloud credential theft, data access", "difficulty": "Medium", }, "path_traversal": { "title": "Sensitive File Access via Path Traversal", "persona": "Malicious user", "scenario": ( "Attacker uses directory traversal sequences (../) through {pattern} " "in {file} to access files outside the intended directory, " "including configuration files, credentials, or system files." ), "impact": "Credential exposure, configuration leak, source code theft", "difficulty": "Easy", }, # --- Dependencies --- "dependency": { "title": "Supply Chain Attack via Vulnerable Dependency", "persona": "Supply chain attacker", "scenario": ( "Attacker compromises a dependency ({pattern}) used in {file}. " "Malicious code in the dependency executes during install or runtime, " "exfiltrating secrets, installing backdoors, or modifying behavior." ), "impact": "Full compromise, backdoor installation, data exfiltration", "difficulty": "Hard (requires compromising upstream package)", }, # --- Auth missing --- "no_auth": { "title": "Unauthorized Access to Unprotected Endpoints", "persona": "Any external attacker / Bot", "scenario": ( "Attacker discovers unprotected API endpoints or routes " "with no authentication middleware. Direct access allows " "data extraction, modification, or service abuse without credentials." ), "impact": "Data breach, unauthorized actions, resource abuse", "difficulty": "Easy", }, # --- Dangerous code --- "dangerous_code": { "title": "Exploitation of Dangerous Code Pattern", "persona": "Malicious user / Insider", "scenario": ( "Attacker exploits dangerous code construct ({pattern}) in {file}. " "The construct allows unintended behavior such as arbitrary code " "execution, deserialization attacks, or unsafe data processing." ), "impact": "Code execution, data manipulation, service disruption", "difficulty": "Medium", }, } # Fallback template for finding types not explicitly mapped _RED_TEAM_FALLBACK = { "title": "Exploitation of Security Weakness", "persona": "Opportunistic attacker", "scenario": ( "Attacker discovers and exploits security weakness ({pattern}) " "in {file}. The specific impact depends on the context and " "the attacker's capabilities." ), "impact": "Variable -- depends on finding severity and context", "difficulty": "Variable", } # ========================================================================= # BLUE TEAM RECOMMENDATION TEMPLATES # ========================================================================= _BLUE_TEAM_TEMPLATES: dict[str, dict] = { "secret": { "recommendation": ( "Move secrets to environment variables, a secrets manager (e.g. AWS " "Secrets Manager, HashiCorp Vault), or a .env file excluded from " "version control. Add a pre-commit hook (e.g. detect-secrets, " "gitleaks) to prevent future leaks. Rotate the compromised credential " "immediately." ), "priority": "CRITICAL", "effort": "Low", }, "code_injection": { "recommendation": ( "Remove all uses of eval(), exec(), and Function(). If dynamic " "code execution is absolutely necessary, use a sandboxed environment " "(e.g. RestrictedPython, vm2 in strict mode) with allowlisted " "operations only. Never pass user input to code execution functions." ), "priority": "CRITICAL", "effort": "Medium", }, "command_injection": { "recommendation": ( "Replace os.system(), os.popen(), and subprocess with shell=True " "with subprocess.run() using shell=False and a list of arguments. " "Never concatenate user input into shell commands. Validate and " "sanitize all inputs. Use shlex.quote() if shell is unavoidable." ), "priority": "CRITICAL", "effort": "Low to Medium", }, "sql_injection": { "recommendation": ( "Use parameterized queries (placeholders) for ALL database operations. " "Never use f-strings, .format(), or string concatenation in SQL. " "Use an ORM (SQLAlchemy, Django ORM) when possible. Add input " "validation and type checking before database operations." ), "priority": "CRITICAL", "effort": "Low", }, "prompt_injection": { "recommendation": ( "Separate system prompts from user content using proper message " "structure (system/user/assistant roles). Never concatenate user " "input directly into system prompts. Add input sanitization, " "output filtering, and content safety guardrails. Limit tool " "access and implement output validation." ), "priority": "HIGH", "effort": "Medium", }, "xss": { "recommendation": ( "Never set innerHTML or use dangerouslySetInnerHTML with user content. " "Use textContent for safe text insertion. Implement Content Security " "Policy (CSP) headers. Use template engines with auto-escaping " "(Jinja2 with autoescape, React JSX). Sanitize user HTML with " "DOMPurify or bleach." ), "priority": "HIGH", "effort": "Low to Medium", }, "ssrf": { "recommendation": ( "Implement URL allowlisting for outbound requests. Block requests " "to private IP ranges (10.x, 172.16-31.x, 192.168.x), localhost, " "and cloud metadata endpoints (169.254.169.254). Validate and " "parse URLs before making requests. Use a dedicated HTTP client " "with SSRF protections." ), "priority": "HIGH", "effort": "Medium", }, "path_traversal": { "recommendation": ( "Use Path.resolve() and verify the resolved path starts with the " "expected base directory. Never pass raw user input to open() or " "file operations. Use os.path.realpath() followed by a prefix check. " "Implement a file access allowlist." ), "priority": "HIGH", "effort": "Low", }, "dependency": { "recommendation": ( "Pin all dependency versions with exact versions (not ranges). " "Use lock files (pip freeze, package-lock.json, poetry.lock). " "Run regular vulnerability scans (safety, npm audit, Snyk). " "Remove unused dependencies. Verify package integrity with hashes." ), "priority": "MEDIUM", "effort": "Low", }, "dangerous_code": { "recommendation": ( "Replace dangerous constructs with safe alternatives: " "pickle -> json, yaml.load -> yaml.safe_load, eval -> ast.literal_eval " "(for literals only). Add input validation before any dynamic operation. " "Implement proper error handling and type checking." ), "priority": "HIGH", "effort": "Low to Medium", }, "no_auth": { "recommendation": ( "Add authentication middleware to all endpoints except public " "health checks. Implement RBAC/ABAC for authorization. Use " "established auth libraries (Flask-Login, Passport.js, Django auth). " "Add rate limiting to prevent brute force attacks." ), "priority": "CRITICAL", "effort": "Medium", }, "permission": { "recommendation": ( "Set restrictive file permissions (600 for secrets, 644 for configs, " "755 for executables). Never use 777. Run services as non-root users. " "Use chown/chmod to enforce ownership." ), "priority": "MEDIUM", "effort": "Low", }, "large_file": { "recommendation": ( "Investigate oversized files for accidentally committed binaries, " "databases, or data dumps. Add them to .gitignore. Use Git LFS " "for legitimate large files." ), "priority": "LOW", "effort": "Low", }, } _BLUE_TEAM_FALLBACK = { "recommendation": ( "Review the finding in context and apply the principle of least " "privilege. Add input validation, proper error handling, and " "logging. Consult OWASP guidelines for the specific vulnerability type." ), "priority": "MEDIUM", "effort": "Variable", } # ========================================================================= # PHASE IMPLEMENTATIONS # ========================================================================= def _phase1_surface_mapping(target: Path, verbose: bool = False) -> dict: """Phase 1: Surface Mapping -- inventory files, entry points, dependencies.""" logger.info("Phase 1: Surface Mapping") files_by_type: dict[str, int] = {} entry_points: list[str] = [] dependency_files: list[str] = [] config_files: list[str] = [] total_files = 0 _entry_point_patterns = [ re.compile(r"""(?i)(?:^main\.py|^app\.py|^server\.py|^index\.\w+|^manage\.py)"""), re.compile(r"""(?i)(?:^wsgi\.py|^asgi\.py|^gunicorn|^uvicorn)"""), re.compile(r"""(?i)(?:^Dockerfile|^docker-compose)"""), re.compile(r"""(?i)(?:\.github[/\\]workflows|Jenkinsfile|\.gitlab-ci)"""), ] _dep_file_names = { "requirements.txt", "requirements-dev.txt", "requirements-test.txt", "setup.py", "setup.cfg", "pyproject.toml", "Pipfile", "Pipfile.lock", "package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "go.mod", "go.sum", "Cargo.toml", "Cargo.lock", "Gemfile", "Gemfile.lock", "composer.json", "composer.lock", } _config_extensions = {".json", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf", ".env"} for root, dirs, filenames in os.walk(target): dirs[:] = [d for d in dirs if d not in SKIP_DIRECTORIES] for fname in filenames: total_files += 1 fpath = Path(root) / fname suffix = fpath.suffix.lower() # Categorize by extension ext_key = suffix if suffix else "(no extension)" files_by_type[ext_key] = files_by_type.get(ext_key, 0) + 1 # Detect entry points for pat in _entry_point_patterns: if pat.search(fname) or pat.search(str(fpath)): entry_points.append(str(fpath)) break # Detect dependency files if fname.lower() in _dep_file_names: dependency_files.append(str(fpath)) # Detect config files if suffix in _config_extensions or fname.lower().startswith(".env"): config_files.append(str(fpath)) # Sort by count descending sorted_types = sorted(files_by_type.items(), key=lambda x: x[1], reverse=True) return { "total_files": total_files, "files_by_type": dict(sorted_types), "entry_points": sorted(set(entry_points)), "dependency_files": sorted(set(dependency_files)), "config_files": sorted(set(config_files)), } def _phase2_threat_modeling_hints(surface_map: dict, findings: list[dict]) -> dict: """Phase 2: Threat Modeling Hints -- identify components for STRIDE analysis.""" logger.info("Phase 2: Threat Modeling Hints") components: list[dict] = [] # Entry points are high-value STRIDE targets for ep in surface_map.get("entry_points", []): components.append({ "component": ep, "type": "entry_point", "stride_focus": ["Spoofing", "Tampering", "Elevation of Privilege"], "reason": "Application entry point -- critical for authentication and authorization", }) # Dependency files = supply chain for dep_file in surface_map.get("dependency_files", []): components.append({ "component": dep_file, "type": "dependency_manifest", "stride_focus": ["Tampering", "Elevation of Privilege"], "reason": "Dependency manifest -- supply chain attack vector", }) # Config files = information disclosure for cfg in surface_map.get("config_files", []): components.append({ "component": cfg, "type": "configuration", "stride_focus": ["Information Disclosure", "Tampering"], "reason": "Configuration file -- may contain secrets or security settings", }) # Files with critical findings critical_files: set[str] = set() for f in findings: if f.get("severity") in ("CRITICAL", "HIGH"): critical_files.add(f.get("file", "")) for cf in sorted(critical_files): if cf: components.append({ "component": cf, "type": "high_risk_source", "stride_focus": [ "Spoofing", "Tampering", "Repudiation", "Information Disclosure", "Denial of Service", "Elevation of Privilege", ], "reason": "Source file with CRITICAL/HIGH severity findings", }) return { "components_for_stride": components, "total_components": len(components), "recommendation": ( "Run a formal STRIDE analysis on each component above. " "For each STRIDE category, document: attack vector, impact (1-5), " "probability (1-5), and proposed mitigation." ), } def _phase3_security_checklist( secrets_report: dict, dep_report: dict, inj_report: dict, quick_report: dict, ) -> dict: """Phase 3: Security Checklist -- compile all scanner results.""" logger.info("Phase 3: Security Checklist") checklist: list[dict] = [] # Secrets check secrets_count = secrets_report.get("total_findings", 0) checklist.append({ "check": "No hardcoded secrets in source code", "status": "PASS" if secrets_count == 0 else "FAIL", "details": f"{secrets_count} secret(s) detected", "scanner": "secrets_scanner", }) # Dependency check dep_score = dep_report.get("score", 0) dep_count = dep_report.get("total_findings", 0) checklist.append({ "check": "Dependencies are secure and pinned", "status": "PASS" if dep_score >= 80 else ("WARN" if dep_score >= 50 else "FAIL"), "details": f"{dep_count} finding(s), score={dep_score}", "scanner": "dependency_scanner", }) # Injection check inj_count = inj_report.get("total_findings", 0) inj_critical = inj_report.get("severity_counts", {}).get("CRITICAL", 0) checklist.append({ "check": "No injection vulnerabilities", "status": "PASS" if inj_count == 0 else ("FAIL" if inj_critical > 0 else "WARN"), "details": f"{inj_count} finding(s), {inj_critical} CRITICAL", "scanner": "injection_scanner", }) # Quick scan check quick_score = quick_report.get("score", 0) quick_count = quick_report.get("total_findings", 0) checklist.append({ "check": "No dangerous code patterns", "status": "PASS" if quick_score >= 80 else ("WARN" if quick_score >= 50 else "FAIL"), "details": f"{quick_count} finding(s), score={quick_score}", "scanner": "quick_scan", }) # Summary counts pass_count = sum(1 for c in checklist if c["status"] == "PASS") warn_count = sum(1 for c in checklist if c["status"] == "WARN") fail_count = sum(1 for c in checklist if c["status"] == "FAIL") return { "checklist": checklist, "summary": { "pass": pass_count, "warn": warn_count, "fail": fail_count, "total": len(checklist), }, } def _phase4_red_team_scenarios(all_findings: list[dict], auth_score: float) -> dict: """Phase 4: Red Team Scenarios -- generate attack scenarios from findings.""" logger.info("Phase 4: Red Team Scenarios") scenarios: list[dict] = [] seen_types: set[str] = set() # Generate scenarios from findings (one per unique type+file combination, # capped to keep the report manageable) MAX_SCENARIOS = 20 # Sort by severity so we get the most critical first severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3, "INFO": 4} sorted_findings = sorted( all_findings, key=lambda f: severity_order.get(f.get("severity", "INFO"), 5), ) for finding in sorted_findings: if len(scenarios) >= MAX_SCENARIOS: break # Determine the template key finding_type = finding.get("type", "") injection_type = finding.get("injection_type", "") pattern = finding.get("pattern", "unknown") file_path = finding.get("file", "unknown") # Choose best template if injection_type and injection_type in _RED_TEAM_TEMPLATES: template_key = injection_type elif finding_type in _RED_TEAM_TEMPLATES: template_key = finding_type else: template_key = None template = ( _RED_TEAM_TEMPLATES.get(template_key, _RED_TEAM_FALLBACK) if template_key else _RED_TEAM_FALLBACK ) # Deduplicate: one scenario per (template_key, file) pair dedup_key = f"{template_key or finding_type}:{file_path}" if dedup_key in seen_types: continue seen_types.add(dedup_key) # Interpolate template scenario_text = template["scenario"].format( pattern=pattern, file=file_path, ) scenarios.append({ "title": template["title"], "persona": template["persona"], "scenario": scenario_text, "impact": template["impact"], "difficulty": template["difficulty"], "severity": finding.get("severity", "MEDIUM"), "source_finding": { "type": finding_type, "pattern": pattern, "file": file_path, "line": finding.get("line", 0), }, }) # Add no-auth scenario if auth score is low if auth_score < 40 and "no_auth" not in seen_types: template = _RED_TEAM_TEMPLATES["no_auth"] scenarios.append({ "title": template["title"], "persona": template["persona"], "scenario": template["scenario"], "impact": template["impact"], "difficulty": template["difficulty"], "severity": "HIGH", "source_finding": { "type": "architectural", "pattern": "missing_auth", "file": "(project-wide)", "line": 0, }, }) return { "scenarios": scenarios, "total_scenarios": len(scenarios), } def _phase5_blue_team_recommendations(all_findings: list[dict], auth_score: float) -> dict: """Phase 5: Blue Team Recommendations -- hardening advice per finding type.""" logger.info("Phase 5: Blue Team Recommendations") recommendations: list[dict] = [] seen_types: set[str] = set() # Group findings by type for consolidated recommendations for finding in all_findings: finding_type = finding.get("type", "") injection_type = finding.get("injection_type", "") # Choose best template key if injection_type and injection_type in _BLUE_TEAM_TEMPLATES: rec_key = injection_type elif finding_type in _BLUE_TEAM_TEMPLATES: rec_key = finding_type else: rec_key = None if rec_key and rec_key not in seen_types: seen_types.add(rec_key) template = _BLUE_TEAM_TEMPLATES[rec_key] # Count affected findings affected = [ f for f in all_findings if f.get("injection_type", "") == rec_key or f.get("type", "") == rec_key ] recommendations.append({ "category": rec_key, "recommendation": template["recommendation"], "priority": template["priority"], "effort": template["effort"], "affected_findings": len(affected), "example_files": sorted(set( f.get("file", "") for f in affected[:5] )), }) # Add no-auth recommendation if applicable if auth_score < 40 and "no_auth" not in seen_types: template = _BLUE_TEAM_TEMPLATES["no_auth"] recommendations.append({ "category": "no_auth", "recommendation": template["recommendation"], "priority": template["priority"], "effort": template["effort"], "affected_findings": 0, "example_files": [], }) # Sort by priority (CRITICAL first) priority_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3} recommendations.sort(key=lambda r: priority_order.get(r["priority"], 5)) return { "recommendations": recommendations, "total_recommendations": len(recommendations), } def _phase6_verdict( target_str: str, all_findings: list[dict], source_files: list[Path], total_source_files: int, secrets_report: dict, dep_report: dict, inj_report: dict, quick_report: dict, ) -> dict: """Phase 6: Verdict -- compute score and emit final verdict.""" logger.info("Phase 6: Verdict") domain_scores = score_calculator.compute_domain_scores( secrets_findings=secrets_report.get("findings", []), injection_findings=inj_report.get("findings", []), dependency_report=dep_report, quick_findings=quick_report.get("findings", []), source_files=source_files, total_source_files=total_source_files, ) final_score = calculate_weighted_score(domain_scores) verdict = get_verdict(final_score) return { "domain_scores": domain_scores, "final_score": final_score, "verdict": { "label": verdict["label"], "description": verdict["description"], "emoji": verdict["emoji"], }, } # ========================================================================= # REPORT GENERATION # ========================================================================= def _generate_markdown_report( target: str, phases: dict, elapsed: float, phases_run: list[int], ) -> str: """Generate a comprehensive Markdown audit report.""" lines: list[str] = [] ts = get_timestamp() lines.append("# 007 -- Full Security Audit Report") lines.append("") lines.append(f"**Target:** `{target}`") lines.append(f"**Timestamp:** {ts}") lines.append(f"**Duration:** {elapsed:.2f}s") lines.append(f"**Phases executed:** {', '.join(str(p) for p in phases_run)}") lines.append("") lines.append("---") lines.append("") # Phase 1: Surface Mapping if 1 in phases_run and "phase1" in phases: p1 = phases["phase1"] lines.append("## Phase 1: Surface Mapping") lines.append("") lines.append(f"**Total files:** {p1.get('total_files', 0)}") lines.append("") # Files by type fbt = p1.get("files_by_type", {}) if fbt: lines.append("### File Types") lines.append("") lines.append("| Extension | Count |") lines.append("|-----------|-------|") for ext, count in list(fbt.items())[:20]: lines.append(f"| `{ext}` | {count} |") lines.append("") # Entry points eps = p1.get("entry_points", []) if eps: lines.append("### Entry Points") lines.append("") for ep in eps: lines.append(f"- `{ep}`") lines.append("") # Dependency files dfs = p1.get("dependency_files", []) if dfs: lines.append("### Dependency Files") lines.append("") for df in dfs: lines.append(f"- `{df}`") lines.append("") lines.append("---") lines.append("") # Phase 2: Threat Modeling Hints if 2 in phases_run and "phase2" in phases: p2 = phases["phase2"] lines.append("## Phase 2: Threat Modeling Hints") lines.append("") lines.append(f"**Components identified for STRIDE analysis:** {p2.get('total_components', 0)}") lines.append("") for comp in p2.get("components_for_stride", [])[:30]: lines.append(f"- **`{comp['component']}`** ({comp['type']})") lines.append(f" - STRIDE focus: {', '.join(comp['stride_focus'])}") lines.append(f" - Reason: {comp['reason']}") lines.append("") lines.append(f"> {p2.get('recommendation', '')}") lines.append("") lines.append("---") lines.append("") # Phase 3: Security Checklist if 3 in phases_run and "phase3" in phases: p3 = phases["phase3"] summary = p3.get("summary", {}) lines.append("## Phase 3: Security Checklist") lines.append("") lines.append( f"**Results:** {summary.get('pass', 0)} PASS / " f"{summary.get('warn', 0)} WARN / " f"{summary.get('fail', 0)} FAIL" ) lines.append("") lines.append("| Check | Status | Details | Scanner |") lines.append("|-------|--------|---------|---------|") for item in p3.get("checklist", []): status_icon = {"PASS": "[PASS]", "WARN": "[WARN]", "FAIL": "[FAIL]"}.get( item["status"], item["status"] ) lines.append( f"| {item['check']} | {status_icon} | {item['details']} | {item['scanner']} |" ) lines.append("") lines.append("---") lines.append("") # Phase 4: Red Team Scenarios if 4 in phases_run and "phase4" in phases: p4 = phases["phase4"] lines.append("## Phase 4: Red Team Scenarios") lines.append("") lines.append(f"**Total scenarios:** {p4.get('total_scenarios', 0)}") lines.append("") for i, sc in enumerate(p4.get("scenarios", []), start=1): lines.append(f"### Scenario {i}: {sc['title']}") lines.append("") lines.append(f"- **Persona:** {sc['persona']}") lines.append(f"- **Severity:** {sc['severity']}") lines.append(f"- **Difficulty:** {sc['difficulty']}") lines.append(f"- **Impact:** {sc['impact']}") lines.append(f"- **Description:** {sc['scenario']}") src = sc.get("source_finding", {}) if src.get("file"): lines.append(f"- **Source:** `{src['file']}`:L{src.get('line', 0)} ({src.get('pattern', '')})") lines.append("") lines.append("---") lines.append("") # Phase 5: Blue Team Recommendations if 5 in phases_run and "phase5" in phases: p5 = phases["phase5"] lines.append("## Phase 5: Blue Team Recommendations") lines.append("") lines.append(f"**Total recommendations:** {p5.get('total_recommendations', 0)}") lines.append("") for rec in p5.get("recommendations", []): lines.append(f"### [{rec['priority']}] {rec['category'].replace('_', ' ').title()}") lines.append("") lines.append(f"**Affected findings:** {rec['affected_findings']}") lines.append(f"**Effort:** {rec['effort']}") lines.append("") lines.append(f"{rec['recommendation']}") lines.append("") if rec.get("example_files"): lines.append("**Example files:**") for ef in rec["example_files"]: if ef: lines.append(f"- `{ef}`") lines.append("") lines.append("---") lines.append("") # Phase 6: Verdict if 6 in phases_run and "phase6" in phases: p6 = phases["phase6"] domain_scores = p6.get("domain_scores", {}) final_score = p6.get("final_score", 0) verdict = p6.get("verdict", {}) lines.append("## Phase 6: Verdict") lines.append("") lines.append("### Domain Scores") lines.append("") lines.append("| Domain | Weight | Score |") lines.append("|--------|--------|-------|") for domain, weight in SCORING_WEIGHTS.items(): score = domain_scores.get(domain, 0.0) label = SCORING_LABELS.get(domain, domain) lines.append(f"| {label} | {weight * 100:.0f}% | {score:.1f} |") lines.append("") lines.append(f"### Final Score: **{final_score:.1f} / 100**") lines.append("") lines.append( f"### Verdict: **{verdict.get('emoji', '')} {verdict.get('label', 'N/A')}**" ) lines.append("") lines.append(f"> {verdict.get('description', '')}") lines.append("") lines.append("---") lines.append("") lines.append("*Generated by 007 -- Licenca para Auditar*") lines.append("") return "\n".join(lines) def _generate_text_summary( target: str, phases: dict, elapsed: float, phases_run: list[int], ) -> str: """Generate a concise text summary for stdout.""" lines: list[str] = [] lines.append("=" * 72) lines.append(" 007 FULL SECURITY AUDIT -- SUMMARY") lines.append("=" * 72) lines.append("") lines.append(f" Target: {target}") lines.append(f" Timestamp: {get_timestamp()}") lines.append(f" Duration: {elapsed:.2f}s") lines.append(f" Phases: {', '.join(str(p) for p in phases_run)}") lines.append("") # Phase 1 summary if "phase1" in phases: p1 = phases["phase1"] lines.append(f" Phase 1 -- Surface: {p1.get('total_files', 0)} files, " f"{len(p1.get('entry_points', []))} entry points, " f"{len(p1.get('dependency_files', []))} dep files") # Phase 2 summary if "phase2" in phases: p2 = phases["phase2"] lines.append(f" Phase 2 -- Threat Model Hints: " f"{p2.get('total_components', 0)} components for STRIDE") # Phase 3 summary if "phase3" in phases: p3 = phases["phase3"] summary = p3.get("summary", {}) lines.append( f" Phase 3 -- Checklist: " f"{summary.get('pass', 0)} PASS / " f"{summary.get('warn', 0)} WARN / " f"{summary.get('fail', 0)} FAIL" ) # Phase 4 summary if "phase4" in phases: p4 = phases["phase4"] lines.append(f" Phase 4 -- Red Team: {p4.get('total_scenarios', 0)} attack scenarios") # Phase 5 summary if "phase5" in phases: p5 = phases["phase5"] lines.append(f" Phase 5 -- Blue Team: {p5.get('total_recommendations', 0)} recommendations") # Phase 6 verdict if "phase6" in phases: p6 = phases["phase6"] final_score = p6.get("final_score", 0) verdict = p6.get("verdict", {}) lines.append("") lines.append("-" * 72) lines.append(f" FINAL SCORE: {final_score:.1f} / 100") lines.append(f" VERDICT: {verdict.get('emoji', '')} {verdict.get('label', 'N/A')}") lines.append(f" {verdict.get('description', '')}") lines.append("=" * 72) lines.append("") return "\n".join(lines) # ========================================================================= # MAIN ENTRY POINT # ========================================================================= def run_audit( target_path: str, output_format: str = "text", phases_to_run: str = "all", verbose: bool = False, ) -> dict: """Execute the full 6-phase security audit. Args: target_path: Path to the directory to audit. output_format: 'text', 'json', or 'markdown'. phases_to_run: 'all' or a comma-separated list of phase numbers (e.g. '1,3,6'). verbose: Enable debug-level logging. Returns: JSON-compatible audit report dict. """ if verbose: logger.setLevel("DEBUG") ensure_directories() target = Path(target_path).resolve() if not target.exists(): logger.error("Target path does not exist: %s", target) sys.exit(1) if not target.is_dir(): logger.error("Target is not a directory: %s", target) sys.exit(1) # Parse phases if phases_to_run == "all": phases_list = [1, 2, 3, 4, 5, 6] else: try: phases_list = sorted(set(int(p.strip()) for p in phases_to_run.split(","))) if not all(1 <= p <= 6 for p in phases_list): logger.error("Phase numbers must be between 1 and 6.") sys.exit(1) except ValueError: logger.error("Invalid --phase value. Use 'all' or comma-separated numbers (1-6).") sys.exit(1) logger.info("Starting full audit of %s (phases: %s)", target, phases_list) start_time = time.time() target_str = str(target) # ------------------------------------------------------------------ # Run scanners if needed (phases 3-6 need scanner data) # ------------------------------------------------------------------ need_scanners = any(p in phases_list for p in [3, 4, 5, 6]) secrets_report: dict = {"findings": [], "score": 100, "total_findings": 0} dep_report: dict = {"findings": [], "score": 100, "total_findings": 0} inj_report: dict = {"findings": [], "score": 100, "total_findings": 0} quick_report: dict = {"findings": [], "score": 100, "total_findings": 0} all_findings: list[dict] = [] report_findings: list[dict] = [] if need_scanners: logger.info("Running scanners for phases %s...", [p for p in phases_list if p >= 3]) try: secrets_report = secrets_scanner.run_scan( target_path=target_str, output_format="json", verbose=verbose, ) except SystemExit: pass try: dep_report = dependency_scanner.run_scan( target_path=target_str, output_format="json", verbose=verbose, ) except SystemExit: pass try: inj_report = injection_scanner.run_scan( target_path=target_str, output_format="json", verbose=verbose, ) except SystemExit: pass try: quick_report = quick_scan.run_scan( target_path=target_str, output_format="json", verbose=verbose, ) except SystemExit: pass # Aggregate and deduplicate raw = ( secrets_report.get("findings", []) + dep_report.get("findings", []) + inj_report.get("findings", []) + quick_report.get("findings", []) ) all_findings = score_calculator._deduplicate_findings(raw) report_findings = score_calculator.redact_findings_for_report(all_findings) # ------------------------------------------------------------------ # Collect source files if needed for phase 6 # ------------------------------------------------------------------ source_files: list[Path] = [] total_source_files = 0 if 6 in phases_list: source_files = score_calculator._collect_source_files(target) total_source_files = len(source_files) # ------------------------------------------------------------------ # Execute phases # ------------------------------------------------------------------ phases_data: dict = {} if 1 in phases_list: phases_data["phase1"] = _phase1_surface_mapping(target, verbose=verbose) if 2 in phases_list: # Phase 2 benefits from phase 1 data and findings surface = phases_data.get("phase1") or _phase1_surface_mapping(target, verbose=verbose) phases_data["phase2"] = _phase2_threat_modeling_hints(surface, report_findings) if 3 in phases_list: phases_data["phase3"] = _phase3_security_checklist( secrets_report, dep_report, inj_report, quick_report, ) # Auth score for phases 4 and 5 auth_score = 50.0 if 6 in phases_list or 4 in phases_list or 5 in phases_list: if source_files: auth_count = score_calculator._count_pattern_matches( source_files, score_calculator._AUTH_PATTERNS, ) if auth_count == 0: auth_score = 25.0 else: auth_score = score_calculator._score_from_positive_signals( auth_count, total_source_files, base_score=40, max_score=95, ) if 4 in phases_list: phases_data["phase4"] = _phase4_red_team_scenarios(report_findings, auth_score) if 5 in phases_list: phases_data["phase5"] = _phase5_blue_team_recommendations(report_findings, auth_score) if 6 in phases_list: phases_data["phase6"] = _phase6_verdict( target_str=target_str, all_findings=all_findings, source_files=source_files, total_source_files=total_source_files, secrets_report=secrets_report, dep_report=dep_report, inj_report=inj_report, quick_report=quick_report, ) elapsed = time.time() - start_time # ------------------------------------------------------------------ # Generate and save Markdown report # ------------------------------------------------------------------ md_report = _generate_markdown_report(target_str, phases_data, elapsed, phases_list) ts_file = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H-%M-%S") report_filename = f"audit_{ts_file}.md" report_path = REPORTS_DIR / report_filename try: report_path.write_text(md_report, encoding="utf-8") logger.info("Markdown report saved to %s", report_path) except OSError as exc: logger.warning("Could not save report: %s", exc) # ------------------------------------------------------------------ # Audit log # ------------------------------------------------------------------ verdict_data = phases_data.get("phase6", {}).get("verdict", {}) final_score = phases_data.get("phase6", {}).get("final_score", "N/A") log_audit_event( action="full_audit", target=target_str, result=f"score={final_score}, verdict={verdict_data.get('label', 'N/A')}", details={ "phases_run": phases_list, "total_findings": len(all_findings), "report_path": str(report_path), "duration_seconds": round(elapsed, 3), }, ) # ------------------------------------------------------------------ # Build final report dict # ------------------------------------------------------------------ full_report = { "report": "full_audit", "target": target_str, "timestamp": get_timestamp(), "duration_seconds": round(elapsed, 3), "phases_run": phases_list, "phases": phases_data, "total_findings": len(all_findings), "findings": report_findings, "report_path": str(report_path), } # ------------------------------------------------------------------ # Output # ------------------------------------------------------------------ if output_format == "json": print(json.dumps(full_report, indent=2, ensure_ascii=False)) elif output_format == "markdown": print(md_report) else: print(_generate_text_summary(target_str, phases_data, elapsed, phases_list)) print(f" Full report saved to: {report_path}") print("") return full_report # ========================================================================= # CLI # ========================================================================= if __name__ == "__main__": parser = argparse.ArgumentParser( description=( "007 Full Audit -- Comprehensive 6-phase security audit.\n\n" "Phases:\n" " 1: Surface Mapping -- file inventory, entry points, deps\n" " 2: Threat Modeling Hints -- STRIDE analysis targets\n" " 3: Security Checklist -- run all scanners\n" " 4: Red Team Scenarios -- attack scenario generation\n" " 5: Blue Team Recs -- hardening recommendations\n" " 6: Verdict -- scoring and final verdict" ), epilog=( "Examples:\n" " python full_audit.py --target ./my-project\n" " python full_audit.py --target ./my-project --output markdown\n" " python full_audit.py --target ./my-project --phase 1,3,6\n" " python full_audit.py --target ./my-project --output json --verbose" ), formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "--target", required=True, help="Path to the directory to audit (required).", ) parser.add_argument( "--output", choices=["text", "json", "markdown"], default="text", help="Output format: 'text' (default), 'json', or 'markdown'.", ) parser.add_argument( "--phase", default="all", help=( "Which phases to run: 'all' (default) or comma-separated numbers " "(e.g. '1,3,6'). Range: 1-6." ), ) parser.add_argument( "--verbose", action="store_true", default=False, help="Enable verbose/debug logging.", ) args = parser.parse_args() run_audit( target_path=args.target, output_format=args.output, phases_to_run=args.phase, verbose=args.verbose, )