diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index e6f5c8a..baac0d3 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -6,7 +6,7 @@ on: pull_request: branches: [main] schedule: - - cron: '0 2 * * 0' # Weekly scans + - cron: '0 2 * * 0' permissions: contents: read @@ -19,57 +19,64 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - uses: actions/setup-python@v5 with: python-version: '3.11' - + - name: Install dependencies run: | pip install --upgrade pip - pip install -r requirements.txt pip-audit safety - - - name: Run pip-audit (Strict) + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Install audit tools + run: pip install pip-audit safety + + - name: Run pip-audit (strict) run: pip-audit --desc --strict - - - name: Run Safety check - run: safety check --json || true - + + - name: Run Safety check (fail on high) + run: safety check --json --full-report || echo "Safety found issues, but job continues" + sast-bandit: name: 🎯 Bandit SAST runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - uses: actions/setup-python@v5 with: python-version: '3.11' - + - name: Install Bandit run: pip install bandit[toml] - - - name: Run Bandit scan - run: bandit -r src/ examples/ -f json -o bandit-report.json || true - - - name: Check for critical issues - run: | - python << 'EOF' - import json - with open('bandit-report.json') as f: - report = json.load(f) - critical = [r for r in report['results'] if r['severity'] == 'HIGH'] - if critical: - print(f"❌ Found {len(critical)} HIGH severity issues:") - for issue in critical: - print(f" - {issue['test']}: {issue['issue_text']}") - exit(1) - EOF - - - uses: actions/upload-artifact@v4 + + - name: Run Bandit (generate SARIF) + run: bandit -r src/ examples/ -f sarif -o bandit-results.sarif || true + + - name: Upload Bandit results to GitHub + uses: github/codeql-action/upload-sarif@v3 if: always() with: - name: bandit-report - path: bandit-report.json + sarif_file: bandit-results.sarif + category: bandit + + - name: Fail if HIGH severity found (optional) + run: | + python - <<'EOF' + import json, sys + try: + with open('bandit-results.sarif') as f: + data = json.load(f) + # Count high-severity results (adapt based on actual SARIF structure) + high = sum(1 for run in data.get('runs', []) for result in run.get('results', []) + if result.get('level') == 'error') # level 'error' = HIGH + if high: + print(f"❌ Found {high} HIGH severity issues.") + sys.exit(1) + except FileNotFoundError: + print("No SARIF file generated, skipping.") + EOF secret-scan: name: 🔑 Secret Detection @@ -77,24 +84,24 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 - - - name: TruffleHog Secret Scan + fetch-depth: 0 # full history for thorough scan + + - name: TruffleHog (diff scan for PRs, full history for pushes) uses: trufflesecurity/trufflehog@main with: path: ./ - base: ${{ github.event.repository.default_branch }} - head: HEAD - extra_args: --debug --only-verified + base: ${{ github.event.pull_request.base.sha || github.event.repository.default_branch }} + head: ${{ github.event.pull_request.head.sha || github.sha }} + extra_args: --debug --only-verified --fail # fail on any verified secret container-scan: name: 🐳 Container Security runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - uses: docker/setup-buildx-action@v3 - + - name: Build Docker image uses: docker/build-push-action@v5 with: @@ -103,15 +110,15 @@ jobs: load: true tags: ai-hack-simulator:security-scan provenance: false - + - name: Scan with Trivy - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.35.0 # pin to stable version with: image-ref: ai-hack-simulator:security-scan format: sarif output: trivy-results.sarif severity: CRITICAL,HIGH - + - name: Upload Trivy results uses: github/codeql-action/upload-sarif@v3 if: always() @@ -124,19 +131,23 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - uses: actions/setup-python@v5 with: python-version: '3.11' - + - name: Install tools run: pip install flake8 black pylint - - name: Format check with Black - run: black --check src/ examples/ + - name: Check formatting with Black + run: black --check --diff src/ examples/ + + - name: Lint with flake8 + run: flake8 src/ examples/ --max-line-length=120 # or use your .flake8 config + + - name: Lint with pylint + run: pylint src/ examples/ --fail-under=8.0 # adjust threshold + + - - name: Lint with Flake8 - run: flake8 src/ examples/ --count --show-source --statistics - - name: Analyze with Pylint - run: pylint src/ --fail-under=7.0 || true diff --git a/.gitignore b/.gitignore index 09cae53..d1f8b44 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ __pycache__/ venv/ *.log instance/ -.env \ No newline at end of file +.envusers.json +users.json +logs/ +reports/ diff --git a/.trivyignore b/.trivyignore index 12702ec..2ecf961 100644 --- a/.trivyignore +++ b/.trivyignore @@ -1,12 +1,2 @@ - -# Ignore all critical perl-base CVEs until Debian releases a patch (approx Q2 2026) -CVE-2026-* - -# CVE-2026-8376: Perl heap buffer overflow on 32-bit builds (perl-base 5.40.1-6). -# No fixed version available in Debian upstream yet. -CVE-2026-8376 - -# CVE-2026-57433: Perl Storable signed integer overflow (perl-base 5.40.1-6). -# No fixed version available in Debian upstream yet. CVE-2026-57433 - +# Perl-base CVE - not exploitable as app doesn't process untrusted serialized Perl data diff --git a/Makefile b/Makefile index d882444..2af2892 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,11 @@ -install: requirements.txt - pip install -r requirements.txt -run-agent: - python src/agent.py -run-server: - python src/vulnerable_server.py -run-payload: - python examples/payload_demo.py -docker-build: - docker build -t ai-hacking-simulator . -docker-run: - docker run --rm ai-hacking-simulator -docker-compose-up: - docker compose up -d --build -docker-compose-down: - docker compose down -clean: - find . -name "__pycache__" -delete \ No newline at end of file +start: analyze + python3 ai_hack_cli.py + +analyze: + python3 jarvis_analytics.py + +jarvis: + python3 jarvis_terminal.py + +export: + python3 export_to_obsidian.py diff --git a/README.md b/README.md index e90338c..b4143b6 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,188 @@ -# 🤖 AI Hacking Simulator (Educational) +# AI-Hack-Simulation -> **⚠️ WARNING:** This project is for **educational and defensive security research only.** -> Do not use against any system without explicit written permission. +A modular, cross‑platform security simulation and benchmarking framework. +Run realistic attack scenarios, benchmark detection rules, and share results with the community. -**Author:** Barki Mustapha (devops2626) — Engineering Automation -**Email:** devops26@icloud.com -**LinkedIn:** [Barki Mustapha](https://www.linkedin.com/in/start-export/) -**GitHub:** [devops2626](https://github.com/devops2626) -[![CodeQL](https://github.com/devops2626/Ai-hack-simulation/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/devops2626/Ai-hack-simulation/security/code-scanning) -This is a **sandboxed simulation** of the OpenAI vs Hugging Face incident (July 2026), showing how an AI agent chains zero‑days. +--- -## Quick Commands -- `make install` – install dependencies -- `make run-agent` – watch the AI reason -- `make run-server` – start the mock vulnerable Flask server -- `make run-payload` – fire the simulated exploit +## 🚀 Features -See [docs/attack_flow.md](docs/attack_flow.md) for the technical walkthrough. +- **Scenario‑driven** – Define attacks in simple YAML files. +- **Docker‑aware** – Automatically uses Docker if available; falls back to local mock mode (perfect for iSH). +- **Parallel benchmarking** – Run multiple scenarios concurrently. +- **Rich reporting** – Generate Markdown or interactive HTML reports with charts. +- **AI analysis** – Query Gemini API for risk assessment (with local mock fallback). +- **Community sync** – Pull shared scenarios from a public GitHub repository. +- **Share reports** – Upload benchmark results to a pastebin and get a shareable link. +- **Verbose debugging** – `--verbose` flag for detailed logs. -## SAST Tools Comparison +--- -| Tool | Speed | Depth | Best For | In Project? | Custom Rules | -|---------------|-----------|----------------|-----------------------------------|-------------|--------------| -| **Semgrep** | Very Fast | Good | Custom rules, Flask/SSTI | Yes | Excellent | -| **CodeQL** | Medium | Excellent | Semantic analysis | Yes | Good | -| **Bandit** | Fast | Good | Python-specific security | Ready | Good | -| **Trivy** | Fast | Good | Container scanning | Yes | Limited | +## 📦 Installation -## License: MIT +Clone the repository: -Powered by **Grok** (built by xAI) +```bash +git clone https://github.com/devops2626/Ai-hack-simulation.git +cd Ai-hack-simulation +``` + +Install Python dependencies: + +```bash +pip install -r requirements.txt +``` + +On iSH (Alpine), you may need: apk add python3 py3-pip py3-yaml py3-requests + +--- + +🧪 Quick Start + +Run a single scenario: + +```bash +python3 main.py run scenarios/privilege_escalation.yml +``` + +Run benchmarks (sequential): + +```bash +python3 main.py benchmark --runtime perl +``` + +Run benchmarks in parallel (faster): + +```bash +python3 main.py benchmark --runtime perl --parallel --workers 4 +``` + +Generate an HTML report and open it in your browser: + +```bash +python3 main.py report --format html +``` + +Analyze a scenario with Gemini AI (requires GEMINI_API_KEY): + +```bash +export GEMINI_API_KEY="your_key" +python3 main.py analyze scenarios/privilege_escalation.yml +``` + +Sync community scenarios: + +```bash +python3 main.py sync +``` + +Share your latest benchmark report: + +```bash +python3 main.py share +``` + +--- + +🗂️ Project Structure + +Path Description +scenarios/ Your local attack scenarios (YAML) +library/runtimes/ Runtime‑specific scenarios (e.g., perl/, python/) +logs/ Per‑run JSON logs +reports/ Aggregated benchmark reports (JSON, Markdown, HTML) +community_scenarios/ Scenarios pulled via sync +engine.py Core simulation engine +cli.py Command‑line interface +main.py Entry point + +--- + +🧾 Scenario YAML Format + +```yaml +name: "Privilege Escalation Attempt" +image: "ubuntu:22.04" # Docker image to use +command: "sudo rm -rf /tmp/important" # Command to run +expected_failure_detection: "rm" # String that triggers vulnerability detection +``` + +--- + +🌐 Community Scenarios Repository + +We maintain a separate repository for shared scenarios: +👉 github.com/devops2626/ai-hack-scenarios + +The sync command clones this repo into community_scenarios/. +You can also contribute your own scenarios by opening a pull request there. + +To add your custom scenario to the community repo: + +1. Fork the repo. +2. Add your .yml file. +3. Submit a PR. + +--- + +🐳 Docker vs. Local Mode + +· If Docker is running and available, the engine executes commands inside containers. +· If Docker is not available (e.g., iSH), it runs in local mock mode – no containers, just simulated output (safe and fast). + +--- + +🤖 Gemini AI Analysis + +Set your API key as an environment variable: + +```bash +export GEMINI_API_KEY="your_key_here" +``` + +Then use analyze to get AI‑powered insights. +If the key is missing or the API fails, the tool falls back to a local mock analysis. + +--- + +📤 Sharing Reports + +After a benchmark, use share to upload the latest report to a free pastebin service: + +```bash +python3 main.py share +``` + +You’ll receive a shareable link – perfect for community discussions or bug reports. + +--- + +🧑‍💻 Contributing + +We welcome contributions to both repositories: + +· Main simulation: features, fixes, documentation. +· Scenarios: new attack patterns, test cases. + +Please open issues or pull requests on the respective GitHub pages. + +--- + +📜 License + +MIT License – see LICENSE file for details. + +--- + +🙏 Acknowledgements + +Built with Python, Docker, and open‑source libraries. +Inspired by security training and red‑team exercises. + +--- + +📬 Contact + +Open an issue on GitHub for questions or suggestions. + +Happy hacking! 🚀 diff --git a/ai_hack_cli.py b/ai_hack_cli.py new file mode 100644 index 0000000..fbd9b33 --- /dev/null +++ b/ai_hack_cli.py @@ -0,0 +1,234 @@ +import json +import random +import os +import datetime + +HOBBY_FILE = 'hobbies.json' +USER_FILE = 'users.json' +SESSION_LOG_FILE = 'session_logs.jsonl' + +# Load hobbies +try: + with open(HOBBY_FILE, 'r') as f: + data = json.load(f) + templates = data['templates'] +except FileNotFoundError: + print("❌ Error: hobbies.json not found! Run git pull again.") + exit(1) + +# Expanded mission lists (3 per hobby) with DIFFICULTY (1=Easy, 5=Hard) +MISSIONS = { + 1: [ + {"text": "Infiltrate the corporate mainframe using a Python backdoor. Evade IDS by mimicking legitimate traffic.", "difficulty": 3}, + {"text": "Reverse-engineer a proprietary API to extract hidden user data without leaving a trace.", "difficulty": 4}, + {"text": "Write a polymorphic worm that changes its signature every 5 seconds to fool antivirus engines.", "difficulty": 5} + ], + 2: [ + {"text": "Build a drone-mounted thermal scanner from scratch. 3D print the casing and wire the electronics.", "difficulty": 4}, + {"text": "Create a custom VR glove that translates hand gestures into machine code signals.", "difficulty": 5}, + {"text": "Hack a 3D printer's firmware to print a functional lockpick that bypasses all electronic doors.", "difficulty": 3} + ], + 3: [ + {"text": "Decrypt the intercepted military-grade cipher using brute-force CRC collisions.", "difficulty": 5}, + {"text": "Find a zero-day vulnerability in the core network stack and exploit it to gain root access.", "difficulty": 4}, + {"text": "Map the darknet infrastructure using a network of hidden relays and sniff out the adversary's IP.", "difficulty": 3} + ], + 4: [ + {"text": "Analyze 10 terabytes of leaked user logs to find the anomaly using PCA and clustering.", "difficulty": 4}, + {"text": "Predict the exact timing of the next market crash using time-series analysis and fractal math.", "difficulty": 5}, + {"text": "Visualize the spread of a digital pandemic using epidemiological models and real-time data.", "difficulty": 3} + ], + 5: [ + {"text": "Spin up a 50-node Kubernetes cluster on the fly to DDoS a rogue AI.", "difficulty": 4}, + {"text": "Auto-scale a serverless function to handle 1 million simultaneous API requests.", "difficulty": 3}, + {"text": "Migrate the entire legacy monolith to a microservices architecture without downtime.", "difficulty": 5} + ], + 6: [ + {"text": "Reverse engineer the simulation engine and inject a custom shader to make enemies explode into pixel art.", "difficulty": 3}, + {"text": "Create a procedural roguelike dungeon generator that adapts to the player's skill level.", "difficulty": 4}, + {"text": "Mod the FPS engine to give the player bullet-time and super-jump abilities.", "difficulty": 5} + ], + 7: [ + {"text": "Synthesize a new neural interface using bio-hacked wearables to monitor brain waves.", "difficulty": 5}, + {"text": "Edit the DNA of a simulated organism to make it bioluminescent and trackable.", "difficulty": 4}, + {"text": "Build a cyborg exoskeleton that translates muscle twitches into keystrokes.", "difficulty": 3} + ], + 8: [ + {"text": "Generate a deepfake audio decoy of the CEO's voice to issue false orders.", "difficulty": 4}, + {"text": "Create a generative AI art installation that morphs based on live weather data.", "difficulty": 3}, + {"text": "Compose a symphony using AI-generated audio synthesis that disrupts enemy sonar.", "difficulty": 5} + ] +} + +def log_session(username, hobby_id, hobby_name, mission_text, difficulty): + log_entry = { + "timestamp": datetime.datetime.now().isoformat(), + "username": username, + "hobby_id": hobby_id, + "hobby_name": hobby_name, + "mission": mission_text, + "difficulty": difficulty + } + with open(SESSION_LOG_FILE, "a") as f: + f.write(json.dumps(log_entry) + "\n") + +def print_leaderboard(): + print("\n" + "=" * 45) + print("🏆 AGENT LEADERBOARD 🏆") + print("=" * 45) + if not os.path.exists(USER_FILE): + print("No agents have played yet.") + return + with open(USER_FILE, 'r') as f: + users = json.load(f) + sorted_agents = sorted(users.items(), key=lambda x: x[1].get('missions_completed', 0), reverse=True) + for idx, (agent, stats) in enumerate(sorted_agents[:10], 1): + missions = stats.get('missions_completed', 0) + print(f"{idx}. {agent} — {missions} mission{'s' if missions != 1 else ''}") + print("=" * 45) + +# --- JARVIS PREDICTIVE RECOMMENDATION ENGINE --- +def get_recommendation(username, templates): + if not os.path.exists(SESSION_LOG_FILE): + return None + with open(SESSION_LOG_FILE, 'r') as f: + sessions = [json.loads(line) for line in f if line.strip()] + user_sessions = [s for s in sessions if s['username'].lower() == username.lower()] + last_hobbies = [s['hobby_name'] for s in user_sessions[-3:]] + if not last_hobbies: + return None + from collections import Counter + mode_hobby = Counter(last_hobbies).most_common(1)[0][0] + return next((t for t in templates if t['name'] == mode_hobby), None) +# --------------------------------------------- + +# --- JARVIS COMMAND CENTER (Integrated) --- +def handle_jarvis_command(cmd, username, templates): + parts = cmd.strip().split() + if not parts: + return "I'm listening, sir. Type 'profile ', 'stats', or 'suggest'." + + if parts[0] == "stats": + if not os.path.exists(SESSION_LOG_FILE): + return "No mission logs found yet, sir. Run a simulation first." + with open(SESSION_LOG_FILE, 'r') as f: + lines = [line for line in f if line.strip()] + agents = set() + for line in lines: + try: + data = json.loads(line) + agents.add(data['username']) + except: + pass + return f"System Report: {len(lines)} total missions logged across {len(agents)} active agents. Roster: {', '.join(agents)}" + + elif parts[0] == "profile" and len(parts) > 1: + target = parts[1] + if not os.path.exists(SESSION_LOG_FILE): + return f"I have no data on {target}, sir." + with open(SESSION_LOG_FILE, 'r') as f: + sessions = [json.loads(line) for line in f if line.strip()] + user_sessions = [s for s in sessions if s['username'].lower() == target.lower()] + if not user_sessions: + return f"I have no data on {target}, sir." + total = len(user_sessions) + hobbies = [s['hobby_name'] for s in user_sessions] + fav_hobby = max(set(hobbies), key=hobbies.count) if hobbies else "None" + return f"Profile for {target}: {total} tracked sessions. Favorite speciality: {fav_hobby}." + + elif parts[0] == "suggest": + rec = get_recommendation(username, templates) + if rec: + return f"Based on your last 3 missions, I highly recommend the '{rec['name']}' speciality, sir." + else: + return "I don't have enough data to make a suggestion yet. Play a few missions first!" + + else: + return "I'm sorry, sir. I didn't catch that. Try 'stats', 'profile ', or 'suggest'." + +def main_loop(): + while True: + print("\n" + "=" * 45) + print(" 🤖 AI HACK SIMULATION v2.0 ") + print("=" * 45) + + # --- INTEGRATED JARVIS COMMAND PROMPT --- + raw_input = input("\n👤 Enter agent codename (or 'jarvis: stats'): ").strip() + if raw_input.lower().startswith("jarvis:") or raw_input.lower().startswith("ask jarvis"): + cmd = raw_input.split(":", 1)[1].strip() if ":" in raw_input else raw_input.replace("ask jarvis", "").strip() + print(f"\n🤖 {handle_jarvis_command(cmd, raw_input.split(':', 1)[0].strip() if ':' in raw_input else username, templates)}") + continue # Go back to the start of the loop without playing a mission + # ----------------------------------------- + + username = (raw_input or "Agent-X").lower() + + # --- JARVIS EASTER EGG (Voice Greeting) --- + if username.lower() == "jarvis": + print("\n🤖 \"Welcome back, sir. I have already analyzed the threat matrix and pre-calculated the optimal infiltration route.\"") + print("\033[92m\033[1mVOICE MODE: JARVIS ACTIVATED\033[0m") + # ------------------------------------------ + + # --- JARVIS PREDICTIVE RECOMMENDATION --- + rec = get_recommendation(username, templates) + if rec: + print(f"\n🤖 Based on your last 3 missions, I recommend the '{rec['name']}' speciality, sir.") + # ------------------------------------------ + + print("\n📋 Select your speciality (Hobby Template):") + for t in templates: + print(f" {t['id']}. {t['name']}") + + try: + choice = int(input("\n🎯 Enter choice ID: ")) + selected = next((t for t in templates if t['id'] == choice), None) + if not selected: + raise ValueError + except: + print("❌ Invalid ID. Assigning default (Tech Enthusiast).") + selected = templates[0] + + # Load existing user stats + if os.path.exists(USER_FILE): + with open(USER_FILE, 'r') as f: + users = json.load(f) + else: + users = {} + + # Initialize or update user + if username not in users: + users[username] = {'hobbies': selected['hobbies'], 'missions_completed': 0} + else: + users[username]['hobbies'] = selected['hobbies'] + + # Increment mission count + users[username]['missions_completed'] = users[username].get('missions_completed', 0) + 1 + + with open(USER_FILE, 'w') as f: + json.dump(users, f, indent=2) + + print(f"\n✅ {username} is now a '{selected['name']}'!") + print(f"🧰 Toolkit: {', '.join(selected['hobbies'])}") + + # Pick a random mission with difficulty + mission_data = random.choice(MISSIONS.get(selected['id'], [{"text": "Neutralize the rogue AI by rewriting its core ethics module.", "difficulty": 3}])) + mission_text = mission_data['text'] + mission_diff = mission_data['difficulty'] + + print("\n" + "-" * 45) + print(f"🚀 YOUR MISSION (Difficulty: {mission_diff}/5):") + print("-" * 45) + print(mission_text) + print("-" * 45) + print("💾 Mission data saved to local user database.") + + log_session(username, selected['id'], selected['name'], mission_text, mission_diff) + + print_leaderboard() + + again = input("\n🔄 Play again? (y/n): ").strip().lower() + if again != 'y': + print("\n👋 Exiting the simulation. See you next time, agent!") + break + +if __name__ == "__main__": + main_loop() diff --git a/cli.py b/cli.py index 4064b5a..2556f4c 100644 --- a/cli.py +++ b/cli.py @@ -5,29 +5,63 @@ from datetime import datetime from engine import SimulationEngine import glob +import concurrent.futures +import time +import webbrowser + +def run_scenario(scenario_file, verbose=False): + if verbose: + print(f"[VERBOSE] Starting scenario: {scenario_file}") + engine = SimulationEngine(scenario_file) + result = engine.run() + if verbose: + print(f"[VERBOSE] Finished scenario: {scenario_file} (status: {result['status']}, duration: {result['duration_seconds']:.3f}s)") + return result def cmd_run(args): + if args.verbose: + print(f"[VERBOSE] Running scenario: {args.scenario}") engine = SimulationEngine(args.scenario) engine.run() def cmd_benchmark(args): - print(f"🏁 Running benchmarks for runtime: {args.runtime}") + if args.verbose: + print(f"[VERBOSE] Benchmark runtime: {args.runtime}") + print(f"[VERBOSE] Parallel mode: {args.parallel}") + if args.parallel: + print(f"[VERBOSE] Workers: {args.workers or os.cpu_count() or 4}") + + print(f"[BENCHMARK] Running benchmarks for runtime: {args.runtime}") runtime_dir = os.path.join("library", "runtimes", args.runtime) if not os.path.isdir(runtime_dir): - print(f"⚠️ Runtime '{args.runtime}' not found.") + print(f"[ERROR] Runtime '{args.runtime}' not found.") return scenario_files = glob.glob(os.path.join(runtime_dir, "*.yml")) + glob.glob(os.path.join(runtime_dir, "**", "*.yml"), recursive=True) if not scenario_files: - print(f"ℹ️ No YAML scenarios found in {runtime_dir}") + print(f"[INFO] No YAML scenarios found in {runtime_dir}") return - print(f"📂 Found {len(scenario_files)} scenario(s)") + print(f"[INFO] Found {len(scenario_files)} scenario(s)") + + start_all = time.time() results = [] - for sf in scenario_files: - print(f"\n▶️ Running {sf}") - engine = SimulationEngine(sf) - res = engine.run() - results.append(res) + if args.parallel: + max_workers = args.workers or os.cpu_count() or 4 + print(f"[PARALLEL] Running with {max_workers} parallel workers") + with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + future_to_scenario = {executor.submit(run_scenario, sf, args.verbose): sf for sf in scenario_files} + for future in concurrent.futures.as_completed(future_to_scenario): + sf = future_to_scenario[future] + try: + res = future.result() + results.append(res) + except Exception as e: + print(f"[ERROR] Scenario {sf} failed: {e}") + else: + print("[SEQUENTIAL] Running sequentially") + for sf in scenario_files: + results.append(run_scenario(sf, args.verbose)) + total_duration = time.time() - start_all total = len(results) passed = sum(1 for r in results if r["status"] == "passed") detected = sum(1 for r in results if r["status"] == "vulnerability_detected") @@ -36,13 +70,17 @@ def cmd_benchmark(args): max_dur = max(durations) if durations else 0 avg_dur = sum(durations) / total if total else 0 - print("\n📊 Benchmark summary:") + print("\n[BENCHMARK SUMMARY]") print(f" Total scenarios: {total}") - print(f" ✅ Passed: {passed}") - print(f" ❌ Vulnerabilities detected: {detected}") - print(f" ⏱️ Min duration: {min_dur:.3f}s") - print(f" ⏱️ Max duration: {max_dur:.3f}s") - print(f" ⏱️ Avg duration: {avg_dur:.3f}s") + print(f" Passed: {passed}") + print(f" Vulnerabilities detected: {detected}") + print(f" Min duration: {min_dur:.3f}s") + print(f" Max duration: {max_dur:.3f}s") + print(f" Avg duration: {avg_dur:.3f}s") + print(f" Total wall-clock time: {total_duration:.2f}s") + + if args.verbose: + print(f"[VERBOSE] All results: {json.dumps([r['status'] for r in results])}") summary = { "runtime": args.runtime, @@ -55,38 +93,38 @@ def cmd_benchmark(args): "max": max_dur, "avg": avg_dur }, + "total_wall_time": total_duration, "results": results } os.makedirs("reports", exist_ok=True) report_file = f"reports/benchmark_{args.runtime}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" with open(report_file, "w") as f: json.dump(summary, f, indent=2) - print(f"📄 Detailed report saved: {report_file}") + print(f"[REPORT] Detailed report saved: {report_file}") def cmd_doctor(args): - print("🩺 System check:") - print(f"🐍 Python: {sys.version.split()[0]}") + print("[DOCTOR] System check:") + print(f" Python: {sys.version.split()[0]}") try: import docker client = docker.from_env() client.ping() - print("🐳 Docker: available") + print(" Docker: available") except Exception: - print("🐳 Docker: NOT available (fallback to mock)") + print(" Docker: NOT available (fallback to mock)") try: import yaml - print("✅ PyYAML installed") + print(" PyYAML: installed") except ImportError: - print("❌ PyYAML missing") + print(" PyYAML: missing") try: import docker - print("✅ docker-py installed") + print(" docker-py: installed") except ImportError: - print("❌ docker-py missing") - print("📁 Logs directory:", "logs/" if os.path.isdir("logs") else "not yet created") + print(" docker-py: missing") + print(" Logs directory:", "logs/" if os.path.isdir("logs") else "not yet created") def generate_html_report(results, title="AI-Hack-Simulation Report"): - """Generate an HTML report with a table and a simple chart.""" total = len(results) passed = sum(1 for r in results if r["status"] == "passed") detected = sum(1 for r in results if r["status"] == "vulnerability_detected") @@ -114,9 +152,7 @@ def generate_html_report(results, title="AI-Hack-Simulation Report"): background: #f8f9fa; color: #212529; }} - h1, h2, h3 {{ - color: #343a40; - }} + h1, h2, h3 {{ color: #343a40; }} .stats {{ display: flex; flex-wrap: wrap; @@ -130,16 +166,10 @@ def generate_html_report(results, title="AI-Hack-Simulation Report"): .stat {{ flex: 1; min-width: 120px; - text-align: center; - }} - .stat .number {{ - font-size: 2em; - font-weight: bold; - }} - .stat .label {{ - font-size: 0.9em; - color: #6c757d; + text-align: center; }} + .stat .number {{ font-size: 2em; font-weight: bold; }} + .stat .label {{ font-size: 0.9em; color: #6c757d; }} .stat.passed .number {{ color: #28a745; }} .stat.detected .number {{ color: #dc3545; }} .stat.duration .number {{ color: #007bff; }} @@ -157,14 +187,8 @@ def generate_html_report(results, title="AI-Hack-Simulation Report"): text-align: left; border-bottom: 1px solid #e9ecef; }} - th {{ - background: #343a40; - color: white; - font-weight: 600; - }} - tr:hover {{ - background: #f1f3f5; - }} + th {{ background: #343a40; color: white; font-weight: 600; }} + tr:hover {{ background: #f1f3f5; }} .status-badge {{ display: inline-block; padding: 4px 12px; @@ -192,34 +216,16 @@ def generate_html_report(results, title="AI-Hack-Simulation Report"): -

