Files
NeuroSploit/backend/api/v1/providers.py
CyberSecurityUP e0935793c5 NeuroSploit v3.2 - Autonomous AI Penetration Testing Platform
116 modules | 100 vuln types | 18 API routes | 18 frontend pages

Major features:
- VulnEngine: 100 vuln types, 526+ payloads, 12 testers, anti-hallucination prompts
- Autonomous Agent: 3-stream auto pentest, multi-session (5 concurrent), pause/resume/stop
- CLI Agent: Claude Code / Gemini CLI / Codex CLI inside Kali containers
- Validation Pipeline: negative controls, proof of execution, confidence scoring, judge
- AI Reasoning: ReACT engine, token budget, endpoint classifier, CVE hunter, deep recon
- Multi-Agent: 5 specialists + orchestrator + researcher AI + vuln type agents
- RAG System: BM25/TF-IDF/ChromaDB vectorstore, few-shot, reasoning templates
- Smart Router: 20 providers (8 CLI OAuth + 12 API), tier failover, token refresh
- Kali Sandbox: container-per-scan, 56 tools, VPN support, on-demand install
- Full IA Testing: methodology-driven comprehensive pentest sessions
- Notifications: Discord, Telegram, WhatsApp/Twilio multi-channel alerts
- Frontend: React/TypeScript with 18 pages, real-time WebSocket updates
2026-02-22 17:59:28 -03:00

404 lines
12 KiB
Python

