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:

NodeKey PropertiesDescription
Domainname, registrar, creation_date, expiration_date, WHOIS dataRoot domain with full WHOIS information
Subdomainname, has_dns_records, status, status_codes, http_live_url_count, ai_service_hintDiscovered 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.
IPaddress, version, is_cdn, cdn_name, asnResolved IP address with CDN/ASN metadata
Portnumber, protocol, stateOpen port on an IP
Servicename, product, version, banner, ai_runtime_versionRunning 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.
ExternalDomaindomain, sources, redirect_from_urls, redirect_to_urls, status_codes_seen, times_seenOut-of-scope domain encountered during recon (redirects, crawling, historical scans)

Web Application Nodes

Represent the application layer:

NodeKey PropertiesDescription
BaseURLurl (scheme://host:port), scheme, host, sourceOne per HTTP service. Identifies the service location; per-path response data lives on Endpoint.
Endpointpath, 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_guessDiscovered URL path with HTTP method + full response metadata. AI-surface annotations live here -- see Adversarial AI Recon.
Parametername, position (query/body/header/path), is_injectableInput parameter, flagged when vulnerable

Technology & Security Nodes

Represent detected software and security posture:

NodeKey PropertiesDescription
Technologyname, version, categories, confidence, detected_by, known_cve_countFramework, 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.
Headername, value, is_security_headerHTTP response header
Certificatesubject_cn, issuer, not_after, san, tls_versionTLS certificate details
DNSRecordtype (A/AAAA/MX/NS/TXT/SOA), value, ttlDNS record

Vulnerability & Exploitation Nodes

Represent security findings and successful attacks:

NodeKey PropertiesDescription
Vulnerabilityid, name, severity (lowercase), source (nuclei/gvm/security_check/nmap_nse/graphql/subdomain_takeover/vhost_sni/cache_poisoning), curl_commandScanner finding with evidence. Cache-poisoning findings add confidence_tier, cache_header, cache_impact, cache_buster, poc_link
CVEid, cvss, severity (uppercase), description, publishedKnown vulnerability from NVD
MitreDatacve_id, cwe_id, cwe_name, abstractionCWE weakness mapping
Capeccapec_id, name, likelihood, severity, execution_flowCommon attack pattern
ChainFindingfinding_type, severity, title, evidence, confidenceEvoGraph: agent discovery (replaces legacy Exploit node) — see EvoGraph

JS Reconnaissance Nodes

Represent analyzed JavaScript files and their findings (hierarchical structure):

NodeKey PropertiesDescription
JsReconFinding (js_file)id, finding_type='js_file', title, source_url, is_uploadedAnalyzed JS file -- parent node for all findings from that file
JsReconFinding (finding)id, finding_type, severity, confidence, title, evidenceIndividual 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_methodPhase 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_idExisting 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 JsReconFinding node with finding_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 with source='js_recon'. external_domain findings are the exception: they link directly to Domain since they have no single parent JS file. Phase 6 AI SDK findings ride on the existing JsReconFinding label with new finding_type values prefixed ai- — 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:

NodeKey PropertiesDescription
MultiscannerScanid, source, source_label, run_id, target, verification_enabled, scan_start_time, scan_end_time, duration_seconds, status, total_findings, validated_findings, assets_scannedScan metadata and statistics, one per project + source
MultiscannerRepositoryid, name, source, asset_kind, scan_idA scanned repository (sources: git, github, github deleted commits, gitlab)
MultiscannerImageid, name, source, asset_kind, scan_idA scanned container image, namespace/image:tag (source: docker)
MultiscannerModelid, name, source, asset_kind, scan_idA scanned model, space or dataset (source: huggingface)
MultiscannerBucketid, name, source, asset_kind, scan_idA scanned object-storage bucket (sources: s3, gcs)
MultiscannerEndpointid, name, source, asset_kind, scan_idA scanned service or scan root (sources: jenkins, elasticsearch, postman, circleci, travisci, filesystem)
MultiscannerFindingdetector_name, validation_status, source, asset, location, finding_kind, redacted, commit, line, link, timestamp, extra_dataOne 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:

NodeKey PropertiesDescription
Packagepurl, ecosystem, name, version, source, source_pathA 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}
MalPackageFindingfinding_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)
SbomDocumentid, nameThe 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 / GHSA half of an OSV verdict reuses the existing Vulnerability label with source='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:

SourceConnection 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:

  1. Attack surface mapping — queries the Domain → Subdomain → IP → Port → Service chain
  2. Technology-CVE correlation — traverses Technology → CVE relationships, prioritizing by CVSS score
  3. Injectable parameter discovery — queries Parameter nodes flagged as is_injectable: true
  4. Exploit feasibility assessment — cross-references ports, services, and CVEs to find matching Metasploit modules
  5. 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.

RelationshipFrom (EvoGraph)To (Recon Graph)Purpose
CHAIN_TARGETSAttackChainIP / Subdomain / Port / CVE / DomainAttack chain's target
STEP_TARGETEDChainStepIP / Subdomain / PortStep's target infrastructure
STEP_EXPLOITEDChainStepCVECVE this step attempted to exploit
STEP_IDENTIFIEDChainStepTechnologyTechnology identified during this step
FOUND_ONChainFindingIP / SubdomainWhere the finding was discovered
FINDING_RELATES_CVEChainFindingCVECVE related to the finding
CREDENTIAL_FORChainFindingService / PortService/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