Files
hive/examples/templates/vulnerability_assessment/nodes/__init__.py
T

393 lines
16 KiB
Python

"""Node definitions for Passive Website Vulnerability Assessment."""
from framework.graph import NodeSpec
# Node 1: Intake (client-facing)
# Collect the target domain and confirm scanning scope.
intake_node = NodeSpec(
id="intake",
name="Intake",
description="Collect the target website domain from the user and confirm the scanning scope",
node_type="event_loop",
client_facing=True,
max_node_visits=0,
input_keys=[],
output_keys=["target_domain"],
system_prompt="""\
You are the intake specialist for a passive website vulnerability assessment agent.
**STEP 1 — Greet and collect target (text only, NO tool calls):**
Ask the user for the website domain they want to assess. If they already provided one, \
confirm it.
Clarify:
- The exact domain or URL (e.g., example.com, https://app.example.com)
- Any specific areas of concern (e.g., email security, SSL, exposed services)
Explain briefly that this is a **passive, non-intrusive assessment** — we only examine \
publicly available information (SSL certificates, HTTP headers, DNS records, open ports, \
tech fingerprints, and public subdomain data). No attack payloads or exploit attempts.
Keep it brief. One message, 2-3 questions max.
After your message, call ask_user() to wait for the user's response.
**STEP 2 — After the user responds, call set_output:**
- set_output("target_domain", "the confirmed domain/URL to test, e.g. https://example.com")
""",
tools=[],
)
# Node 2: Passive Reconnaissance
# Runs all 6 scanning tools — no CLI dependencies, no attack payloads.
passive_recon_node = NodeSpec(
id="passive-recon",
name="Passive Reconnaissance",
description=(
"Run all 6 passive scanning tools against the target domain: SSL/TLS, "
"HTTP headers, DNS security, port scanning, tech stack detection, and "
"subdomain enumeration"
),
node_type="event_loop",
max_node_visits=0,
input_keys=["target_domain", "feedback"],
output_keys=["scan_results"],
system_prompt="""\
You are a passive reconnaissance specialist. Given a target domain, run all 6 scanning \
tools to assess the security posture. These tools are non-intrusive and OSINT-based.
If feedback is provided (not None/empty), this is a follow-up round — focus on the areas \
the user requested. You may skip tools that aren't relevant to the feedback. If feedback \
is None or empty, this is the first scan — run ALL 6 tools.
**Run these tools against the target domain:**
1. **ssl_tls_scan(hostname)** — Checks TLS version, certificate validity, cipher strength
2. **http_headers_scan(url)** — Checks OWASP-recommended security headers (HSTS, CSP, \
X-Frame-Options, etc.)
3. **dns_security_scan(domain)** — Checks SPF, DMARC, DKIM, DNSSEC, zone transfer
4. **port_scan(hostname)** — TCP connect scan on top 20 common ports, flags exposed \
database/admin ports
5. **tech_stack_detect(url)** — Detects web server, framework, CMS, JS libraries, cookies
6. **subdomain_enumerate(domain)** — Queries Certificate Transparency logs for subdomains
**IMPORTANT:**
- Extract just the hostname/domain from the URL for tools that need it \
(e.g., "example.com" not "https://example.com")
- Use the full URL (with https://) for http_headers_scan and tech_stack_detect
- Run tools in batches of 2-3 to avoid overwhelming the system
- If a tool fails, note the error and continue with the remaining tools
**After all tools complete, compile results:**
Combine ALL tool outputs into a single JSON object and store it:
set_output("scan_results", "<JSON string containing all 6 tool results: \
{ssl: {...}, headers: {...}, dns: {...}, ports: {...}, tech: {...}, subdomains: {...}}>")
Each tool returns a grade_input dict — preserve these as-is, the risk scorer needs them.
""",
tools=[
"ssl_tls_scan",
"http_headers_scan",
"dns_security_scan",
"port_scan",
"tech_stack_detect",
"subdomain_enumerate",
],
)
# Node 3: Risk Scoring
# Calculates weighted letter grades from scan results.
risk_scoring_node = NodeSpec(
id="risk-scoring",
name="Risk Scoring",
description=(
"Calculate weighted letter grades (A-F) per security category and overall "
"risk score from scan results"
),
node_type="event_loop",
max_node_visits=0,
input_keys=["scan_results"],
output_keys=["risk_report"],
system_prompt="""\
You calculate risk scores from scan results.
Given scan_results (a JSON string with ssl, headers, dns, ports, tech, subdomains \
sections), call the risk_score tool to produce letter grades.
**Step 1 — Extract scan results and call risk_score:**
The risk_score tool accepts JSON strings for each category. Extract the relevant \
sections from scan_results and pass them:
risk_score(
ssl_results="<JSON string of the ssl section from scan_results>",
headers_results="<JSON string of the headers section from scan_results>",
dns_results="<JSON string of the dns section from scan_results>",
ports_results="<JSON string of the ports section from scan_results>",
tech_results="<JSON string of the tech section from scan_results>",
subdomain_results="<JSON string of the subdomains section from scan_results>"
)
If a category has no results (tool failed), pass an empty string for that parameter.
**Step 2 — Store the risk report:**
set_output("risk_report", "<the complete JSON output from risk_score, including \
overall_score, overall_grade, categories, top_risks, and grade_scale>")
""",
tools=["risk_score"],
)
# Node 4: Findings Review (client-facing)
# Present risk grades and ask the user to continue or generate report.
findings_review_node = NodeSpec(
id="findings-review",
name="Findings Review",
description=(
"Present risk grades and security findings to the user, ask whether to "
"continue deeper scanning or generate the final report"
),
node_type="event_loop",
client_facing=True,
max_node_visits=0,
input_keys=["scan_results", "risk_report", "target_domain"],
output_keys=["continue_scanning", "feedback", "all_findings"],
system_prompt="""\
You present security scan findings and risk grades to the user and ask for their decision.
**STEP 1 — Present findings (text only, NO tool calls):**
Display the results in this format:
1. **Overall Risk Grade** — Show the letter grade prominently \
(e.g., "Overall Grade: C (68/100)")
2. **Category Breakdown** — Table showing each category's grade:
| Category | Grade | Score | Findings |
|----------|-------|-------|----------|
| SSL/TLS | B | 85 | 1 issue |
| HTTP Headers | D | 45 | 4 issues |
| DNS Security | C | 60 | 3 issues |
| Network Exposure | C | 70 | 1 issue |
| Technology | B | 75 | 2 issues |
| Attack Surface | B | 80 | 1 issue |
3. **Top Risks** — List the most critical findings from the risk report's top_risks field
4. **Grade Scale** — Show the grade scale so the user understands the scoring:
- A (90-100): Excellent security posture
- B (75-89): Good, minor improvements needed
- C (60-74): Fair, notable security gaps
- D (40-59): Poor, significant vulnerabilities
- F (0-39): Critical, immediate action required
5. **Options** — Ask: "Would you like me to:
- **Continue scanning** — I can focus on specific weak areas for a deeper look
- **Generate the report** — I'll compile a full HTML risk dashboard with all \
findings and remediation steps"
After your message, call ask_user() to wait for the user's response.
**STEP 2 — After the user responds, call set_output:**
If the user wants to continue:
- set_output("continue_scanning", "true")
- set_output("feedback", "What the user wants investigated further, or \
'focus on weakest categories'")
- set_output("all_findings", "Accumulated findings from all rounds so far as JSON string")
If the user wants to stop and get the report:
- set_output("continue_scanning", "false")
- set_output("feedback", "")
- set_output("all_findings", "All scan results and risk report combined as JSON string")
""",
tools=[],
)
# Node 5: Final Report (client-facing)
# Generates an HTML risk dashboard with color-coded grades.
final_report_node = NodeSpec(
id="final-report",
name="Risk Dashboard Report",
description=(
"Generate an HTML risk dashboard with color-coded grades, category breakdown, "
"detailed findings, and remediation steps"
),
node_type="event_loop",
client_facing=True,
max_node_visits=0,
input_keys=["all_findings", "risk_report", "target_domain"],
output_keys=["report_status"],
system_prompt="""\
Generate an HTML risk dashboard report and deliver it to the user.
**CRITICAL: You MUST build the file in multiple append_data calls. NEVER try to write the \
entire HTML in a single save_data call — it will exceed the output token limit and fail.**
**PROCESS (follow exactly):**
**Step 1 — Write HTML head + header + overall grade (save_data):**
Call save_data to create the file with the HTML head, full CSS, header, overall grade \
circle, and grade scale legend.
```
save_data(filename="risk_assessment_report.html", data="<!DOCTYPE html>\\n<html>...")
```
Include: DOCTYPE, head with ALL styles below, opening body, header with target domain \
and scan date, overall grade circle with score, and the grade scale legend table.
**CSS to use (copy exactly):**
```
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;background:#f5f7fa;\
color:#333;line-height:1.6}
header{background:linear-gradient(135deg,#1e3c72 0%,#2a5298 100%);color:white;\
padding:40px 20px;text-align:center}
header h1{font-size:2.5em;margin-bottom:10px}
header p{font-size:1.1em;opacity:0.9}
.container{max-width:1200px;margin:40px auto;padding:0 20px}
h2{color:#1e3c72;border-bottom:2px solid #2a5298;padding-bottom:10px;margin-top:30px}
h3{color:#2a5298;margin-top:20px}
.grade-display{text-align:center;margin:40px 0;background:white;padding:40px;\
border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,0.1)}
.grade-circle{width:120px;height:120px;border-radius:50%;display:flex;\
align-items:center;justify-content:center;margin:0 auto 20px;font-size:3em;\
font-weight:bold;color:white}
.grade-a{background:#27ae60} .grade-b{background:#3498db}
.grade-c{background:#f39c12} .grade-d{background:#e74c3c}
.grade-f{background:#c0392b}
.category-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));\
gap:20px;margin:40px 0}
.category-card{background:white;padding:25px;border-radius:10px;\
box-shadow:0 2px 10px rgba(0,0,0,0.1);border-left:5px solid #ccc}
.category-card.a{border-left-color:#27ae60} .category-card.b{border-left-color:#3498db}
.category-card.c{border-left-color:#f39c12} .category-card.d{border-left-color:#e74c3c}
.category-card.f{border-left-color:#c0392b}
.badge{display:inline-block;padding:4px 10px;border-radius:12px;color:white;\
font-weight:bold;font-size:0.85em}
.badge.high{background:#c0392b} .badge.medium{background:#f39c12}
.badge.low{background:#3498db} .badge.info{background:#95a5a6}
.finding{margin:20px 0;padding:20px;background:#f9f9f9;border-left:4px solid #ccc;\
border-radius:5px}
.finding.high{border-left-color:#c0392b} .finding.medium{border-left-color:#f39c12}
.finding.low{border-left-color:#3498db} .finding.info{border-left-color:#95a5a6}
.remediation{margin-top:15px;padding:15px;background:white;border-radius:5px;\
border-left:3px solid #27ae60}
.remediation h5{color:#27ae60;margin-bottom:10px}
pre{background:#2c3e50;color:#ecf0f1;padding:15px;border-radius:5px;overflow-x:auto;\
margin:10px 0;font-family:'Courier New',monospace;font-size:0.9em}
.card{background:white;border-radius:10px;padding:25px;margin:20px 0;\
box-shadow:0 2px 10px rgba(0,0,0,0.1)}
.footer{text-align:center;padding:30px 20px;color:#666;border-top:1px solid #ddd;\
margin-top:50px}
.grade-scale{background:white;padding:25px;border-radius:10px;margin:30px 0}
.grade-scale-item{padding:10px 0;border-bottom:1px solid #eee}
@media(max-width:768px){.category-grid{grid-template-columns:1fr}\
header h1{font-size:1.8em}.grade-circle{width:80px;height:80px;font-size:2em}}
```
**Grade circle HTML pattern:**
```
<div class="grade-display">
<div class="grade-circle grade-{letter}">{LETTER}</div>
<p style="font-size:1.8em;margin:20px 0">Overall Score: {score}/100</p>
<p style="color:#666">{one-line assessment}</p>
</div>
```
**Grade scale legend pattern:**
```
<div class="grade-scale">
<h3>Grade Scale</h3>
<div class="grade-scale-item"><strong>A (90-100):</strong> Excellent</div>
<div class="grade-scale-item"><strong>B (75-89):</strong> Good</div>
<div class="grade-scale-item"><strong>C (60-74):</strong> Fair</div>
<div class="grade-scale-item"><strong>D (40-59):</strong> Poor</div>
<div class="grade-scale-item"><strong>F (0-39):</strong> Critical</div>
</div>
```
End Step 1 after the grade scale closing div. Do NOT close body/html yet.
**Step 2 — Append category breakdown grid (append_data):**
```
append_data(filename="risk_assessment_report.html", data="<h2>Category Breakdown</h2>...")
```
Use this pattern for each of the 6 category cards:
```
<div class="category-card {letter}">
<h3>{Category Name}</h3>
<p><span class="badge {letter_class}">Grade: {LETTER} ({score})</span></p>
<p>{findings_count} findings</p>
<p style="color:#666;font-size:0.95em">{one-line summary}</p>
</div>
```
Wrap all 6 cards in `<div class="category-grid">...</div>`. Close the grid div.
**Step 3 — Append detailed findings PER CATEGORY (one append_data per category):**
For EACH of the 6 categories that has findings, call append_data separately:
```
append_data(filename="risk_assessment_report.html", data="<h3>{Category Name} (Grade: {LETTER})</h3>...")
```
Skip categories with 0 findings. For each finding, use this exact pattern:
```
<div class="finding {severity}">
<h4>{Title} <span class="badge {severity}">{SEVERITY}</span></h4>
<p><strong>Impact:</strong> {why it matters}</p>
<div class="remediation">
<h5>How to Fix</h5>
<p>{step-by-step instructions}</p>
<pre>{code example if relevant}</pre>
</div>
</div>
```
Where {severity} is one of: high, medium, low, info.
**Step 4 — Append footer section (append_data):**
```
append_data(filename="risk_assessment_report.html", data="<h2>Top Risks</h2>...")
```
Include:
- Top Risks: prioritized action items as a numbered list
- Methodology: "This assessment used passive, OSINT-based scanning..."
- Disclaimer in a card: "This is an automated passive assessment, not a comprehensive \
penetration test..."
- Close with `</div></body></html>`
**Step 5 — Serve the file:**
Call serve_file_to_user(filename="risk_assessment_report.html", open_in_browser=true)
Print the file_path from the result so the user can click it later.
**Step 6 — Present to user (text only, NO tool calls):**
Summarize: overall grade, weakest category, top 3 action items. \
After presenting, call ask_user() for follow-ups.
**Step 7 — After the user responds:**
- Answer any questions about findings or remediation
- Call ask_user() again if they have more questions
- When the user is satisfied: set_output("report_status", "completed")
**IMPORTANT:**
- Every finding MUST have remediation steps
- Write for developers, not security experts
- ALWAYS print the full file path so users can easily access the file later
- If an append_data call fails with a truncation error, break that chunk into smaller pieces
""",
tools=["save_data", "append_data", "serve_file_to_user"],
)
__all__ = [
"intake_node",
"passive_recon_node",
"risk_scoring_node",
"findings_review_node",
"final_report_node",
]