mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-06-30 07:15:30 +02:00
Merge pull request #28 from Hasan72341/main
UI/UX overhaul, critical stability fixes, and NVIDIA NIM integration
This commit is contained in:
@@ -78,6 +78,12 @@ ENABLE_CVE_HUNT=true
|
||||
# NVD API key for higher rate limits: https://nvd.nist.gov/developers/request-an-api-key
|
||||
#NVD_API_KEY=
|
||||
|
||||
# NVIDIA NIM API key for free 40 RPM endpoint
|
||||
NIM_API_KEY=
|
||||
|
||||
# NVIDIA NIM Model (optional - defaults to openai/gpt-oss-120b)
|
||||
#NIM_MODEL=
|
||||
|
||||
# GitHub token for exploit search (optional, increases rate limit)
|
||||
#GITHUB_TOKEN=
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ async def toggle_provider(provider_id: str, req: ToggleRequest):
|
||||
# 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",
|
||||
"NIM_API_KEY", "NIM_MODEL", "NIM_BASE_URL",
|
||||
"OPENROUTER_API_KEY", "TOGETHER_API_KEY", "FIREWORKS_API_KEY",
|
||||
"OLLAMA_HOST", "LMSTUDIO_HOST",
|
||||
"ENABLE_SMART_ROUTER", "ENABLE_REASONING", "ENABLE_CVE_HUNT",
|
||||
|
||||
@@ -71,6 +71,7 @@ class SettingsUpdate(BaseModel):
|
||||
llm_model: Optional[str] = None
|
||||
anthropic_api_key: Optional[str] = None
|
||||
openai_api_key: Optional[str] = None
|
||||
nim_api_key: Optional[str] = None
|
||||
openrouter_api_key: Optional[str] = None
|
||||
gemini_api_key: Optional[str] = None
|
||||
together_api_key: Optional[str] = None
|
||||
@@ -103,6 +104,7 @@ class SettingsResponse(BaseModel):
|
||||
llm_model: str = ""
|
||||
has_anthropic_key: bool = False
|
||||
has_openai_key: bool = False
|
||||
has_nim_key: bool = False
|
||||
has_openrouter_key: bool = False
|
||||
has_gemini_key: bool = False
|
||||
has_together_key: bool = False
|
||||
@@ -172,7 +174,9 @@ def _load_settings_from_env() -> dict:
|
||||
|
||||
# Detect provider from which keys are set
|
||||
provider = "claude"
|
||||
if os.getenv("ANTHROPIC_API_KEY"):
|
||||
if os.getenv("NIM_API_KEY"):
|
||||
provider = "nim"
|
||||
elif os.getenv("ANTHROPIC_API_KEY"):
|
||||
provider = "claude"
|
||||
elif os.getenv("OPENAI_API_KEY"):
|
||||
provider = "openai"
|
||||
@@ -184,6 +188,7 @@ def _load_settings_from_env() -> dict:
|
||||
"llm_model": os.getenv("DEFAULT_LLM_MODEL", ""),
|
||||
"anthropic_api_key": os.getenv("ANTHROPIC_API_KEY", ""),
|
||||
"openai_api_key": os.getenv("OPENAI_API_KEY", ""),
|
||||
"nim_api_key": os.getenv("NIM_API_KEY", ""),
|
||||
"openrouter_api_key": os.getenv("OPENROUTER_API_KEY", ""),
|
||||
"gemini_api_key": os.getenv("GEMINI_API_KEY", ""),
|
||||
"together_api_key": os.getenv("TOGETHER_API_KEY", ""),
|
||||
@@ -224,6 +229,7 @@ async def get_settings():
|
||||
llm_model=_settings.get("llm_model", ""),
|
||||
has_anthropic_key=bool(_settings["anthropic_api_key"] or os.getenv("ANTHROPIC_API_KEY")),
|
||||
has_openai_key=bool(_settings["openai_api_key"] or os.getenv("OPENAI_API_KEY")),
|
||||
has_nim_key=bool(_settings.get("nim_api_key") or os.getenv("NIM_API_KEY")),
|
||||
has_openrouter_key=bool(_settings["openrouter_api_key"] or os.getenv("OPENROUTER_API_KEY")),
|
||||
has_gemini_key=bool(_settings.get("gemini_api_key") or os.getenv("GEMINI_API_KEY")),
|
||||
has_together_key=bool(_settings.get("together_api_key") or os.getenv("TOGETHER_API_KEY")),
|
||||
@@ -275,6 +281,12 @@ async def update_settings(settings_data: SettingsUpdate):
|
||||
os.environ["OPENAI_API_KEY"] = settings_data.openai_api_key
|
||||
env_updates["OPENAI_API_KEY"] = settings_data.openai_api_key
|
||||
|
||||
if settings_data.nim_api_key is not None:
|
||||
_settings["nim_api_key"] = settings_data.nim_api_key
|
||||
if settings_data.nim_api_key:
|
||||
os.environ["NIM_API_KEY"] = settings_data.nim_api_key
|
||||
env_updates["NIM_API_KEY"] = settings_data.nim_api_key
|
||||
|
||||
if settings_data.openrouter_api_key is not None:
|
||||
_settings["openrouter_api_key"] = settings_data.openrouter_api_key
|
||||
if settings_data.openrouter_api_key:
|
||||
@@ -564,6 +576,11 @@ CLOUD_MODELS = {
|
||||
"codex": [
|
||||
{"model_id": "codex-mini-latest", "display_name": "Codex Mini", "context_length": 192000},
|
||||
],
|
||||
"nim": [
|
||||
{"model_id": "openai/gpt-oss-120b", "display_name": "NVIDIA GPT-OSS 120B", "context_length": 32768},
|
||||
{"model_id": "meta/llama-3.1-70b-instruct", "display_name": "Llama 3.1 70B (NIM)", "context_length": 32768},
|
||||
{"model_id": "meta/llama-3.1-405b-instruct", "display_name": "Llama 3.1 405B (NIM)", "context_length": 32768},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ class Settings(BaseSettings):
|
||||
# LLM Settings
|
||||
ANTHROPIC_API_KEY: Optional[str] = os.getenv("ANTHROPIC_API_KEY")
|
||||
OPENAI_API_KEY: Optional[str] = os.getenv("OPENAI_API_KEY")
|
||||
NIM_API_KEY: Optional[str] = os.getenv("NIM_API_KEY")
|
||||
NIM_BASE_URL: str = os.getenv("NIM_BASE_URL", "https://integrate.api.nvidia.com/v1/chat/completions")
|
||||
OPENROUTER_API_KEY: Optional[str] = os.getenv("OPENROUTER_API_KEY")
|
||||
GEMINI_API_KEY: Optional[str] = os.getenv("GEMINI_API_KEY")
|
||||
AZURE_OPENAI_API_KEY: Optional[str] = os.getenv("AZURE_OPENAI_API_KEY")
|
||||
|
||||
@@ -217,6 +217,8 @@ except ImportError:
|
||||
ANTHROPIC_AVAILABLE = False
|
||||
anthropic = None
|
||||
|
||||
|
||||
|
||||
# Try to import openai
|
||||
try:
|
||||
import openai
|
||||
@@ -367,6 +369,7 @@ class LLMClient:
|
||||
self.azure_openai_api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2024-02-01")
|
||||
self.azure_openai_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT", "")
|
||||
self.codex_key = os.getenv("CODEX_API_KEY", "")
|
||||
self.nim_key = os.getenv("NIM_API_KEY", "")
|
||||
self.ollama_model = os.getenv("OLLAMA_MODEL", "llama3.2")
|
||||
self.configured_model = os.getenv("DEFAULT_LLM_MODEL", "") # User-configured model name
|
||||
self.client = None
|
||||
@@ -419,6 +422,14 @@ class LLMClient:
|
||||
|
||||
def _initialize_provider(self):
|
||||
"""Initialize the first available LLM provider"""
|
||||
# 0. Try NVIDIA NIM (NVIDIA's OpenAI-compatible endpoint)
|
||||
if self.nim_key:
|
||||
self.client = "nim" # Placeholder - uses HTTP requests
|
||||
self.provider = "nim"
|
||||
self.model_name = self.configured_model or os.getenv("NIM_MODEL", "openai/gpt-oss-120b")
|
||||
print(f"[LLM] NVIDIA NIM initialized (model: {self.model_name})")
|
||||
return
|
||||
|
||||
# 1. Try Claude (Anthropic)
|
||||
if ANTHROPIC_AVAILABLE and self.anthropic_key:
|
||||
try:
|
||||
@@ -573,6 +584,7 @@ class LLMClient:
|
||||
"openai_lib": OPENAI_AVAILABLE,
|
||||
"ollama_available": self._check_ollama(),
|
||||
"lmstudio_available": self._check_lmstudio(),
|
||||
"has_nim_key": bool(self.nim_key),
|
||||
"has_google_key": bool(self.google_key),
|
||||
"has_together_key": bool(self.together_key),
|
||||
"has_fireworks_key": bool(self.fireworks_key),
|
||||
@@ -639,6 +651,14 @@ class LLMClient:
|
||||
)
|
||||
return message.content[0].text
|
||||
|
||||
elif self.provider == "nim":
|
||||
return await self._generate_openai_compatible(
|
||||
prompt, system or default_system, max_tokens,
|
||||
url=os.getenv("NIM_BASE_URL", "https://integrate.api.nvidia.com/v1/chat/completions"),
|
||||
api_key=self.nim_key,
|
||||
model=self.model_name or "openai/gpt-oss-120b",
|
||||
)
|
||||
|
||||
elif self.provider == "openai":
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model_name or "gpt-4o",
|
||||
|
||||
@@ -177,7 +177,7 @@ class KnowledgeProcessor:
|
||||
|
||||
def _extract_text_pdf(self, file_path: Path) -> str:
|
||||
"""Extract text from PDF."""
|
||||
if not HAS_PYPDKF2:
|
||||
if not HAS_PYPDF2:
|
||||
logger.warning("PyPDF2 not installed - PDF extraction unavailable. Install: pip install PyPDF2")
|
||||
# Try reading as text fallback
|
||||
try:
|
||||
|
||||
@@ -77,6 +77,13 @@ class Provider:
|
||||
|
||||
# Default provider definitions
|
||||
DEFAULT_PROVIDERS: List[Dict] = [
|
||||
# === NVIDIA NIM Provider (Tier 2 - Free) ===
|
||||
{
|
||||
"id": "nim", "name": "NVIDIA NIM", "auth_type": "api_key",
|
||||
"api_format": "openai_compat", "base_url": "https://integrate.api.nvidia.com/v1",
|
||||
"tier": 2, "default_model": os.getenv("NIM_MODEL", "openai/gpt-oss-120b"),
|
||||
"env_key": "NIM_API_KEY",
|
||||
},
|
||||
# === OAuth Providers (Tier 1 - Subscription) ===
|
||||
{
|
||||
"id": "claude_code", "name": "Claude Code", "auth_type": "oauth",
|
||||
|
||||
@@ -32,7 +32,7 @@ class TokenExtractor:
|
||||
"""Extracts tokens from CLI tools installed on the system."""
|
||||
|
||||
EXTRACTORS = [
|
||||
"claude_code", "codex_cli", "gemini_cli", "cursor",
|
||||
"claude_code", "codex_cli", "cursor",
|
||||
"copilot", "iflow", "qwen_code", "kiro",
|
||||
]
|
||||
|
||||
@@ -129,46 +129,6 @@ class TokenExtractor:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _extract_gemini_cli(self) -> Optional[ExtractedToken]:
|
||||
"""Gemini CLI: ~/.gemini/oauth_creds.json"""
|
||||
creds_path = Path.home() / ".gemini" / "oauth_creds.json"
|
||||
if not creds_path.exists():
|
||||
return None
|
||||
try:
|
||||
data = json.loads(creds_path.read_text())
|
||||
access_token = data.get("access_token")
|
||||
refresh = data.get("refresh_token")
|
||||
# Gemini CLI uses multiple field names for expiry
|
||||
expires = (
|
||||
data.get("expiry_date") # Gemini CLI uses this (milliseconds)
|
||||
or data.get("expires_at")
|
||||
or data.get("expiry")
|
||||
)
|
||||
if access_token and access_token.startswith("ya29."):
|
||||
# Parse expiry: may be ms timestamp, unix timestamp, or ISO string
|
||||
if isinstance(expires, (int, float)):
|
||||
# If > 1e12, it's milliseconds — convert to seconds
|
||||
if expires > 1e12:
|
||||
expires = expires / 1000.0
|
||||
elif isinstance(expires, str):
|
||||
try:
|
||||
from datetime import datetime
|
||||
dt = datetime.fromisoformat(expires.replace("Z", "+00:00"))
|
||||
expires = dt.timestamp()
|
||||
except Exception:
|
||||
expires = None
|
||||
return ExtractedToken(
|
||||
provider_id="gemini_cli",
|
||||
credential_type="oauth",
|
||||
token=access_token,
|
||||
refresh_token=refresh,
|
||||
expires_at=expires,
|
||||
label="Gemini CLI",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _extract_cursor(self) -> Optional[ExtractedToken]:
|
||||
"""Cursor: SQLite state.vscdb database"""
|
||||
is_mac = platform.system() == "Darwin"
|
||||
|
||||
@@ -104,8 +104,6 @@ class TokenRefresher:
|
||||
return await self._refresh_anthropic(account_id, refresh_token)
|
||||
elif provider_id == "codex_cli":
|
||||
return await self._refresh_openai(account_id, refresh_token)
|
||||
elif provider_id == "gemini_cli":
|
||||
return await self._refresh_google(account_id, refresh_token)
|
||||
else:
|
||||
logger.debug(
|
||||
f"TokenRefresher: No refresh method for {provider_id}"
|
||||
@@ -115,74 +113,6 @@ class TokenRefresher:
|
||||
logger.error(f"TokenRefresher: Refresh failed for {provider_id}: {e}")
|
||||
return False
|
||||
|
||||
async def _refresh_google(self, account_id: str, refresh_token: str) -> bool:
|
||||
"""Refresh a Google OAuth token."""
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
# Gemini CLI client_id/secret - extracted at runtime from CLI binary or set via env
|
||||
gemini_client_id = os.environ.get("GEMINI_CLI_CLIENT_ID", "")
|
||||
gemini_client_secret = os.environ.get("GEMINI_CLI_CLIENT_SECRET", "")
|
||||
if not gemini_client_id or not gemini_client_secret:
|
||||
# Try extracting from installed Gemini CLI
|
||||
try:
|
||||
from backend.core.smart_router.token_extractor import TokenExtractor
|
||||
extractor = TokenExtractor()
|
||||
creds = extractor._extract_gemini_oauth_creds()
|
||||
if creds:
|
||||
gemini_client_id = creds.get("client_id", "")
|
||||
gemini_client_secret = creds.get("client_secret", "")
|
||||
except Exception:
|
||||
pass
|
||||
payload = {
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refresh_token,
|
||||
"client_id": gemini_client_id,
|
||||
"client_secret": gemini_client_secret,
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(GOOGLE_TOKEN_URL, data=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
new_token = data.get("access_token")
|
||||
expires_in = data.get("expires_in", 3600)
|
||||
if new_token:
|
||||
self._registry.update_credential(
|
||||
account_id,
|
||||
new_token,
|
||||
time.time() + expires_in,
|
||||
)
|
||||
# Save refreshed token to Gemini CLI creds file
|
||||
self._save_gemini_cli_token(new_token, expires_in)
|
||||
logger.info(f"TokenRefresher: Google token refreshed (expires in {expires_in}s)")
|
||||
return True
|
||||
else:
|
||||
body = await resp.text()
|
||||
logger.warning(f"TokenRefresher: Google refresh failed: {resp.status} {body[:200]}")
|
||||
except ImportError:
|
||||
logger.warning("TokenRefresher: aiohttp not available for token refresh")
|
||||
except Exception as e:
|
||||
logger.error(f"TokenRefresher: Google refresh error: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _save_gemini_cli_token(new_token: str, expires_in: int):
|
||||
"""Write refreshed token back to Gemini CLI credentials file on disk."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
creds_path = Path.home() / ".gemini" / "oauth_creds.json"
|
||||
if not creds_path.exists():
|
||||
return
|
||||
try:
|
||||
data = json.loads(creds_path.read_text())
|
||||
data["access_token"] = new_token
|
||||
data["expiry_date"] = int((time.time() + expires_in) * 1000)
|
||||
creds_path.write_text(json.dumps(data, indent=2))
|
||||
logger.debug("TokenRefresher: Saved refreshed token to Gemini CLI creds file")
|
||||
except Exception as e:
|
||||
logger.debug(f"TokenRefresher: Failed to save Gemini CLI token: {e}")
|
||||
|
||||
async def _refresh_anthropic(self, account_id: str, refresh_token: str) -> bool:
|
||||
"""Refresh an Anthropic/Claude OAuth token."""
|
||||
try:
|
||||
|
||||
+6
-2
@@ -128,11 +128,15 @@ async def health_check():
|
||||
# Check LLM availability
|
||||
anthropic_key = os.getenv("ANTHROPIC_API_KEY", "")
|
||||
openai_key = os.getenv("OPENAI_API_KEY", "")
|
||||
nim_key = os.getenv("NIM_API_KEY", "")
|
||||
|
||||
llm_status = "not_configured"
|
||||
llm_provider = None
|
||||
|
||||
if anthropic_key and anthropic_key not in ["", "your-anthropic-api-key"]:
|
||||
if nim_key and nim_key not in ["", "your-nim-api-key"]:
|
||||
llm_status = "configured"
|
||||
llm_provider = "nim"
|
||||
elif anthropic_key and anthropic_key not in ["", "your-anthropic-api-key"]:
|
||||
llm_status = "configured"
|
||||
llm_provider = "claude"
|
||||
elif openai_key and openai_key not in ["", "your-openai-api-key"]:
|
||||
@@ -146,7 +150,7 @@ async def health_check():
|
||||
"llm": {
|
||||
"status": llm_status,
|
||||
"provider": llm_provider,
|
||||
"message": "AI agent ready" if llm_status == "configured" else "Set ANTHROPIC_API_KEY or OPENAI_API_KEY to enable AI features"
|
||||
"message": "AI agent ready" if llm_status == "configured" else "Set ANTHROPIC_API_KEY, OPENAI_API_KEY or NIM_API_KEY to enable AI features"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,3 +38,6 @@ apscheduler>=3.10.0
|
||||
httpx>=0.26.0
|
||||
pytest>=7.4.0
|
||||
pytest-asyncio>=0.23.0
|
||||
|
||||
# Document Processing
|
||||
PyPDF2>=3.0.0
|
||||
|
||||
+81
-3
@@ -25,8 +25,19 @@ logger = logging.getLogger(__name__)
|
||||
class LLMManager:
|
||||
"""Manage multiple LLM providers"""
|
||||
|
||||
def __init__(self, config: Dict):
|
||||
def __init__(self, config: Optional[Dict] = None):
|
||||
"""Initialize LLM manager"""
|
||||
if config is None:
|
||||
# Try to load from default config file or environment
|
||||
config = {}
|
||||
try:
|
||||
config_path = Path("config/config.json")
|
||||
if config_path.exists():
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load default config: {e}")
|
||||
|
||||
self.config = config.get('llm', {})
|
||||
self.default_profile_name = self.config.get('default_profile', 'gemini_pro_default')
|
||||
self.profiles = self.config.get('profiles', {})
|
||||
@@ -34,8 +45,37 @@ class LLMManager:
|
||||
self.active_profile = self.profiles.get(self.default_profile_name, {})
|
||||
|
||||
# Load active profile settings
|
||||
self.provider = self.active_profile.get('provider', 'gemini').lower()
|
||||
self.model = self.active_profile.get('model', 'gemini-pro')
|
||||
self.provider = self.active_profile.get('provider', '').lower()
|
||||
self.model = self.active_profile.get('model', '')
|
||||
|
||||
# Overriding priority: If NIM_API_KEY is in env and we are using a default/empty provider, use nim
|
||||
if (not self.provider or self.provider == 'gemini') and os.getenv("NIM_API_KEY"):
|
||||
self.provider = "nim"
|
||||
# If the model was specifically gemini-pro (default) or empty, change to a NIM model
|
||||
if not self.model or self.model == 'gemini-pro':
|
||||
self.model = os.getenv("DEFAULT_LLM_MODEL", "meta/llama-3.1-70b-instruct")
|
||||
elif not self.provider:
|
||||
# Detect from environment if not in config
|
||||
if os.getenv("ANTHROPIC_API_KEY"):
|
||||
self.provider = "claude"
|
||||
elif os.getenv("OPENAI_API_KEY"):
|
||||
self.provider = "gpt"
|
||||
elif os.getenv("GEMINI_API_KEY"):
|
||||
self.provider = "gemini"
|
||||
else:
|
||||
self.provider = "gemini" # Final fallback
|
||||
|
||||
# Default models per provider if still empty
|
||||
default_models = {
|
||||
"nim": "meta/llama-3.1-70b-instruct",
|
||||
"claude": "claude-3-5-sonnet-20240620",
|
||||
"gpt": "gpt-4o",
|
||||
"gemini": "gemini-1.5-pro"
|
||||
}
|
||||
|
||||
if not self.model:
|
||||
self.model = os.getenv("DEFAULT_LLM_MODEL", default_models.get(self.provider, "gemini-pro"))
|
||||
|
||||
self.api_key = self._get_api_key(self.active_profile.get('api_key', ''))
|
||||
self.temperature = self.active_profile.get('temperature', 0.7)
|
||||
self.max_tokens = self.active_profile.get('max_tokens', 4096)
|
||||
@@ -151,6 +191,8 @@ class LLMManager:
|
||||
try:
|
||||
if self.provider == 'claude':
|
||||
raw_response = self._generate_claude(prompt, system_prompt)
|
||||
elif self.provider == 'nim':
|
||||
raw_response = self._generate_nim(prompt, system_prompt)
|
||||
elif self.provider == 'gpt':
|
||||
raw_response = self._generate_gpt(prompt, system_prompt)
|
||||
elif self.provider == 'gemini':
|
||||
@@ -282,6 +324,42 @@ Identify any potential hallucinations, inconsistencies, or areas where the respo
|
||||
finally:
|
||||
self.hallucination_mitigation_strategy = original_mitigation_state # Restore original state
|
||||
|
||||
def _generate_nim(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
||||
"""Generate using NVIDIA NIM API (OpenAI-compatible)"""
|
||||
api_key = os.getenv("NIM_API_KEY", self.api_key)
|
||||
if not api_key:
|
||||
raise ValueError("NIM_API_KEY not set.")
|
||||
|
||||
base_url = os.getenv("NIM_BASE_URL", "https://integrate.api.nvidia.com/v1/chat/completions")
|
||||
url = base_url
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
messages = []
|
||||
if system_prompt:
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
data = {
|
||||
"model": self.model or "openai/gpt-oss-120b",
|
||||
"messages": messages,
|
||||
"temperature": self.temperature,
|
||||
"max_tokens": self.max_tokens
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=data, timeout=180)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
return result["choices"][0]["message"]["content"]
|
||||
else:
|
||||
raise ValueError(f"NIM API error {response.status_code}: {response.text}")
|
||||
except Exception as e:
|
||||
logger.error(f"NIM error: {e}")
|
||||
raise
|
||||
|
||||
def _generate_claude(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
||||
"""Generate using Claude API with requests (bypasses httpx/SSL issues on macOS)"""
|
||||
if not self.api_key:
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
"knowledge_entries": []
|
||||
}
|
||||
],
|
||||
"vuln_type_index": {},
|
||||
"vuln_type_index": {
|
||||
"information_disclosure": [],
|
||||
"clickjacking": []
|
||||
},
|
||||
"version": "1.0",
|
||||
"updated_at": "2026-02-16T14:50:31.618046"
|
||||
"updated_at": "2026-04-28T19:00:40.997968"
|
||||
}
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
# These override .env if set
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
- NIM_API_KEY=${NIM_API_KEY:-}
|
||||
- DATABASE_URL=sqlite+aiosqlite:///./data/neurosploit.db
|
||||
volumes:
|
||||
- neurosploit-data:/app/data
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# - Full Kali apt repos available for on-demand apt-get install of any security tool
|
||||
|
||||
# ---- Stage 1: Pre-compile Go security tools ----
|
||||
FROM golang:1.24-bookworm AS go-builder
|
||||
FROM golang:1.26-bookworm AS go-builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git build-essential libpcap-dev \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Kali-based container with real penetration testing tools
|
||||
# Provides Nuclei, Naabu, and other ProjectDiscovery tools via isolated execution
|
||||
|
||||
FROM golang:1.24-bookworm AS go-builder
|
||||
FROM golang:1.26-bookworm AS go-builder
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git build-essential && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NeuroSploit v3 - AI-Powered Penetration Testing</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -17,19 +17,19 @@ export default function Button({
|
||||
disabled,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const baseStyles = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-700 disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
const baseStyles = 'inline-flex items-center justify-center font-bold uppercase tracking-widest rounded-none transition-all duration-300 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed border'
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-primary-500 text-white hover:bg-primary-600 focus:ring-primary-500',
|
||||
secondary: 'bg-dark-900 text-white hover:bg-dark-800 focus:ring-dark-500',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
|
||||
ghost: 'text-dark-300 hover:text-white hover:bg-dark-900/50 focus:ring-dark-500',
|
||||
primary: 'bg-cyber-green/10 text-cyber-green border-cyber-green/50 hover:bg-cyber-green hover:text-black hover:shadow-neon-green',
|
||||
secondary: 'bg-cyber-blue/10 text-cyber-blue border-cyber-blue/50 hover:bg-cyber-blue hover:text-black hover:shadow-neon-blue',
|
||||
danger: 'bg-cyber-red/10 text-cyber-red border-cyber-red/50 hover:bg-cyber-red hover:text-black hover:shadow-[0_0_20px_rgba(255,0,85,0.4)]',
|
||||
ghost: 'bg-transparent text-dark-400 border-transparent hover:text-white hover:bg-white/5',
|
||||
}
|
||||
|
||||
const sizes = {
|
||||
sm: 'px-3 py-1.5 text-sm',
|
||||
md: 'px-4 py-2 text-sm',
|
||||
lg: 'px-6 py-3 text-base',
|
||||
sm: 'px-4 py-1.5 text-[10px] font-mono',
|
||||
md: 'px-6 py-2.5 text-xs font-mono',
|
||||
lg: 'px-8 py-3.5 text-sm font-mono',
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,17 +11,25 @@ interface CardProps {
|
||||
|
||||
export default function Card({ children, className, title, subtitle, action }: CardProps) {
|
||||
return (
|
||||
<div className={clsx('bg-dark-800 rounded-xl border border-dark-900/50', className)}>
|
||||
<div className={clsx('cyber-card group', className)}>
|
||||
{(title || action) && (
|
||||
<div className="flex items-center justify-between p-4 border-b border-dark-900/50">
|
||||
<div>
|
||||
{title && <h3 className="text-lg font-semibold text-white">{title}</h3>}
|
||||
{subtitle && <p className="text-sm text-dark-400 mt-1">{subtitle}</p>}
|
||||
<div className="flex items-center justify-between p-5 border-b border-cyber-green/10 relative overflow-hidden">
|
||||
{/* Subtle header pulse glow */}
|
||||
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-cyber-green/20 to-transparent"></div>
|
||||
|
||||
<div className="z-10">
|
||||
{title && (
|
||||
<h3 className="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 bg-cyber-green rounded-full shadow-neon-green"></span>
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
{subtitle && <p className="text-[10px] text-dark-400 mt-1 uppercase tracking-widest font-mono">{subtitle}</p>}
|
||||
</div>
|
||||
{action}
|
||||
<div className="z-10">{action}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4">{children}</div>
|
||||
<div className="p-5">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,16 +13,28 @@ export default function Header() {
|
||||
const title = pageTitles[location.pathname] || 'NeuroSploit'
|
||||
|
||||
return (
|
||||
<header className="h-16 bg-dark-800 border-b border-dark-900/50 flex items-center justify-between px-6">
|
||||
<h1 className="text-xl font-semibold text-white">{title}</h1>
|
||||
<header className="h-16 glass-panel border-b border-cyber-green/10 flex items-center justify-between px-8 z-40">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-dark-400">
|
||||
<div className="w-1 h-6 bg-cyber-green/50 rounded-full animate-pulse"></div>
|
||||
<h1 className="text-xl font-bold text-white tracking-tighter uppercase font-sans">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6 font-mono text-[11px]">
|
||||
<div className="flex items-center gap-2 px-3 py-1 bg-cyber-green/5 border border-cyber-green/20 rounded text-cyber-green">
|
||||
<span className="w-1.5 h-1.5 bg-cyber-green rounded-full animate-pulse"></span>
|
||||
SCANNER_ACTIVE
|
||||
</div>
|
||||
|
||||
<span className="text-dark-400 uppercase tracking-widest hidden sm:block">
|
||||
{new Date().toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
})}
|
||||
<span className="ml-2 text-cyber-green/30">|</span>
|
||||
<span className="ml-2">SEC_LEVEL: ALPHA</span>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -8,12 +8,17 @@ interface LayoutProps {
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
return (
|
||||
<div className="flex min-h-screen bg-dark-700 overflow-hidden">
|
||||
<div className="flex min-h-screen bg-cyber-black overflow-hidden selection:bg-cyber-green selection:text-black">
|
||||
{/* Dynamic scanning line */}
|
||||
<div className="fixed top-0 left-0 w-full h-1 bg-cyber-green/20 blur-[2px] z-[9999] animate-scanline pointer-events-none"></div>
|
||||
|
||||
<Sidebar />
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<div className="flex-1 flex flex-col min-w-0 relative">
|
||||
<Header />
|
||||
<main className="flex-1 p-6 overflow-auto">
|
||||
{children}
|
||||
<main className="flex-1 p-6 overflow-auto scrollbar-thin scrollbar-thumb-cyber-green/20">
|
||||
<div className="max-w-[1600px] mx-auto animate-fadeIn">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -63,25 +63,27 @@ export default function Sidebar() {
|
||||
<aside
|
||||
className={`${
|
||||
sidebarCollapsed ? 'w-16' : 'w-64'
|
||||
} bg-dark-800 border-r border-dark-900/50 flex flex-col transition-all duration-300 ease-in-out flex-shrink-0`}
|
||||
} glass-panel border-r border-cyber-green/10 flex flex-col transition-all duration-300 ease-in-out flex-shrink-0 z-50`}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className={`border-b border-dark-900/50 ${sidebarCollapsed ? 'p-3' : 'p-4'}`}>
|
||||
<div className={`border-b border-cyber-green/10 ${sidebarCollapsed ? 'p-3' : 'p-4'}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<Link to="/" className="flex items-center gap-3 min-w-0">
|
||||
<div className="w-10 h-10 bg-primary-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<Shield className="w-6 h-6 text-white" />
|
||||
<Link to="/" className="flex items-center gap-3 min-w-0 group">
|
||||
<div className="w-10 h-10 bg-cyber-green/10 border border-cyber-green/30 rounded-lg flex items-center justify-center flex-shrink-0 group-hover:shadow-neon-green transition-all duration-300">
|
||||
<Shield className="w-6 h-6 text-cyber-green animate-pulse-glow" />
|
||||
</div>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-lg font-bold text-white truncate">NeuroSploit</h1>
|
||||
<p className="text-xs text-dark-400">v3.0 AI Pentest</p>
|
||||
<h1 className="text-lg font-bold text-white truncate tracking-tighter uppercase">
|
||||
Neuro<span className="text-cyber-green">Sploit</span>
|
||||
</h1>
|
||||
<p className="text-[10px] text-cyber-green/50 font-mono tracking-widest uppercase">v3.0 AI PENTEST</p>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="text-dark-400 hover:text-white transition-colors p-1 rounded hover:bg-dark-700 flex-shrink-0"
|
||||
className="text-dark-400 hover:text-cyber-green transition-all p-1 rounded hover:bg-cyber-green/5 flex-shrink-0 border border-transparent hover:border-cyber-green/20"
|
||||
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
@@ -94,16 +96,17 @@ export default function Sidebar() {
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 p-2 overflow-y-auto overflow-x-hidden">
|
||||
<nav className="flex-1 p-2 overflow-y-auto overflow-x-hidden space-y-4 pt-4">
|
||||
{navGroups.map((group) => (
|
||||
<div key={group.label} className="mb-3">
|
||||
<div key={group.label}>
|
||||
{!sidebarCollapsed && (
|
||||
<p className="px-3 mb-1 text-[10px] font-semibold uppercase text-dark-500 tracking-wider">
|
||||
<p className="px-3 mb-2 text-[10px] font-bold uppercase text-dark-500 tracking-[0.2em] flex items-center gap-2">
|
||||
<span className="w-1 h-1 bg-cyber-green/30 rounded-full"></span>
|
||||
{group.label}
|
||||
</p>
|
||||
)}
|
||||
{sidebarCollapsed && <div className="border-t border-dark-700/50 mx-2 mb-2 mt-1" />}
|
||||
<ul className="space-y-0.5">
|
||||
{sidebarCollapsed && <div className="border-t border-cyber-green/5 mx-2 mb-2 mt-1" />}
|
||||
<ul className="space-y-1">
|
||||
{group.items.map((item) => {
|
||||
const isActive = location.pathname === item.path
|
||||
const Icon = item.icon
|
||||
@@ -114,16 +117,19 @@ export default function Sidebar() {
|
||||
title={sidebarCollapsed ? item.label : undefined}
|
||||
className={`flex items-center ${
|
||||
sidebarCollapsed ? 'justify-center px-2' : 'gap-3 px-3'
|
||||
} py-2.5 rounded-lg transition-colors ${
|
||||
} py-2.5 rounded transition-all duration-200 relative group overflow-hidden ${
|
||||
isActive
|
||||
? 'bg-primary-500/20 text-primary-500'
|
||||
: 'text-dark-300 hover:bg-dark-900/50 hover:text-white'
|
||||
? 'bg-cyber-green/10 text-cyber-green border-l-2 border-cyber-green shadow-[inset_4px_0_10px_rgba(0,255,102,0.1)]'
|
||||
: 'text-dark-300 hover:bg-cyber-green/5 hover:text-white border-l-2 border-transparent'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||
<Icon className={`w-5 h-5 flex-shrink-0 transition-transform duration-300 ${isActive ? 'scale-110' : 'group-hover:scale-110 group-hover:text-cyber-green'}`} />
|
||||
{!sidebarCollapsed && (
|
||||
<span className="whitespace-nowrap text-sm">{item.label}</span>
|
||||
<span className="whitespace-nowrap text-sm font-medium tracking-tight">{item.label}</span>
|
||||
)}
|
||||
|
||||
{/* Hover effect light */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-cyber-green/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
@@ -134,10 +140,18 @@ export default function Sidebar() {
|
||||
</nav>
|
||||
|
||||
{/* Status */}
|
||||
<div className="p-3 border-t border-dark-900/50">
|
||||
<div className={`flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-2'} text-sm`}>
|
||||
<Activity className="w-4 h-4 text-green-500 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <span className="text-dark-400">System Online</span>}
|
||||
<div className="p-4 border-t border-cyber-green/10 bg-cyber-green/[0.02]">
|
||||
<div className={`flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} text-[11px] font-mono`}>
|
||||
<div className="relative">
|
||||
<Activity className="w-4 h-4 text-cyber-green" />
|
||||
<div className="absolute inset-0 bg-cyber-green rounded-full blur-[4px] animate-pulse opacity-50"></div>
|
||||
</div>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white font-bold uppercase tracking-tighter">System Online</span>
|
||||
<span className="text-cyber-green/50 text-[9px] uppercase">Node: NS-2026-ALPHA</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -3,61 +3,133 @@
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--bg-card: #0f3460;
|
||||
--text-primary: #eee;
|
||||
--text-secondary: #aaa;
|
||||
--accent: #e94560;
|
||||
--border: #333;
|
||||
--bg-primary: #050505;
|
||||
--bg-secondary: #0a0a0a;
|
||||
--bg-card: #121212;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #94a3b8;
|
||||
--accent: #00ff66;
|
||||
--border: #1a1a1a;
|
||||
--cyber-green: #00ff66;
|
||||
--cyber-blue: #00f0ff;
|
||||
--cyber-purple: #bd00ff;
|
||||
--cyber-red: #ff0055;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
font-family: 'Space Grotesk', -apple-system, sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Background Grid Effect */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-image:
|
||||
linear-gradient(rgba(0, 255, 102, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0, 255, 102, 0.03) 1px, transparent 1px);
|
||||
background-size: 30px 30px;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* Scanline Effect */
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
rgba(18, 16, 16, 0) 50%,
|
||||
rgba(0, 0, 0, 0.1) 50%
|
||||
), linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 0, 0, 0.02),
|
||||
rgba(0, 255, 0, 0.01),
|
||||
rgba(0, 0, 255, 0.02)
|
||||
);
|
||||
background-size: 100% 4px, 3px 100%;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
/* Custom cyber scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-secondary);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-card);
|
||||
border-radius: 4px;
|
||||
background: var(--border);
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 10px var(--accent);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
/* Cyber Text Effects */
|
||||
.cyber-glitch {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
.cyber-glitch::after {
|
||||
content: attr(data-text);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 2px;
|
||||
text-shadow: -1px 0 var(--cyber-red);
|
||||
background: var(--bg-primary);
|
||||
overflow: hidden;
|
||||
clip: rect(0, 900px, 0, 0);
|
||||
animation: glitch-anim 2s infinite linear alternate-reverse;
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
@keyframes glitch-anim {
|
||||
0% { clip: rect(10px, 9999px, 50px, 0); }
|
||||
20% { clip: rect(30px, 9999px, 10px, 0); }
|
||||
40% { clip: rect(80px, 9999px, 40px, 0); }
|
||||
60% { clip: rect(20px, 9999px, 70px, 0); }
|
||||
80% { clip: rect(40px, 9999px, 20px, 0); }
|
||||
100% { clip: rect(60px, 9999px, 80px, 0); }
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s infinite;
|
||||
/* Glassmorphism utility */
|
||||
.glass-panel {
|
||||
background: rgba(10, 10, 10, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.cyber-card {
|
||||
@apply glass-panel transition-all duration-300;
|
||||
}
|
||||
|
||||
.cyber-card:hover {
|
||||
border-color: rgba(0, 255, 102, 0.3);
|
||||
box-shadow: 0 0 20px rgba(0, 255, 102, 0.1);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,33 @@ export default {
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Space Grotesk', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'monospace'],
|
||||
},
|
||||
colors: {
|
||||
cyber: {
|
||||
black: '#050505',
|
||||
darker: '#0a0a0a',
|
||||
dark: '#121212',
|
||||
gray: '#1a1a1a',
|
||||
green: '#00ff66',
|
||||
blue: '#00f0ff',
|
||||
purple: '#bd00ff',
|
||||
red: '#ff0055',
|
||||
yellow: '#f3ff00',
|
||||
},
|
||||
primary: {
|
||||
50: '#fef2f2',
|
||||
100: '#fee2e2',
|
||||
200: '#fecaca',
|
||||
300: '#fca5a5',
|
||||
400: '#f87171',
|
||||
500: '#e94560',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
800: '#991b1b',
|
||||
900: '#7f1d1d',
|
||||
500: '#00ff66', // Switching primary to cyber green
|
||||
600: '#00e65c',
|
||||
700: '#00cc52',
|
||||
800: '#00b347',
|
||||
900: '#00993d',
|
||||
},
|
||||
dark: {
|
||||
50: '#f8fafc',
|
||||
@@ -27,12 +42,38 @@ export default {
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#1a1a2e',
|
||||
800: '#16213e',
|
||||
900: '#0f3460',
|
||||
950: '#0a0a15',
|
||||
700: '#050505', // True black for main bg
|
||||
800: '#0a0a0a', // Darker cards
|
||||
900: '#121212', // Subtle borders
|
||||
950: '#020202',
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'glitch': 'glitch 1s linear infinite',
|
||||
'scanline': 'scanline 8s linear infinite',
|
||||
'pulse-glow': 'pulse-glow 2s ease-in-out infinite',
|
||||
'matrix': 'matrix 20s linear infinite',
|
||||
},
|
||||
keyframes: {
|
||||
glitch: {
|
||||
'2%, 64%': { transform: 'translate(2px, 0) skew(0deg)' },
|
||||
'4%, 60%': { transform: 'translate(-2px, 0) skew(0deg)' },
|
||||
'62%': { transform: 'translate(0, 0) skew(5deg)' },
|
||||
},
|
||||
scanline: {
|
||||
'0%': { transform: 'translateY(-100%)' },
|
||||
'100%': { transform: 'translateY(100%)' },
|
||||
},
|
||||
'pulse-glow': {
|
||||
'0%, 100%': { opacity: '1', filter: 'drop-shadow(0 0 5px #00ff66)' },
|
||||
'50%': { opacity: '0.7', filter: 'drop-shadow(0 0 20px #00ff66)' },
|
||||
},
|
||||
},
|
||||
boxShadow: {
|
||||
'neon-green': '0 0 5px #00ff66, 0 0 20px rgba(0, 255, 102, 0.2)',
|
||||
'neon-blue': '0 0 5px #00f0ff, 0 0 20px rgba(0, 240, 255, 0.2)',
|
||||
'neon-purple': '0 0 5px #bd00ff, 0 0 20px rgba(189, 0, 255, 0.2)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"updated_at": "2026-02-24T13:16:20.190712",
|
||||
"updated_at": "2026-04-27T10:20:59.887416",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "recon_full",
|
||||
|
||||
@@ -42,3 +42,8 @@ dev = ["pytest>=7.4.0", "pytest-asyncio>=0.23.0"]
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = [
|
||||
"backend",
|
||||
]
|
||||
|
||||
@@ -67,7 +67,7 @@ docker image inspect "$IMAGE_NAME" --format \
|
||||
if [ "$RUN_TEST" = true ]; then
|
||||
echo ""
|
||||
echo "[*] Running health check..."
|
||||
docker run --rm "$IMAGE_NAME" \
|
||||
docker run --rm "$IMAGE_NAME" bash -c \
|
||||
"nuclei -version 2>&1; echo '---'; naabu -version 2>&1; echo '---'; httpx -version 2>&1; echo '---'; subfinder -version 2>&1; echo '---'; nmap --version 2>&1 | head -1; echo '---'; nikto -Version 2>&1 | head -1; echo '---'; sqlmap --version 2>&1; echo '---'; ffuf -V 2>&1; echo '---'; echo 'ALL OK'"
|
||||
echo ""
|
||||
echo "[+] Health check passed"
|
||||
|
||||
Reference in New Issue
Block a user