Skip to content

Commit 5792692

Browse files
committed
improvements
1 parent f5cda91 commit 5792692

3 files changed

Lines changed: 120 additions & 14 deletions

File tree

src/secnodeapi/ai/generate.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,26 @@
1414

1515

1616
async def generate_test_cases(
17-
understanding: APIUnderstanding, structure: SchemaStructure, instructions: Optional[Dict[str, Any]] = None
17+
understanding: APIUnderstanding,
18+
structure: SchemaStructure,
19+
instructions: Optional[Dict[str, Any]] = None,
20+
tool_context: Optional[Dict[str, Any]] = None,
1821
) -> List[TestCase]:
1922
"""Generate adversarial test cases for OWASP and business logic flaws."""
20-
logger.info("Generating adversarial test cases with AI")
23+
logger.info(
24+
"Generating adversarial test cases with AI",
25+
has_instructions=bool(instructions),
26+
has_tool_context=bool(tool_context),
27+
)
2128

2229
sys_prompt = (
2330
"You are an elite Offensive Security Researcher evaluating an API. Your goal is to generate "
2431
"aggressive, complex, and highly realistic test cases. You MUST meticulously analyze the API structure, "
2532
"infer data models, identify relationships between differing endpoints, and exploit implied business logic constraints. "
33+
"In addition, you MUST fully leverage reconnaissance results from tools like Dirsearch and Arjun:\n"
34+
"- Use tool-discovered endpoints and paths (including shadow/admin/debug routes) as high-priority targets.\n"
35+
"- Use Arjun-discovered parameters for parameter tampering, auth bypass, and hidden debug flags.\n"
36+
"- Combine schema-derived parameters with recon-discovered parameters to craft richer attacks.\n\n"
2637
"Focus heavily on:\n"
2738
"1. BOLA/IDOR: Swapping UUIDs/integers, wrapping IDs in arrays, using wildcard IDs.\n"
2839
"2. BOPLA (Mass Assignment): Injecting 'is_admin': true, 'role': 'admin', 'credit_balance': 99999 into body payloads.\n"
@@ -36,12 +47,22 @@ async def generate_test_cases(
3647

3748
user_prompt = (
3849
f"API Understanding Context:\n{understanding.model_dump_json(indent=2)}\n\n"
39-
f"Complete Target API Structure:\n{structure.model_dump_json(indent=2)}\n\n"
50+
f"Complete Target API Structure (including enriched endpoints/parameters):\n{structure.model_dump_json(indent=2)}\n\n"
4051
"Generate 100-200 highly targeted, exhaustive test cases ensuring sweeping coverage "
4152
"of OWASP API Top 10 and tailored business logic exploitation vectors specific to this API's purpose. "
4253
"Ensure realistic payloads based on the defined schemas (e.g. if an endpoint expects JSON, provide a structured JSON body)."
4354
)
4455

56+
if tool_context:
57+
user_prompt += (
58+
"\n\n[RECON & TOOL SIGNALS]\n"
59+
"Use the following signals from Dirsearch, Arjun, and other tools to bias test generation "
60+
"towards likely-vulnerable or undocumented surfaces:\n"
61+
f"{json.dumps(tool_context, indent=2)}\n"
62+
"- Prioritize endpoints and parameters that only appear in recon/tool output.\n"
63+
"- Generate additional tests that mix tool-discovered params with sensitive actions (admin, payments, account management, exports, reporting, etc.)."
64+
)
65+
4566
if instructions:
4667
greybox_context = (
4768
"\n\n[GREYBOX CONTEXT - AUTHENTICATED TESTING]\n"
@@ -66,4 +87,4 @@ async def generate_test_cases(
6687
return [TestCase(**t) for t in data.get("tests", [])]
6788
except Exception as e:
6889
logger.error("Failed to parse TestCases JSON", error=str(e), result=result)
69-
return []
90+
return []

src/secnodeapi/ai/understand.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
AI understanding stage for API context analysis.
33
"""
44
import json
5+
from typing import Any, Dict, Optional
56

67
import structlog
78

@@ -12,17 +13,33 @@
1213
logger = structlog.get_logger(__name__)
1314

1415

15-
async def understand_api_with_ai(structure: SchemaStructure) -> APIUnderstanding:
16+
async def understand_api_with_ai(
17+
structure: SchemaStructure,
18+
tool_context: Optional[Dict[str, Any]] = None,
19+
) -> APIUnderstanding:
1620
"""Evaluate API business purpose and trust boundaries with AI."""
17-
logger.info("Analyzing API business context with AI")
21+
logger.info(
22+
"Analyzing API business context with AI",
23+
has_tool_context=bool(tool_context),
24+
)
1825

1926
sys_prompt = (
2027
"You are an expert Application Security Architect. Analyze this API structure "
21-
"and output ONLY valid JSON matching this schema:\n"
28+
"together with reconnaissance signals from external tools (e.g. Dirsearch, Arjun, Nuclei). "
29+
"Use tool-discovered endpoints, parameters, and weak signals to refine your understanding of the true attack surface. "
30+
"Output ONLY valid JSON matching this schema:\n"
2231
'{"business_context": "string", "trust_boundaries": ["string"], "high_risk_flows": ["string"]}'
2332
)
33+
2434
user_prompt = f"API Structure:\n{structure.model_dump_json(indent=2)}"
2535

36+
if tool_context:
37+
user_prompt += (
38+
"\n\nReconnaissance & tool signals (Dirsearch / Arjun / others):\n"
39+
f"{json.dumps(tool_context, indent=2)}\n"
40+
"Pay special attention to shadow/admin paths, undocumented methods, and discovered parameters when reasoning about trust boundaries and high-risk flows."
41+
)
42+
2643
result = await call_llm(sys_prompt, user_prompt, temperature=0.1)
2744
try:
2845
data = json.loads(result)
@@ -33,4 +50,4 @@ async def understand_api_with_ai(structure: SchemaStructure) -> APIUnderstanding
3350
business_context="Failed to parse",
3451
trust_boundaries=[],
3552
high_risk_flows=[],
36-
)
53+
)

src/secnodeapi/services/pipeline.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime
66
import json
77
import re
8-
from typing import Dict, List, Optional, Tuple
8+
from typing import Any, Dict, List, Optional, Tuple
99
from urllib.parse import urlparse
1010

1111
import structlog
@@ -339,6 +339,21 @@ async def run_agent_pipeline(
339339
)
340340
queue = _deduplicate_test_cases(queue + chain_tests)
341341

342+
# Human-friendly iteration summary in the terminal
343+
console.print(
344+
Panel(
345+
f"[bold]Iteration {iteration} summary[/bold]\n"
346+
f"- Executed tests this iteration: [cyan]{len(results)}[/cyan]\n"
347+
f"- New confirmed findings: [green]{len(batch_confirmed)}[/green]\n"
348+
f"- New suspected findings: [yellow]{len(batch_suspected)}[/yellow]\n"
349+
f"- Chained follow-up tests queued: [magenta]{len(chain_tests)}[/magenta]\n"
350+
f"- Remaining request budget: [white]{remaining_budget}[/white]\n"
351+
f"- Dynamic concurrency: [white]{dynamic_concurrency}[/white]",
352+
title=f"🤖 Agent Iteration {iteration}",
353+
border_style="magenta",
354+
)
355+
)
356+
342357
# Run final AI deduplication
343358
console.rule("[bold yellow]Phase 3: Final Intelligence Processing")
344359
confirmed = await deduplicate_findings_with_ai(confirmed)
@@ -369,7 +384,7 @@ async def build_pipeline_artifacts(
369384
verify_ssl=pipeline_input.verify_ssl,
370385
)
371386
api_structure = analyze_api_structure(raw_schema)
372-
387+
373388
# Phase 1a — Active Reconnaissance (built-in fuzzer)
374389
discovered_endpoints = await perform_active_recon(api_structure, pipeline_input)
375390
if discovered_endpoints:
@@ -380,9 +395,10 @@ async def build_pipeline_artifacts(
380395
api_structure.endpoints = list(unique_endpoints.values())
381396

382397
# Phase 1b — External Tool Orchestration (Dirsearch, Arjun, Nuclei, SQLMap)
398+
tool_context: Optional[Dict[str, Any]] = None
383399
try:
384400
orch_result = await run_tool_orchestration_phase(api_structure, api_structure.base_url)
385-
401+
386402
# Merge tool-discovered endpoints into schema
387403
if orch_result.discovered_endpoints:
388404
for ep in orch_result.discovered_endpoints:
@@ -396,22 +412,74 @@ async def build_pipeline_artifacts(
396412
total=len(api_structure.endpoints),
397413
added=len(orch_result.discovered_endpoints),
398414
)
415+
416+
# Merge Dirsearch/Arjun-discovered parameters back into API structure
417+
if orch_result.discovered_params:
418+
merged_count = 0
419+
for ep_key, params in orch_result.discovered_params.items():
420+
# Normalise the endpoint key (which may be a full URL) down to a path
421+
try:
422+
parsed = urlparse(ep_key)
423+
path = parsed.path or ep_key
424+
except Exception:
425+
path = ep_key
426+
if not path.startswith("/"):
427+
path = f"/{path}"
428+
429+
for endpoint in api_structure.endpoints:
430+
if endpoint.path != path:
431+
continue
432+
existing_param_names = {p.name for p in endpoint.parameters}
433+
for p_name in params:
434+
if not p_name or p_name in existing_param_names:
435+
continue
436+
endpoint.parameters.append(
437+
type(endpoint).model_fields["parameters"].annotation.__args__[0]( # type: ignore[index]
438+
name=p_name,
439+
**{"in": "query"},
440+
required=False,
441+
schema_type="string",
442+
)
443+
)
444+
merged_count += 1
445+
logger.info(
446+
"Merged tool-discovered parameters",
447+
total_parameters_merged=merged_count,
448+
)
449+
450+
# Build compact tool context for downstream AI prompts
451+
tool_context = {
452+
"dirsearch_discovered_paths": sorted(
453+
{ep.path for ep in orch_result.discovered_endpoints}
454+
),
455+
"tool_discovered_params": orch_result.discovered_params,
456+
"tool_findings_sample": [
457+
{
458+
"endpoint": f.endpoint,
459+
"method": f.method,
460+
"vuln": f.vulnerability_class,
461+
"cvss": f.cvss_score,
462+
}
463+
for f in orch_result.tool_findings[:25]
464+
],
465+
}
399466
except Exception as e:
400467
logger.warning("Tool orchestration phase failed, continuing without tools", error=str(e))
401468
orch_result = None
469+
tool_context = None
402470

403471
# Phase 1c — Deep Doc Analysis + AI-Directed Arjun Parameter Discovery
404472
try:
405473
api_structure = await perform_deep_recon(raw_schema, api_structure, pipeline_input)
406474
except Exception as e:
407475
logger.warning("Deep recon phase failed, continuing without parameter enrichment", error=str(e))
408476

409-
410-
understanding = await understand_api_with_ai(api_structure)
477+
understanding = await understand_api_with_ai(api_structure, tool_context=tool_context)
411478
tests = await generate_test_cases(
412479
understanding,
413480
api_structure,
414-
instructions=pipeline_input.instructions[0].model_dump() if pipeline_input.instructions else None
481+
instructions=pipeline_input.instructions[0].model_dump() if pipeline_input.instructions else None,
482+
tool_context=tool_context,
415483
)
416484
return api_structure, tests, orch_result
417485

0 commit comments

Comments
 (0)