Attack Surface Graph
RedAmon uses a Neo4j graph database as the single source of truth for every finding. The graph stores the complete topology of the target's attack surface as an interconnected knowledge graph, enabling both visual exploration in the webapp and intelligent querying by the AI agent.
Node Types
The graph contains 25 node types organized into eight categories.
Infrastructure Nodes
Represent the network topology:
| Node | Key Properties | Description |
|---|---|---|
| Domain | name, registrar, creation_date, expiration_date, WHOIS data | Root domain with full WHOIS information |
| Subdomain | name, has_dns_records, status, status_codes, http_live_url_count, ai_service_hint | Discovered hostname with HTTP liveness (status: "resolved", "no_http", or HTTP code like "200", "404"). ai_service_hint is set when TXT/NS records reveal an AI provider tenancy -- see Adversarial AI Recon. |
| IP | address, version, is_cdn, cdn_name, asn | Resolved IP address with CDN/ASN metadata |
| Port | number, protocol, state | Open port on an IP |
| Service | name, product, version, banner, ai_runtime_version | Running service with version info. ai_runtime_version is set when Nmap's -sV product/version matches a known AI runtime (ollama, vllm, litellm, tgi, triton, llama.cpp) -- see Adversarial AI Recon. |
| ExternalDomain | domain, sources, redirect_from_urls, redirect_to_urls, status_codes_seen, times_seen | Out-of-scope domain encountered during recon (redirects, crawling, historical scans) |
Web Application Nodes
Represent the application layer:
| Node | Key Properties | Description |
|---|---|---|
| BaseURL | url (scheme://host:port), scheme, host, source | One per HTTP service. Identifies the service location; per-path response data lives on Endpoint. |
| Endpoint | path, method, baseurl, status_code, title, server, response_time_ms, resolved_ip, has_parameters, is_form, source, is_ai_framework_detected, ai_framework_name, ai_frontend_product_guess | Discovered URL path with HTTP method + full response metadata. AI-surface annotations live here -- see Adversarial AI Recon. |
| Parameter | name, position (query/body/header/path), is_injectable | Input parameter, flagged when vulnerable |
Technology & Security Nodes
Represent detected software and security posture:
| Node | Key Properties | Description |
|---|---|---|
| Technology | name, version, categories, confidence, detected_by, known_cve_count | Framework, library, or server. category values starting with ai- (ai-runtime, ai-vector-db, ai-framework, ai-proxy, ai-frontend, ai-sdk-client) flag AI-surface technologies; detected_by values starting with httpx-ai- or *-ai-port identify the AI signal channel -- see Adversarial AI Recon. |
| Header | name, value, is_security_header | HTTP response header |
| Certificate | subject_cn, issuer, not_after, san, tls_version | TLS certificate details |
| DNSRecord | type (A/AAAA/MX/NS/TXT/SOA), value, ttl | DNS record |
Vulnerability & Exploitation Nodes
Represent security findings and successful attacks:
| Node | Key Properties | Description |
|---|---|---|
| Vulnerability | id, name, severity (lowercase), source (nuclei/gvm/security_check/nmap_nse/graphql/subdomain_takeover/vhost_sni/cache_poisoning), curl_command | Scanner finding with evidence. Cache-poisoning findings add confidence_tier, cache_header, cache_impact, cache_buster, poc_link |
| CVE | id, cvss, severity (uppercase), description, published | Known vulnerability from NVD |
| MitreData | cve_id, cwe_id, cwe_name, abstraction | CWE weakness mapping |
| Capec | capec_id, name, likelihood, severity, execution_flow | Common attack pattern |
| ChainFinding | finding_type, severity, title, evidence, confidence | EvoGraph: agent discovery (replaces legacy Exploit node) — see EvoGraph |
JS Reconnaissance Nodes
Represent analyzed JavaScript files and their findings (hierarchical structure):
| Node | Key Properties | Description |
|---|---|---|
| JsReconFinding (js_file) | id, finding_type='js_file', title, source_url, is_uploaded | Analyzed JS file -- parent node for all findings from that file |
| JsReconFinding (finding) | id, finding_type, severity, confidence, title, evidence | Individual finding. finding_type ∈ {dependency_confusion, source_map_exposure, dom_sink, framework, dev_comment, email, internal_ip, object_reference, cloud_asset, external_domain}. Type-specific extras: cloud_asset -> cloud_provider/cloud_asset_type; external_domain -> times_seen/sample_urls; object_reference -> potential_idor (UUID v4 heuristic, not actual IDOR detection) |
| JsReconFinding (ai-sdk-*) | id, finding_type, severity, confidence, sdk_name, ai_provider, sample, byte_offset, detection_method | Phase 6 — Adversarial AI. finding_type ∈ {ai-sdk-client, ai-sdk-key-literal, ai-sdk-browser-allowed, ai-frontend-detected, ai-provider-url}. AI/LLM SDK signals discovered inside JS bundles by match_ai_sdk(). sdk_name is the canonical vendor product name (OpenAI, Anthropic, LangChain Core, Pinecone, Open WebUI, etc.). sample is a redacted form of the captured key (first 6 + "..." + last 4 — never the full secret). byte_offset makes the node id stable across re-scans for idempotent MERGE. See Adversarial AI Recon → JS Recon AI SDK Detection. |
| Secret (AI-enriched) | source_url, matched_text, validation_status, ai_provider, ai_finding_id | Existing Secret node gets two extra properties when a Phase 6 ai-sdk-key-literal finding's captured value overlaps. ai_provider carries the AI vendor name (e.g. "OpenAI SDK constructor", "Langfuse Secret Key"). ai_finding_id is the foreign key into the matching JsReconFinding for full provenance. Enrichment is gated on the Secret's matched_text starting with a known AI-key prefix (sk-, hf_, lsv2_, gsk_, r8_, pcsk_, pplx-, xai-, csk-, tgp_, pa-, AIzaSy, co_, rpa_, pk-lf-, fw_) so Stripe/Slack/AWS literals are never wrongly enriched. Existing "all leaked secrets" queries gain AI context automatically: MATCH (s:Secret) WHERE s.ai_provider IS NOT NULL. |
Each JS file becomes a
JsReconFindingnode withfinding_type='js_file'. All findings, secrets, and endpoints from that file are linked to it -- not directly to Domain/BaseURL. Secrets and endpoints reuse the existing Secret and Endpoint node types withsource='js_recon'.external_domainfindings are the exception: they link directly toDomainsince they have no single parent JS file. Phase 6 AI SDK findings ride on the existingJsReconFindinglabel with newfinding_typevalues prefixedai-— no new node labels, no parallel taxonomy.
Secret Multiscanner Nodes
Represent findings from the Secret Multiscanner. One scan node per project + source, so a Docker scan and a GitHub scan of the same project are two independent subgraphs:
| Node | Key Properties | Description |
|---|---|---|
| MultiscannerScan | id, source, source_label, run_id, target, verification_enabled, scan_start_time, scan_end_time, duration_seconds, status, total_findings, validated_findings, assets_scanned | Scan metadata and statistics, one per project + source |
| MultiscannerRepository | id, name, source, asset_kind, scan_id | A scanned repository (sources: git, github, github deleted commits, gitlab) |
| MultiscannerImage | id, name, source, asset_kind, scan_id | A scanned container image, namespace/image:tag (source: docker) |
| MultiscannerModel | id, name, source, asset_kind, scan_id | A scanned model, space or dataset (source: huggingface) |
| MultiscannerBucket | id, name, source, asset_kind, scan_id | A scanned object-storage bucket (sources: s3, gcs) |
| MultiscannerEndpoint | id, name, source, asset_kind, scan_id | A scanned service or scan root (sources: jenkins, elasticsearch, postman, circleci, travisci, filesystem) |
| MultiscannerFinding | detector_name, validation_status, source, asset, location, finding_kind, redacted, commit, line, link, timestamp, extra_data | One deduplicated secret. validation_status ∈ {validated (confirmed live), unvalidated (API rejected it), verify_error (the verify call failed), unverified (verification was off)}. finding_kind='image_history' marks a secret baked into a Dockerfile RUN/ENV directive, whose path exists in no filesystem |
Asset nodes are grouped by shape, not one label per source, so 14 sources produce 5 asset labels instead of 14. Findings are deduplicated on source + asset + location + line + detector, so the same secret found by two sources stays two findings: the second source's context is a separate fact.
Supply-Chain Nodes
Represent dependencies discovered by Supply-Chain Scanning and their malicious/suspicious verdicts. Shared by all three layers (L1 SBOM/repo audit, L2 live-target harvest, L3 agent tools), so the same package deduplicates onto one node no matter how it was found:
| Node | Key Properties | Description |
|---|---|---|
| Package | purl, ecosystem, name, version, source, source_path | A discovered dependency. Merge key (purl, user_id, project_id). version is nullable (a black-box L2 sighting may not know it, which makes the package unverdictable). source ∈ {sbom, lockfile, dir, sourcemap, retirejs, import, wappalyzer, osv, finding} |
| MalPackageFinding | finding_id, verdict, source_tool, advisory_id, severity, confidence, title, soft_error, aliases, incident_* | A verdict about a package. verdict ∈ {malicious (OSV MAL-), suspicious (GuardDog / typosquat)}; source_tool ∈ {osv, guarddog, typosquat}. soft_error=true means the behavioural pass produced no verdict (rendered as not analysed, not a clean result). The seven incident_* properties carry the supply-chain incident write-up when the offline catalog matched; all NULL means "not in the catalog or never synced", never "safe". Merge key (finding_id, user_id, project_id) |
| SbomDocument | id, name | The parent of every package read out of an uploaded SBOM / lockfile (id: sbom-<user>-<project>-<filename>). A GitHub-repo L1 scan uses GithubRepository as the anchor instead |
The
CVE/GHSAhalf of an OSV verdict reuses the existing Vulnerability label withsource='osv'(not a new node type), and a repository L1 scan anchors to the existing GithubRepository node shared with GitHub Secret Hunt. So only three labels are genuinely new.
Relationship Chain
The graph connects nodes through directed relationships that mirror real infrastructure:
Domain ──HAS_SUBDOMAIN──> Subdomain
Domain ──HAS_EXTERNAL_DOMAIN──> ExternalDomain
Subdomain ──RESOLVES_TO──> IP
IP ──HAS_PORT──> Port
Port ──RUNS_SERVICE──> Service
Port ──SERVES_URL──> BaseURL
Service ──POWERED_BY──> BaseURL
BaseURL ──HAS_ENDPOINT──> Endpoint
Endpoint ──USES_TECHNOLOGY──> Technology [http_probe — incl. AI signatures]
Endpoint ──HAS_HEADER──> Header
Endpoint ──HAS_CERTIFICATE──> Certificate
Endpoint ──HAS_PARAMETER──> Parameter
Technology ──HAS_KNOWN_CVE──> CVE
CVE ──HAS_CWE──> MitreData
MitreData ──HAS_CAPEC──> Capec
Vulnerability ──FOUND_AT──> Endpoint
Vulnerability ──AFFECTS_PARAMETER──> Parameter
Vulnerability ──HAS_CVE──> CVE
IP/Subdomain/Domain ──HAS_VULNERABILITY──> Vulnerability
Nmap enrichment (service detection + NSE scripts):
Service ──USES_TECHNOLOGY──> Technology
Port ──HAS_TECHNOLOGY──> Technology
Vulnerability ──AFFECTS──> Port
Vulnerability ──FOUND_ON──> Technology
JS Reconnaissance (hierarchical: parent -> file -> findings):
BaseURL ──HAS_JS_FILE──> JsReconFinding(js_file) [pipeline-crawled JS]
Domain ──HAS_JS_FILE──> JsReconFinding(js_file) [uploaded JS files]
JsReconFinding(js_file) ──HAS_JS_FINDING──> JsReconFinding
JsReconFinding(js_file) ──HAS_SECRET──> Secret [source='js_recon']
JsReconFinding(js_file) ──HAS_ENDPOINT──> Endpoint [source='js_recon']
Domain ──HAS_JS_FINDING──> JsReconFinding(external_domain) [no single parent JS file]
Secret Multiscanner (one scan per project + source):
Domain ──HAS_MULTISCANNER_SCAN──> MultiscannerScan
MultiscannerScan ──HAS_ASSET──> MultiscannerRepository | MultiscannerImage | MultiscannerModel | MultiscannerBucket | MultiscannerEndpoint
<asset node> ──HAS_FINDING──> MultiscannerFinding
Supply-Chain scanning (L1 SBOM/repo audit + L2 live-target harvest):
Domain ──HAS_SBOM_DOCUMENT──> SbomDocument [L1 upload anchor, when a Domain exists]
Domain ──HAS_REPOSITORY──> GithubRepository [L1 repo anchor; GitHub Hunt reaches the same node via GithubHunt]
SbomDocument ──DEPENDS_ON──> Package [L1 uploaded SBOM/lockfile]
GithubRepository ──DEPENDS_ON──> Package [L1 cloned repo]
BaseURL ──DEPENDS_ON──> Package [L2 live target serves this dependency]
Package ──FLAGGED_AS──> MalPackageFinding [malicious (OSV MAL-) / suspicious (GuardDog, typosquat)]
Package ──HAS_VULNERABILITY──> Vulnerability [source='osv'; the CVE/GHSA half]
BaseURL ──CONTACTS_MALICIOUS_HOST──> ThreatPulse [target contacted a host a published incident names]
EvoGraph bridges (attack chain → recon graph):
AttackChain ─ ─CHAIN_TARGETS─ ─> IP / Subdomain
ChainStep ─ ─STEP_TARGETED─ ─> IP / Port
ChainStep ─ ─STEP_EXPLOITED─ ─> CVE
ChainFinding ─ ─FOUND_ON─ ─> IP / Subdomain
ChainFinding ─ ─FINDING_RELATES_CVE─ ─> CVE
Vulnerability Source Differences
Vulnerabilities connect differently depending on their source:
| Source | Connection Pattern |
|---|---|
| Nuclei (web application) | Linked via FOUND_AT to the Endpoint and AFFECTS_PARAMETER to the vulnerable Parameter |
| GVM (network level) | Linked via HAS_VULNERABILITY directly to IP and Subdomain nodes |
| Nmap NSE (service level) | Linked via AFFECTS to Port, FOUND_ON to Technology, and HAS_CVE to CVE |
| Security checks (DNS/email/headers) | Linked via HAS_VULNERABILITY to Subdomain or Domain |
Web cache poisoning (cache_poisoning) | Linked via HAS_VULNERABILITY to the Endpoint and its BaseURL (both MERGEd on the fly so the finding always lands in a connected subgraph). See Web Cache Poisoning |
Multi-Tenant Design
Every node includes user_id and project_id properties. All queries are automatically scoped to the current user and project — the AI agent never generates tenant filters itself, preventing accidental cross-project data access.
Query Pattern
MATCH (d:Domain {user_id: $userId, project_id: $projectId})
-[:HAS_SUBDOMAIN]->(s:Subdomain)
-[:RESOLVES_TO]->(ip:IP)
-[:HAS_PORT]->(p:Port)
RETURN d, s, ip, p
How the AI Agent Uses the Graph
Before taking any offensive action, the agent queries the graph to build situational awareness:
- Attack surface mapping — queries the Domain → Subdomain → IP → Port → Service chain
- Technology-CVE correlation — traverses Technology → CVE relationships, prioritizing by CVSS score
- Injectable parameter discovery — queries Parameter nodes flagged as
is_injectable: true - Exploit feasibility assessment — cross-references ports, services, and CVEs to find matching Metasploit modules
- Post-exploitation context — after exploiting, creates a
ChainFinding(exploit_success)in the EvoGraph, bridged to the target IP and CVE
The text-to-Cypher system includes 25+ example query patterns and automatically retries with error context on failure (up to 3 attempts).
Graph Visualization
The graph is visualized on the Red Zone:
- 2D mode — force-directed layout with pan, zoom, and node selection
- 3D mode — WebGL rendering with rotation for large graphs
- Render off — a persistent per-project switch that stops the graph being fetched or drawn at all, for projects large enough to slow the browser down; the data stays readable in the table views
- Color coding — each node type has a distinct color
- Filtering — use the bottom bar to show/hide specific node types
- Node drawer — click any node to see all properties
EvoGraph Bridge
The recon graph is connected to RedAmon's EvoGraph (Evolutive Attack Chain Graph) through bridge relationships. EvoGraph tracks everything the AI agent does during exploitation sessions — every tool execution, finding, decision, and failure — and bridges back to the recon graph nodes they relate to.
| Relationship | From (EvoGraph) | To (Recon Graph) | Purpose |
|---|---|---|---|
CHAIN_TARGETS | AttackChain | IP / Subdomain / Port / CVE / Domain | Attack chain's target |
STEP_TARGETED | ChainStep | IP / Subdomain / Port | Step's target infrastructure |
STEP_EXPLOITED | ChainStep | CVE | CVE this step attempted to exploit |
STEP_IDENTIFIED | ChainStep | Technology | Technology identified during this step |
FOUND_ON | ChainFinding | IP / Subdomain | Where the finding was discovered |
FINDING_RELATES_CVE | ChainFinding | CVE | CVE related to the finding |
CREDENTIAL_FOR | ChainFinding | Service / Port | Service/port the credential works on |
This means you can traverse from any recon graph node to see all attack chain activity that targeted it — enabling cross-session intelligence queries like "what has been tried against this IP?" or "which CVEs have been successfully exploited?".
Full details: See EvoGraph — Attack Chain Evolution for the complete attack chain graph schema, node types, and cross-session learning.
Next Steps
- Red Zone — learn how to explore the graph visually
- AI Agent Guide — see how the agent queries the graph
- EvoGraph — Attack Chain Evolution — understand the attack chain graph that bridges to the recon graph
- Scan Timeline — snapshot this graph as a version, activate past versions, and diff two scans (Recon Delta)
- Data Export & Import — export graph data