"""
NeuroSploit v3 - Providers API
REST endpoints for managing LLM providers and accounts.
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
router = APIRouter()
class ConnectRequest(BaseModel):
label: str = "Manual API Key"
credential: str
credential_type: str = "api_key"
model_override: Optional[str] = None
@router.get("")
async def list_providers():
"""List all providers with their accounts and status."""
from backend.core.smart_router import get_registry
registry = get_registry()
if not registry:
return {"enabled": False, "providers": []}
providers = []
for p in registry.get_all_providers():
accounts = []
for a in p.accounts.values():
accounts.append({
"id": a.id,
"label": a.label,
"source": a.source,
"credential_type": a.credential_type,
"is_active": a.is_active,
"tokens_used": a.tokens_used,
"last_used": a.last_used,
"expires_at": a.expires_at,
"model_override": a.model_override,
})
providers.append({
"id": p.id,
"name": p.name,
"auth_type": p.auth_type,
"api_format": p.api_format,
"tier": p.tier,
"default_model": p.default_model,
"accounts": accounts,
"connected": any(
a.is_active and a.id in registry._credentials
for a in p.accounts.values()
),
"enabled": getattr(p, "enabled", True),
})
return {"enabled": True, "providers": providers}
@router.get("/status")
async def providers_status():
"""Get quota and usage summary."""
from backend.core.smart_router import get_router
router_instance = get_router()
if not router_instance:
return {"enabled": False}
return {"enabled": True, **router_instance.get_status()}
@router.post("/{provider_id}/detect")
async def detect_cli_token(provider_id: str):
"""Auto-detect CLI token for a specific provider."""
from backend.core.smart_router import get_registry, get_extractor
registry = get_registry()
extractor = get_extractor()
if not registry or not extractor:
raise HTTPException(400, "Smart Router not enabled")
token = extractor.detect(provider_id)
if not token:
return {"detected": False, "message": f"No CLI token found for {provider_id}"}
# Add to registry
acct_id = registry.add_account(
provider_id=provider_id,
label=token.label,
credential=token.token,
credential_type=token.credential_type,
source="cli_detect",
refresh_token=token.refresh_token,
expires_at=token.expires_at,
)
return {
"detected": True,
"account_id": acct_id,
"label": token.label,
"credential_type": token.credential_type,
"has_refresh_token": token.refresh_token is not None,
"expires_at": token.expires_at,
}
@router.post("/{provider_id}/connect")
async def connect_provider(provider_id: str, req: ConnectRequest):
"""Manually add an API key or credential."""
from backend.core.smart_router import get_registry
registry = get_registry()
if not registry:
raise HTTPException(400, "Smart Router not enabled")
acct_id = registry.add_account(
provider_id=provider_id,
label=req.label,
credential=req.credential,
credential_type=req.credential_type,
source="manual",
model_override=req.model_override,
)
if not acct_id:
raise HTTPException(404, f"Unknown provider: {provider_id}")
return {"success": True, "account_id": acct_id}
@router.delete("/{provider_id}/accounts/{account_id}")
async def remove_account(provider_id: str, account_id: str):
"""Remove an account from a provider."""
from backend.core.smart_router import get_registry
registry = get_registry()
if not registry:
raise HTTPException(400, "Smart Router not enabled")
success = registry.remove_account(provider_id, account_id)
if not success:
raise HTTPException(404, "Account not found")
return {"success": True}
@router.post("/test/{provider_id}/{account_id}")
async def test_connection(provider_id: str, account_id: str):
"""Test connectivity for a specific account."""
from backend.core.smart_router import get_router
router_instance = get_router()
if not router_instance:
raise HTTPException(400, "Smart Router not enabled")
success, message = await router_instance.test_account(provider_id, account_id)
return {"success": success, "message": message}
# Known models per provider for dropdown selection
PROVIDER_MODELS = {
"claude_code": [
"claude-opus-4-6-20250918",
"claude-sonnet-4-5-20250929",
"claude-haiku-4-5-20251001",
"claude-sonnet-4-20250514",
"claude-opus-4-20250514",
"claude-haiku-4-20250514",
],
"kiro": [
"claude-opus-4-6-20250918",
"claude-sonnet-4-5-20250929",
"claude-haiku-4-5-20251001",
"claude-sonnet-4-20250514",
"claude-opus-4-20250514",
"claude-haiku-4-20250514",
],
"anthropic": [
"claude-opus-4-6-20250918",
"claude-sonnet-4-5-20250929",
"claude-haiku-4-5-20251001",
"claude-sonnet-4-20250514",
"claude-opus-4-20250514",
"claude-haiku-4-20250514",
"claude-3-5-sonnet-20241022",
],
"codex_cli": [
"gpt-4o",
"gpt-4o-mini",
"o3-mini",
"o4-mini",
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4.1-nano",
],
"openai": [
"gpt-4o",
"gpt-4o-mini",
"o3-mini",
"o4-mini",
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4.1-nano",
],
"gemini_cli": [
"gemini-3.0-pro",
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.0-flash",
"gemini-2.0-flash-lite",
],
"gemini": [
"gemini-3.0-pro",
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.0-flash",
"gemini-2.0-flash-lite",
],
"cursor": [
"cursor-fast",
"cursor-small",
"gpt-4o",
"claude-sonnet-4-5-20250929",
"claude-3-5-sonnet-20241022",
],
"copilot": [
"gpt-4o",
"gpt-4o-mini",
"claude-sonnet-4-5-20250929",
"claude-3-5-sonnet-20241022",
],
"openrouter": [
"anthropic/claude-opus-4-6",
"anthropic/claude-sonnet-4-5",
"anthropic/claude-haiku-4-5",
"anthropic/claude-sonnet-4",
"anthropic/claude-opus-4",
"openai/gpt-4o",
"google/gemini-3.0-pro",
"google/gemini-2.5-pro",
"google/gemini-2.5-flash",
"meta-llama/llama-4-maverick",
"deepseek/deepseek-r1",
],
"together": [
"meta-llama/Llama-3-70b-chat-hf",
"meta-llama/Llama-3.3-70B-Instruct-Turbo",
"deepseek-ai/DeepSeek-R1",
"Qwen/Qwen2.5-72B-Instruct-Turbo",
],
"fireworks": [
"accounts/fireworks/models/llama-v3p1-70b-instruct",
"accounts/fireworks/models/llama-v3p3-70b-instruct",
"accounts/fireworks/models/deepseek-r1",
],
"iflow": ["kimi-k2"],
"qwen_code": ["qwen3-coder", "qwen-max"],
"ollama": ["llama3", "llama3.2", "mistral", "codellama", "deepseek-r1"],
"lmstudio": ["local-model"],
}
@router.get("/available-models")
async def available_models():
"""Get list of available provider+model combinations for selection dropdowns."""
from backend.core.smart_router import get_registry
registry = get_registry()
if not registry:
return {"models": []}
models = []
for p in registry.get_all_providers():
active = registry.get_active_accounts(p.id)
if not active:
continue
models.append({
"provider_id": p.id,
"provider_name": p.name,
"default_model": p.default_model,
"tier": p.tier,
"available_models": PROVIDER_MODELS.get(p.id, [p.default_model]),
})
# Sort by tier (paid first) then name
models.sort(key=lambda m: (m["tier"], m["provider_name"]))
return {"models": models}
@router.post("/detect-all")
async def detect_all_tokens():
"""Scan all CLI tools for available tokens."""
from backend.core.smart_router import get_registry, get_extractor
registry = get_registry()
extractor = get_extractor()
if not registry or not extractor:
raise HTTPException(400, "Smart Router not enabled")
tokens = extractor.detect_all()
results = []
for token in tokens:
acct_id = registry.add_account(
provider_id=token.provider_id,
label=token.label,
credential=token.token,
credential_type=token.credential_type,
source="cli_detect",
refresh_token=token.refresh_token,
expires_at=token.expires_at,
)
results.append({
"provider_id": token.provider_id,
"label": token.label,
"account_id": acct_id,
})
return {
"detected_count": len(results),
"results": results,
}
class ToggleRequest(BaseModel):
enabled: bool
@router.post("/{provider_id}/toggle")
async def toggle_provider(provider_id: str, req: ToggleRequest):
"""Enable or disable a provider. Disabled providers are skipped by the router."""
from backend.core.smart_router import get_registry
registry = get_registry()
if not registry:
raise HTTPException(400, "Smart Router not enabled")
success = registry.toggle_provider(provider_id, req.enabled)
if not success:
raise HTTPException(404, f"Unknown provider: {provider_id}")
return {"success": True, "provider_id": provider_id, "enabled": req.enabled}
# Whitelist of env keys that can be modified via UI
ALLOWED_ENV_KEYS = {
"ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY",
"OPENROUTER_API_KEY", "TOGETHER_API_KEY", "FIREWORKS_API_KEY",
"OLLAMA_HOST", "LMSTUDIO_HOST",
"ENABLE_SMART_ROUTER", "ENABLE_REASONING", "ENABLE_CVE_HUNT",
"ENABLE_MULTI_AGENT", "ENABLE_RESEARCHER_AI",
"NVD_API_KEY", "GITHUB_TOKEN", "TOKEN_BUDGET",
}
class EnvUpdateRequest(BaseModel):
key: str
value: str
@router.get("/env")
async def get_env_keys():
"""Get current values of allowed env keys (masked for secrets)."""
import os
result = {}
for key in sorted(ALLOWED_ENV_KEYS):
val = os.getenv(key, "")
if val and "KEY" in key and key not in ("ENABLE_SMART_ROUTER", "ENABLE_REASONING",
"ENABLE_CVE_HUNT", "ENABLE_MULTI_AGENT",
"ENABLE_RESEARCHER_AI", "TOKEN_BUDGET"):
# Mask API keys: show first 8 and last 4 chars
if len(val) > 16:
result[key] = val[:8] + "..." + val[-4:]
else:
result[key] = "****"
else:
result[key] = val
return {"env": result, "allowed_keys": sorted(ALLOWED_ENV_KEYS)}
@router.post("/env")
async def update_env_key(req: EnvUpdateRequest):
"""Update an env var and persist to .env file."""
import os
from pathlib import Path
if req.key not in ALLOWED_ENV_KEYS:
raise HTTPException(400, f"Key '{req.key}' is not in the allowed whitelist")
# Update in-process env
os.environ[req.key] = req.value
# Persist to .env file
env_path = Path(__file__).parent.parent.parent.parent / ".env"
try:
lines = []
found = False
if env_path.exists():
for line in env_path.read_text().splitlines():
stripped = line.strip()
if stripped.startswith(f"{req.key}=") or stripped.startswith(f"# {req.key}="):
lines.append(f"{req.key}={req.value}")
found = True
else:
lines.append(line)
if not found:
lines.append(f"{req.key}={req.value}")
env_path.write_text("\n".join(lines) + "\n")
except Exception as e:
# Still updated in-process even if file write failed
return {"success": True, "persisted": False, "error": str(e)}
return {"success": True, "persisted": True}