{"repo": "calvaryyy/lance", "files": {"ava.py": {"43df7c63006a27aef4c5276e1cba288d0da8d7cf": {"t": 1785674063.0306737, "n": 15248, "h": "f296eeccc05b1da7", "c": "#!/usr/bin/env python3\n\"\"\"\nLANCE - AI Synthesis Engine\nPhase 3: Parse vuln_report.json, synthesize findings into a structured\nintelligence report with severity categorization and exploit correlation.\n\"\"\"\n\nimport json\nimport os\nimport sys\nimport re\nfrom pathlib import Path\nfrom datetime import datetime\n\n# ─────────────────────────────────────────────\n# CONFIG\n# ─────────────────────────────────────────────\n\nCONFIG_PATH = Path(__file__).parent / \"lance.conf\"\n\nSUPPORTED_PROVIDERS = [\"ollama\", \"openai\", \"gemini\", \"anthropic\", \"deepseek\"]\n\nSYSTEM_PROMPT = \"\"\"You are an expert penetration tester and vulnerability analyst.\nYou will be given a structured JSON report of vulnerabilities found during an\nexternal attack surface assessment. Your job is to synthesize this data into a\nclear, actionable intelligence report.\n\nYour report must:\n1. Summarize the overall security posture of the target\n2. List all findings grouped by severity (Critical → High → Medium → Low → Info)\n3. For each finding: explain what it is, why it matters, and the realistic exploit path\n4. Identify vulnerability chains — where multiple findings combine into a higher-impact attack\n5. Prioritize findings by a combined score of severity + ease of exploitability\n6. Provide concrete remediation steps per finding\n7. End with an executive summary of the top 3 most critical risks\n\nBe technical, precise, and direct. Do not pad the report. Do not repeat yourself.\nOutput in clean Markdown.\"\"\"\n\n# ─────────────────────────────────────────────\n# UTILITIES\n# ─────────────────────────────────────────────\n\ndef log(msg, level=\"INFO\"):\n colors = {\n \"INFO\": \"\\033[94m\",\n \"OK\": \"\\033[92m\",\n \"WARN\": \"\\033[93m\",\n \"ERROR\": \"\\033[91m\",\n \"PHASE\": \"\\033[96m\",\n }\n reset = \"\\033[0m\"\n prefix = colors.get(level, \"\") + f\"[{level}]\" + reset\n print(f\"{prefix} {msg}\")\n\ndef load_config():\n \"\"\"Load lance.conf into a dict. Returns empty dict if not found.\"\"\"\n if not CONFIG_PATH.exists():\n return {}\n config = {}\n with open(CONFIG_PATH) as f:\n for line in f:\n line = line.strip()\n if not line or line.startswith(\"#\"):\n continue\n if \"=\" in line:\n key, _, val = line.partition(\"=\")\n config[key.strip()] = val.strip()\n return config\n\ndef save_config(config):\n \"\"\"Write config dict to lance.conf.\"\"\"\n lines = [\"# LANCE Configuration File\\n# Auto-generated — edit with care\\n\"]\n for key, val in config.items():\n lines.append(f\"{key}={val}\")\n with open(CONFIG_PATH, \"w\") as f:\n f.write(\"\\n\".join(lines) + \"\\n\")\n log(f\"Config saved → {CONFIG_PATH}\", \"OK\")\n\ndef load_vuln_report(report_path):\n path = Path(report_path)\n if not path.exists():\n log(f\"Vuln report not found: {report_path}\", \"ERROR\")\n sys.exit(1)\n with open(path) as f:\n return json.load(f)\n\ndef prompt_ai_provider():\n \"\"\"Interactive prompt if user hasn't specified --ai flag.\"\"\"\n print(\"\\n\\033[96m[LANCE] No AI provider specified.\\033[0m\")\n print(\"Select a provider:\\n\")\n print(\" 1) Ollama (local inference — no API key needed)\")\n print(\" 2) OpenAI (GPT-4o, GPT-4-turbo, etc.)\")\n print(\" 3) Gemini (Google Gemini Pro)\")\n print(\" 4) Anthropic (Claude)\")\n print(\" 5) DeepSeek (DeepSeek-V3, R1)\")\n print()\n while True:\n choice = input(\"Enter choice [1-5]: \").strip()\n mapping = {\"1\": \"ollama\", \"2\": \"openai\", \"3\": \"gemini\", \"4\": \"anthropic\", \"5\": \"deepseek\"}\n if choice in mapping:\n return mapping[choice]\n print(\"Invalid choice. Enter a number between 1 and 5.\")\n\ndef prompt_ollama_model():\n \"\"\"Let user pick an installed Ollama model.\"\"\"\n try:\n import subprocess\n result = subprocess.run(\"ollama list\", shell=True, capture_output=True, text=True)\n lines = result.stdout.strip().splitlines()\n models = []\n for line in lines[1:]: # skip header\n parts = line.split()\n if parts:\n models.append(parts[0])\n except Exception:\n models = []\n\n if not models:\n log(\"No Ollama models found. Run: ollama pull \", \"WARN\")\n model = input(\"Enter model name manually (e.g. llama3, mistral): \").strip()\n return model or \"llama3\"\n\n print(\"\\nInstalled Ollama models:\")\n for i, m in enumerate(models, 1):\n print(f\" {i}) {m}\")\n print()\n while True:\n choice = input(f\"Select model [1-{len(models)}]: \").strip()\n try:\n idx = int(choice) - 1\n if 0 <= idx < len(models):\n return models[idx]\n except ValueError:\n pass\n print(\"Invalid choice.\")\n\n\n# ─────────────────────────────────────────────\n# PROVIDER CLIENTS\n# ─────────────────────────────────────────────\n\ndef call_ollama(prompt, model):\n try:\n import requests\n except ImportError:\n log(\"requests not installed. Run: pip install requests --break-system-packages\", \"ERROR\")\n sys.exit(1)\n\n log(f\"Calling Ollama [{model}]...\", \"INFO\")\n try:\n response = requests.post(\n \"http://localhost:11434/api/generate\",\n json={\"model\": model, \"prompt\": prompt, \"stream\": False},\n timeout=300\n )\n response.raise_for_status()\n return response.json().get(\"response\", \"\")\n except Exception as e:\n log(f\"Ollama error: {e}\", \"ERROR\")\n sys.exit(1)\n\n\ndef call_openai(prompt, api_key, model=\"gpt-4o\"):\n try:\n import requests\n except ImportError:\n log(\"requests not installed. Run: pip install requests --break-system-packages\", \"ERROR\")\n sys.exit(1)\n\n log(f\"Calling OpenAI [{model}]...\", \"INFO\")\n headers = {\"Authorization\": f\"Bearer {api_key}\", \"Content-Type\": \"application/json\"}\n payload = {\n \"model\": model,\n \"messages\": [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": prompt}\n ],\n \"max_tokens\": 4096,\n }\n try:\n response = requests.post(\n \"https://api.openai.com/v1/chat/completions\",\n headers=headers, json=payload, timeout=120\n )\n response.raise_for_status()\n return response.json()[\"choices\"][0][\"message\"][\"content\"]\n except Exception as e:\n log(f\"OpenAI error: {e}\", \"ERROR\")\n sys.exit(1)\n\n\ndef call_gemini(prompt, api_key, model=\"gemini-1.5-pro\"):\n try:\n import requests\n except ImportError:\n log(\"requests not installed.\", \"ERROR\")\n sys.exit(1)\n\n log(f\"Calling Gemini [{model}]...\", \"INFO\")\n url = f\"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}\"\n payload = {\n \"contents\": [{\"parts\": [{\"text\": SYSTEM_PROMPT + \"\\n\\n\" + prompt}]}],\n \"generationConfig\": {\"maxOutputTokens\": 4096}\n }\n try:\n response = requests.post(url, json=payload, timeout=120)\n response.raise_for_status()\n return response.json()[\"candidates\"][0][\"content\"][\"parts\"][0][\"text\"]\n except Exception as e:\n log(f\"Gemini error: {e}\", \"ERROR\")\n sys.exit(1)\n\n\ndef call_anthropic(prompt, api_key, model=\"claude-sonnet-4-20250514\"):\n try:\n import requests\n except ImportError:\n log(\"requests not installed.\", \"ERROR\")\n sys.exit(1)\n\n log(f\"Calling Anthropic [{model}]...\", \"INFO\")\n headers = {\n \"x-api-key\": api_key,\n \"anthropic-version\": \"2023-06-01\",\n \"Content-Type\": \"application/json\"\n }\n payload = {\n \"model\": model,\n \"max_tokens\": 4096,\n \"system\": SYSTEM_PROMPT,\n \"messages\": [{\"role\": \"user\", \"content\": prompt}]\n }\n try:\n response = requests.post(\n \"https://api.anthropic.com/v1/messages\",\n headers=headers, json=payload, timeout=120\n )\n response.raise_for_status()\n return response.json()[\"content\"][0][\"text\"]\n except Exception as e:\n log(f\"Anthropic error: {e}\", \"ERROR\")\n sys.exit(1)\n\n\ndef call_deepseek(prompt, api_key, model=\"deepseek-chat\"):\n try:\n import requests\n except ImportError:\n log(\"requests not installed.\", \"ERROR\")\n sys.exit(1)\n\n log(f\"Calling DeepSeek [{model}]...\", \"INFO\")\n headers = {\"Authorization\": f\"Bearer {api_key}\", \"Content-Type\": \"application/json\"}\n payload = {\n \"model\": model,\n \"messages\": [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": prompt}\n ],\n \"max_tokens\": 4096,\n }\n try:\n response = requests.post(\n \"https://api.deepseek.com/v1/chat/completions\",\n headers=headers, json=payload, timeout=120\n )\n response.raise_for_status()\n return response.json()[\"choices\"][0][\"message\"][\"content\"]\n except Exception as e:\n log(f\"DeepSeek error: {e}\", \"ERROR\")\n sys.exit(1)\n\n\n# ─────────────────────────────────────────────\n# PROMPT BUILDER\n# ─────────────────────────────────────────────\n\ndef build_synthesis_prompt(vuln_report):\n \"\"\"Build the prompt fed to the AI from the vuln report.\"\"\"\n meta = vuln_report.get(\"meta\", {})\n summary = vuln_report.get(\"summary\", {})\n findings = vuln_report.get(\"findings\", [])\n\n # Slim down findings to essential fields to stay within context limits\n slim_findings = []\n for f in findings:\n slim_findings.append({\n \"name\": f.get(\"name\"),\n \"severity\": f.get(\"severity\"),\n \"url\": f.get(\"url\"),\n \"category\": f.get(\"category\"),\n \"description\": f.get(\"description\", \"\")[:300],\n \"tool\": f.get(\"tool\"),\n \"references\": f.get(\"references\", [])[:3],\n \"extra\": {k: v for k, v in f.get(\"extra\", {}).items()\n if k in (\"template_id\", \"plugin\", \"fixed_in\", \"matcher_name\")},\n })\n\n prompt = f\"\"\"You are analyzing the following vulnerability assessment report.\n\nTARGET: {meta.get('target', 'Unknown')}\nSCAN DATE: {meta.get('timestamp', 'Unknown')}\nASSETS SCANNED: {summary.get('assets_scanned', 0)}\nTOTAL FINDINGS: {summary.get('total_findings', 0)}\n\nSEVERITY BREAKDOWN:\n{json.dumps(summary.get('severity_breakdown', {}), indent=2)}\n\nFINDINGS:\n{json.dumps(slim_findings, indent=2)}\n\n---\nGenerate a full penetration test intelligence report following your instructions.\nFocus especially on vulnerability chains and prioritized remediation.\n\"\"\"\n return prompt\n\n\n# ─────────────────────────────────────────────\n# REPORT WRITER\n# ─────────────────────────────────────────────\n\ndef save_synthesis_report(content, output_dir, target):\n safe_target = target.replace(\"/\", \"_\").replace(\":\", \"_\").replace(\".\", \"_\")\n timestamp = datetime.utcnow().strftime(\"%Y%m%d_%H%M%S\")\n md_path = output_dir / f\"synthesis_{safe_target}_{timestamp}.md\"\n with open(md_path, \"w\") as f:\n f.write(f\"# LANCE Intelligence Report\\n\")\n f.write(f\"**Target:** {target} \\n\")\n f.write(f\"**Generated:** {datetime.utcnow().isoformat()}Z \\n\")\n f.write(f\"**Engine:** LANCE AI Synthesis v0.1\\n\\n\")\n f.write(\"---\\n\\n\")\n f.write(content)\n log(f\"Synthesis report saved → {md_path}\", \"OK\")\n return md_path\n\n\n# ─────────────────────────────────────────────\n# ENTRY POINT\n# ─────────────────────────────────────────────\n\ndef run_synthesis(vuln_report_path, provider=None, model_override=None):\n log(f\"Loading vuln report: {vuln_report_path}\", \"INFO\")\n vuln_report = load_vuln_report(vuln_report_path)\n config = load_config()\n output_dir = Path(vuln_report_path).parent\n\n # Resolve provider\n if not provider:\n provider = config.get(\"default_provider\")\n if not provider:\n provider = prompt_ai_provider()\n\n provider = provider.lower()\n if provider not in SUPPORTED_PROVIDERS:\n log(f\"Unknown provider: {provider}. Choose from: {', '.join(SUPPORTED_PROVIDERS)}\", \"ERROR\")\n sys.exit(1)\n\n log(f\"Provider: {provider}\", \"INFO\")\n\n # Build prompt\n prompt = build_synthesis_prompt(vuln_report)\n\n # Dispatch to provider\n if provider == \"ollama\":\n model = model_override or config.get(\"ollama_model\")\n if not model:\n model = prompt_ollama_model()\n config[\"ollama_model\"] = model\n save_config(config)\n result = call_ollama(prompt, model)\n\n else:\n # API key providers\n key_field = f\"{provider}_api_key\"\n api_key = config.get(key_field)\n\n if not api_key:\n log(f\"No API key found for {provider}.\", \"WARN\")\n log(f\"Run: python3 lance_config.py --set {key_field} \", \"INFO\")\n api_key = input(f\"Enter {provider.upper()} API key (will be saved to config): \").strip()\n if not api_key:\n log(\"No API key provided. Exiting.\", \"ERROR\")\n sys.exit(1)\n config[key_field] = api_key\n save_config(config)\n\n default_models = {\n \"openai\": \"gpt-4o\",\n \"gemini\": \"gemini-1.5-pro\",\n \"anthropic\": \"claude-sonnet-4-20250514\",\n \"deepseek\": \"deepseek-chat\",\n }\n model = model_override or config.get(f\"{provider}_model\") or default_models[provider]\n\n dispatch = {\n \"openai\": lambda: call_openai(prompt, api_key, model),\n \"gemini\": lambda: call_gemini(prompt, api_key, model),\n \"anthropic\": lambda: call_anthropic(prompt, api_key, model),\n \"deepseek\": lambda: call_deepseek(prompt, api_key, model),\n }\n result = dispatch[provider]()\n\n if not result:\n log(\"AI returned empty response.\", \"ERROR\")\n sys.exit(1)\n\n log(\"Synthesis complete.\", \"OK\")\n report_path = save_synthesis_report(result, output_dir, vuln_report[\"meta\"].get(\"target\", \"unknown\"))\n\n print(\"\\n\" + \"─\" * 50)\n print(f\" SYNTHESIS COMPLETE\")\n print(\"─\" * 50)\n print(f\" Provider : {provider} [{model}]\")\n print(f\" Report : {report_path}\")\n print(\"─\" * 50 + \"\\n\")\n\n return report_path\n\n\nif __name__ == \"__main__\":\n import argparse\n\n parser = argparse.ArgumentParser(\n description=\"LANCE AI Synthesis — Intelligent vulnerability report generation\",\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog=\"\"\"\nExamples:\n python3 ai_synthesis.py -r results/example.com/vuln_report.json --ai ollama\n python3 ai_synthesis.py -r results/example.com/vuln_report.json --ai gemini\n python3 ai_synthesis.py -r results/example.com/vuln_report.json --ai anthropic --model claude-opus-4-20250514\n \"\"\"\n )\n parser.add_argument(\"-r\", \"--report\", required=True, help=\"Path to vuln_report.json from Phase 2\")\n parser.add_argument(\"--ai\", dest=\"provider\", choices=SUPPORTED_PROVIDERS,\n help=\"AI provider to use for synthesis\")\n parser.add_argument(\"--model\", dest=\"model_override\", default=None,\n help=\"Override the default model for the selected provider\")\n\n args = parser.parse_args()\n run_synthesis(args.report, provider=args.provider, model_override=args.model_override)\n"}}}}