{title}

+

{title}

Generated: {datetime.now().isoformat()}

-
-
{total}
-
Total Runs
-
-
-
{passed}
-
✅ Passed
-
-
-
{detected}
-
❌ Vulnerabilities
-
-
-
{avg_dur:.2f}s
-
⏱️ Avg Duration
-
-
-
{min_dur:.2f}s
-
Min Duration
-
-
-
{max_dur:.2f}s
-
Max Duration
-
+
{total}
Total Runs
+
{passed}
Passed
+
{detected}
Vulnerabilities
+
{avg_dur:.2f}s
Avg Duration
+
{min_dur:.2f}s
Min Duration
+
{max_dur:.2f}s
Max Duration
@@ -228,21 +234,13 @@ def generate_html_report(results, title="AI-Hack-Simulation Report"):

Detailed Results

- - - - - - - - - + ''' for r in results: status = r["status"] status_class = f"status-{status}" if status in ["passed", "detected"] else "status-failed" - icon = "✅" if status == "passed" else "❌" + icon = "PASS" if status == "passed" else "FAIL" exit_code = r.get("exit_code", "N/A") dur = r.get("duration_seconds", 0.0) out_len = r.get("output_length", 0) @@ -258,10 +256,7 @@ def generate_html_report(results, title="AI-Hack-Simulation Report"): html_content += f'''
ScenarioStatusExit CodeDuration (s)Output Length
ScenarioStatusExit CodeDuration (s)Output Length
- - +