diff --git a/requirements.txt b/requirements.txt
index ea973c5..14b1597 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,4 @@ pydantic>=2.8.0
tenacity>=8.2.3
rich>=13.7.1
python-dotenv>=1.0.1
+requests>=2.32.3
diff --git a/src/__pycache__/config.cpython-313.pyc b/src/__pycache__/config.cpython-313.pyc
index 30c6732..bcf9b5f 100644
Binary files a/src/__pycache__/config.cpython-313.pyc and b/src/__pycache__/config.cpython-313.pyc differ
diff --git a/src/__pycache__/run.cpython-313.pyc b/src/__pycache__/run.cpython-313.pyc
index 16f6abb..838094c 100644
Binary files a/src/__pycache__/run.cpython-313.pyc and b/src/__pycache__/run.cpython-313.pyc differ
diff --git a/src/agent/__pycache__/orchestrator.cpython-313.pyc b/src/agent/__pycache__/orchestrator.cpython-313.pyc
index 7052308..b3cfe80 100644
Binary files a/src/agent/__pycache__/orchestrator.cpython-313.pyc and b/src/agent/__pycache__/orchestrator.cpython-313.pyc differ
diff --git a/src/agent/__pycache__/planner.cpython-313.pyc b/src/agent/__pycache__/planner.cpython-313.pyc
index 602384a..44681e2 100644
Binary files a/src/agent/__pycache__/planner.cpython-313.pyc and b/src/agent/__pycache__/planner.cpython-313.pyc differ
diff --git a/src/agent/orchestrator.py b/src/agent/orchestrator.py
index 8e497c1..440057c 100644
--- a/src/agent/orchestrator.py
+++ b/src/agent/orchestrator.py
@@ -3,7 +3,7 @@ For MVP we call hardcoded skills; planner integration is available for future lo
"""
from typing import Dict, Any, Callable
from . import planner
-from ..skills import login, xss_reflected_low, sqli_low, xss_stored_low, xss_dom_low
+from ..skills import login, xss_reflected_low, sqli_low, xss_stored_low, xss_dom_low, xss_reflected_low_smart, sqli_low_smart
SKILLS: Dict[str, Callable[[str], Dict[str, Any]]] = {
"login": lambda base: login.run(base),
@@ -11,6 +11,8 @@ SKILLS: Dict[str, Callable[[str], Dict[str, Any]]] = {
"xss_reflected_low": lambda base: xss_reflected_low.run(base),
"xss_dom_low": lambda base: xss_dom_low.run(base),
"sqli_low": lambda base: sqli_low.run(base),
+ "sqli_low_smart": lambda base: sqli_low_smart.run(base),
+ "xss_reflected_low_smart": lambda base: xss_reflected_low_smart.run(base),
}
def run_skill(base_url: str, skill: str) -> Dict[str, Any]:
diff --git a/src/agent/planner.py b/src/agent/planner.py
index 96b5f04..b5cb7a0 100644
--- a/src/agent/planner.py
+++ b/src/agent/planner.py
@@ -1,4 +1,39 @@
-from ..openai_client import plan_next_action
+from typing import Dict, Any, List
+from ..models import get_provider
+
+_provider = get_provider()
def decide(context: dict) -> dict:
- return plan_next_action(context)
+ """Mantém compat com o antigo: planejar uma ação simples."""
+ system = "You are a safe web security agent. Return STRICT JSON."
+ user = (
+ "Given this context, propose one next action as JSON:\n"
+ "{'tool':'navigate|fill_form|click|noop','target':'url|selector','data':null|{...},'why':'short'}\n\n"
+ f"Context:\n{context}"
+ )
+ res = _provider.complete_json(system, user)
+ if not isinstance(res, dict):
+ res = {"tool":"noop","target":"","data":None,"why":"fallback"}
+ return res
+
+def propose_fuzz_payloads(category: str, page_context: dict, seeds: List[str], budget: int=8) -> List[str]:
+ """Pede ao modelo variações de payload com base nos seeds e contexto da página."""
+ system = "You are an offensive security payload generator. Return STRICT JSON: {'payloads': [..]}."
+ user = (
+ f"Category: {category}\n"
+ f"Seeds: {seeds}\n"
+ f"Page context (inputs, hints, server msgs): {page_context}\n"
+ f"Generate up to {budget} diverse payloads. Focus on low-noise, high-signal candidates for DVWA Low.\n"
+ "Only return JSON: {'payloads': ['...','...']}."
+ )
+ res = _provider.complete_json(system, user)
+ pls = res.get("payloads", []) if isinstance(res, dict) else []
+ # sanity filter
+ uniq = []
+ for p in pls:
+ if not isinstance(p, str): continue
+ p = p.strip()
+ if p and p not in uniq and len(p) < 256:
+ uniq.append(p)
+ # fallback: se nada vier, retorne seeds
+ return uniq or seeds[:budget]
diff --git a/src/config.py b/src/config.py
index 83c7c8c..d954c69 100644
--- a/src/config.py
+++ b/src/config.py
@@ -2,8 +2,22 @@ from pydantic import BaseModel
import os
class Settings(BaseModel):
+ # Provider: "openai", "ollama", "llamacpp"
+ model_provider: str = os.getenv("MODEL_PROVIDER", "openai").lower()
+
+ # OpenAI
openai_api_key: str = os.getenv("OPENAI_API_KEY", "")
openai_model: str = os.getenv("OPENAI_MODEL", "gpt-5")
+
+ # Ollama (LLaMA)
+ llama_base_url: str = os.getenv("LLAMA_BASE_URL", "http://localhost:11434")
+ llama_model: str = os.getenv("LLAMA_MODEL", "llama3.1") # ex: llama3.1, llama3.2:latest
+
+ # llama.cpp (local python)
+ llamacpp_model_path: str = os.getenv("LLAMACPP_MODEL_PATH", "")
+ llamacpp_n_threads: int = int(os.getenv("LLAMACPP_N_THREADS", "4"))
+
+ # Safety / target
allowlist_hosts: list[str] = [h.strip() for h in os.getenv("ALLOWLIST_HOSTS", "localhost,127.0.0.1,dvwa").split(",")]
dvwa_url_env: str = os.getenv("DVWA_URL", "").strip()
headless: bool = os.getenv("HEADLESS", "true").lower() == "true"
diff --git a/src/fuzz/__pycache__/engine.cpython-313.pyc b/src/fuzz/__pycache__/engine.cpython-313.pyc
new file mode 100644
index 0000000..8a70788
Binary files /dev/null and b/src/fuzz/__pycache__/engine.cpython-313.pyc differ
diff --git a/src/fuzz/__pycache__/seeds.cpython-313.pyc b/src/fuzz/__pycache__/seeds.cpython-313.pyc
new file mode 100644
index 0000000..5ed9b76
Binary files /dev/null and b/src/fuzz/__pycache__/seeds.cpython-313.pyc differ
diff --git a/src/fuzz/engine.py b/src/fuzz/engine.py
new file mode 100644
index 0000000..081484f
--- /dev/null
+++ b/src/fuzz/engine.py
@@ -0,0 +1,20 @@
+from typing import List, Dict, Any, Callable
+from ..agent.planner import propose_fuzz_payloads
+
+def generate_candidates(category: str, page_ctx: dict, seeds: List[str], budget: int=8) -> List[str]:
+ """Combina seeds + LLM proposals."""
+ props = propose_fuzz_payloads(category, page_ctx, seeds, budget)
+ pool = list(dict.fromkeys(seeds + props)) # dedup preservando ordem
+ return pool[: max(budget, len(seeds))]
+
+def try_candidates(try_func: Callable[[str], Dict[str, Any]], candidates: List[str]) -> Dict[str, Any]:
+ """Executa candidatos até achar sucesso, retornando o melhor resultado."""
+ best = {"ok": False}
+ for p in candidates:
+ res = try_func(p)
+ if res.get("ok"):
+ return res
+ # guarda “quase bom” se tiver um reason/signal
+ if not best.get("ok") and len(res.get("evidence_excerpt","")) > len(best.get("evidence_excerpt","")):
+ best = res
+ return best
diff --git a/src/fuzz/seeds.py b/src/fuzz/seeds.py
new file mode 100644
index 0000000..00c9b70
--- /dev/null
+++ b/src/fuzz/seeds.py
@@ -0,0 +1,19 @@
+SQLI_SEEDS = [
+ "1' OR '1'='1' -- ",
+ "' OR '1'='1' -- ",
+ "1' OR 1=1 -- ",
+ "1' OR '1'='1'#",
+]
+
+XSS_REFLECTED_SEEDS = [
+ '',
+ '">',
+ '
',
+ '