addedOutput schema / $defs
Added value: +{
+ "AbuseIpdbInfo": {
+ "description": "AbuseIPDB reputation check (Pro tier only).",
+ "properties": {
+ "abuse_score": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "AbuseIPDB confidence-of-abuse score (0-100). Only present when status='ok'.",
+ "title": "Abuse Score"
+ },
+ "country": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "ISO 3166-1 alpha-2 country code from AbuseIPDB geolocation (may differ from RIPE).",
+ "title": "Country"
+ },
+ "is_tor": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "AbuseIPDB's Tor exit flag (cross-reference with top-level tor_exit field).",
+ "title": "Is Tor"
+ },
+ "isp": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "ISP name as reported by AbuseIPDB.",
+ "title": "Isp"
+ },
+ "reason": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Human-readable reason string. Present when status is skipped/rate_limited/error/pro_only.",
+ "title": "Reason"
+ },
+ "status": {
+ "description": "'ok' = data fetched; 'skipped' = API key not configured; 'rate_limited' = AbuseIPDB quota exceeded; 'error' = transient HTTP/network failure; 'pro_only' = returned on Free tier as upsell hint (see upgrade_url).",
+ "enum": [
+ "ok",
+ "skipped",
+ "rate_limited",
+ "error",
+ "pro_only"
+ ],
+ "title": "Status",
+ "type": "string"
+ },
+ "total_reports": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Number of reports submitted against this IP in the last 90 days.",
+ "title": "Total Reports"
+ },
+ "upgrade_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Upgrade link returned when status='pro_only'.",
+ "title": "Upgrade Url"
+ },
+ "usage_type": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "AbuseIPDB usage classification: 'Data Center/Web Hosting/Transit', 'ISP', 'Mobile ISP', etc.",
+ "title": "Usage Type"
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "title": "AbuseIpdbInfo",
+ "type": "object"
+ },
+ "ErrorDetail": {
+ "description": "Structured failure body. Codes mirror app/exceptions.AppException\nsubclasses; agent retry / upgrade decisions key off `code`, not `message`.",
+ "properties": {
+ "code": {
+ "description": "Stable machine-readable failure category. Agents key retry/upgrade decisions off this.",
+ "enum": [
+ "invalid_argument",
+ "not_found",
+ "rate_limit_exceeded",
+ "auth_required",
+ "tier_limit",
+ "upstream_timeout",
+ "upstream_error",
+ "internal_error"
+ ],
+ "title": "Code",
+ "type": "string"
+ },
+ "docs_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Documentation pointer (e.g. tool input contract) when code='invalid_argument'.",
+ "title": "Docs Url"
+ },
+ "message": {
+ "description": "Human-readable detail. Free text — never parse. Capped at 500 chars to prevent oversized upstream errors from bloating responses.",
+ "maxLength": 500,
+ "title": "Message",
+ "type": "string"
+ },
+ "retry_after_seconds": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "When code='rate_limit_exceeded', the minimum seconds to wait before retrying.",
+ "title": "Retry After Seconds"
+ },
+ "upgrade_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Pricing/upgrade URL when code='tier_limit' or 'rate_limit_exceeded' on the Free tier.",
+ "title": "Upgrade Url"
+ }
+ },
+ "required": [
+ "code",
+ "message"
+ ],
+ "title": "ErrorDetail",
+ "type": "object"
+ },
+ "ErrorResponse": {
+ "description": "MCP error envelope. Tool return type is always\n`SpecificResponse | ErrorResponse` — Union flag tells the agent which arm\narrived without parsing the inner body.",
+ "properties": {
+ "error": {
+ "$ref": "#/$defs/ErrorDetail"
+ }
+ },
+ "required": [
+ "error"
+ ],
+ "title": "ErrorResponse",
+ "type": "object"
+ },
+ "FireholInfo": {
+ "description": "FireHOL level1 blocklist check (Free tier and Pro).",
+ "properties": {
+ "listed": {
+ "default": false,
+ "description": "True if the IP matches any range in firehol_level1 (known-bad aggregated blocklist).",
+ "title": "Listed",
+ "type": "boolean"
+ },
+ "lists_matched": {
+ "description": "List identifiers matched. Currently ['firehol_level1'] when listed, else empty.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Lists Matched",
+ "type": "array"
+ },
+ "status": {
+ "description": "'ok' = trie lookup succeeded; 'skipped' = private/reserved/loopback/link-local IP, not meaningful to check; 'unavailable' = FireHOL feed could not be fetched (be honest with agent).",
+ "enum": [
+ "ok",
+ "skipped",
+ "unavailable"
+ ],
+ "title": "Status",
+ "type": "string"
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "title": "FireholInfo",
+ "type": "object"
+ },
+ "IpLookupResponse": {
+ "properties": {
+ "asn": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Autonomous System Number from RIPE Stat network-info (e.g. 13335 for Cloudflare).",
+ "title": "Asn"
+ },
+ "asn_name": {
+ "anyOf": [
+ {
+ "maxLength": 256,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Human-readable AS name from RIPE Stat as-overview (e.g. 'CLOUDFLARENET').",
+ "title": "Asn Name"
+ },
+ "cloud_provider": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Cloud provider name resolved via two-tier detection: (1) published cloud CIDR ranges (AWS/GCP/Cloudflare), (2) ASN-to-provider map fallback for anycast/public-service IPs outside published ranges (e.g. 8.8.8.8 → AS15169 → 'Google'). Null when neither matches. Always present in response (route emits null-explicit so agents can disambiguate 'not detected' from 'field absent').",
+ "title": "Cloud Provider"
+ },
+ "country": {
+ "anyOf": [
+ {
+ "maxLength": 8,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "ISO 3166-1 alpha-2 country code from RIPE Stat rir-stats-country (RIR-allocated).",
+ "title": "Country"
+ },
+ "cpes": {
+ "description": "CPE 2.3 strings for services detected on this IP per Shodan InternetDB.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Cpes",
+ "type": "array"
+ },
+ "hostnames": {
+ "description": "Hostnames observed pointing to this IP per Shodan InternetDB.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Hostnames",
+ "type": "array"
+ },
+ "ip": {
+ "description": "Queried IP address (IPv4 or IPv6, echoed back verbatim).",
+ "title": "Ip",
+ "type": "string"
+ },
+ "is_datacenter": {
+ "default": false,
+ "description": "True if IP is hosted on a known datacenter / cloud provider. Detection: (1) cloud_provider populated (CIDR or ASN map hit covering AWS/GCP/Cloudflare/DigitalOcean/Hetzner/OVH/Linode/Vultr/Microsoft Azure), (2) ASN in tier-1 datacenter set (adds Oracle/Alibaba/Tencent on top of the cloud_provider map). Use for Nuclei matchers + bug-bounty triage where datacenter targets warrant different scan policy than residential IPs. Always present — never null.",
+ "title": "Is Datacenter",
+ "type": "boolean"
+ },
+ "next_calls": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/$defs/PivotHint"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Suggested follow-up MCP tool calls. Ordered by relevance; agents should chain these without re-prompting the user.",
+ "title": "Next Calls"
+ },
+ "ports": {
+ "description": "Open ports observed by Shodan InternetDB (free, no API key; superseded by reputation.shodan.ports on Pro).",
+ "items": {
+ "type": "integer"
+ },
+ "title": "Ports",
+ "type": "array"
+ },
+ "ptr": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Reverse-DNS PTR record. Null when no PTR is published.",
+ "title": "Ptr"
+ },
+ "reputation": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ReputationInfo"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Multi-source reputation. Free tier: firehol populated, abuseipdb/shodan return status='pro_only' upsell stubs. Pro tier: all three live."
+ },
+ "risk_score": {
+ "default": 0,
+ "description": "Composite 0-100 risk score (v1.17.0 formula). Additive components: ports (10 * min(count, 5) = 0-50), tor_exit (+30), firehol.listed (+20), AbuseIPDB confidence (round(15 * score / 100) = 0-15), is_datacenter (+10), known vulns (5 * min(count, 4) = 0-20). Datacenter membership now adds risk (was a -10 trust bonus pre-1.17). Use severity_label for thresholding.",
+ "title": "Risk Score",
+ "type": "integer"
+ },
+ "severity_label": {
+ "default": "low",
+ "description": "Coarse risk band derived from risk_score (>=75 critical, >=50 high, >=25 medium, else low). Use this for Nuclei matchers and MCP agent triage when you don't want to re-implement the threshold logic; risk_score is the canonical numeric source.",
+ "enum": [
+ "low",
+ "medium",
+ "high",
+ "critical"
+ ],
+ "title": "Severity Label",
+ "type": "string"
+ },
+ "summary": {
+ "default": "",
+ "description": "One-line human-readable summary built from IP, PTR, ASN, country, ports, vulns.",
+ "title": "Summary",
+ "type": "string"
+ },
+ "tags": {
+ "description": "Shodan InternetDB classification tags (e.g. 'cdn', 'cloud', 'vpn', 'tor', 'self-signed').",
+ "items": {
+ "type": "string"
+ },
+ "title": "Tags",
+ "type": "array"
+ },
+ "tor_exit": {
+ "default": false,
+ "description": "True if IP appears in the Tor Project's exit node list. False when not listed or when the upstream list fetch failed (check verdict.sources_unavailable for 'tor' to distinguish). Always present in response — never null.",
+ "title": "Tor Exit",
+ "type": "boolean"
+ },
+ "verdict": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/Verdict"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Falsifiability metadata: sources_queried, sources_unavailable, completeness, deterministic flag. Lets agents distinguish 'no data' from 'source failed' without re-running the call."
+ },
+ "vulns": {
+ "description": "CVEs Shodan InternetDB has associated with banners on this IP, enriched with severity + cvss_v3 from local cve.db (Phase 2 IP enrichment, v1.16.0 BREAKING). Pre-1.16 this was a flat list[str] of CVE IDs. Order is preserved from Shodan (meaningful — Shodan ranks confidence). Unknown CVEs emit severity='UNKNOWN'; do NOT infer 'benign' from UNKNOWN.",
+ "items": {
+ "$ref": "#/$defs/VulnInfo"
+ },
+ "title": "Vulns",
+ "type": "array"
+ }
+ },
+ "required": [
+ "ip"
+ ],
+ "title": "IpLookupResponse",
+ "type": "object"
+ },
+ "PivotHint": {
+ "additionalProperties": true,
+ "description": "A suggested follow-up MCP tool call. Surfaced inside response.next_calls so\nLLM agents can chain related lookups without manual prompting. Each hint names\nthe tool, the input value to pass, and a short reason explaining why this\npivot adds value in the current context.",
+ "properties": {
+ "input": {
+ "description": "Suggested input value to pass to the tool — typically a CVE ID, CWE ID, domain, or IP. Pre-populated from the current response so the agent can call the next tool without re-deriving the argument.",
+ "title": "Input",
+ "type": "string"
+ },
+ "params": {
+ "anyOf": [
+ {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Optional extra kwargs to pass alongside `input`. Used by pivot generators when the next call benefits from a secondary parameter, e.g. {'exclude_id': 'AML.T0051'} to skip the originating technique from a sibling-tactic search. Omitted when no extra args are needed.",
+ "title": "Params"
+ },
+ "reason": {
+ "description": "Short rationale (one sentence) for why this follow-up call adds value, e.g. 'Federal patch deadline + ransomware association', 'Public exploits / PoC availability'.",
+ "title": "Reason",
+ "type": "string"
+ },
+ "tool": {
+ "description": "Canonical MCP tool name to call next. Constrained to known operation_ids in tools/list — adding a new tool here requires expanding the Literal.",
+ "enum": [
+ "cve_lookup",
+ "cve_search",
+ "cve_leading",
+ "bulk_cve_lookup",
+ "exploit_lookup",
+ "kev_detail",
+ "cwe_lookup",
+ "subdomain_enum",
+ "ssl_check",
+ "tech_fingerprint",
+ "asn_lookup",
+ "ip_lookup",
+ "ioc_lookup",
+ "bulk_ioc_lookup",
+ "hash_lookup",
+ "threat_intel",
+ "threat_report",
+ "audit_domain",
+ "domain_report",
+ "dns_lookup",
+ "whois_lookup",
+ "wayback_lookup",
+ "scan_headers",
+ "check_headers",
+ "check_secrets",
+ "check_injection",
+ "check_dependencies",
+ "email_mx",
+ "email_disposable",
+ "phone_lookup",
+ "username_lookup",
+ "password_check",
+ "phishing_check",
+ "atlas_technique_lookup",
+ "atlas_technique_search",
+ "bulk_atlas_technique_lookup",
+ "atlas_case_study_lookup",
+ "atlas_case_study_search",
+ "d3fend_defense_lookup",
+ "d3fend_defense_search",
+ "d3fend_defense_for_attack",
+ "d3fend_attack_coverage"
+ ],
+ "title": "Tool",
+ "type": "string"
+ }
+ },
+ "required": [
+ "tool",
+ "input",
+ "reason"
+ ],
+ "title": "PivotHint",
+ "type": "object"
+ },
+ "ReputationInfo": {
+ "description": "Multi-source IP reputation. Sources present depend on tier (Free: firehol only; Pro: all three).",
+ "properties": {
+ "abuseipdb": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/AbuseIpdbInfo"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "AbuseIPDB abuse confidence. Pro tier only — omitted from the response on Free."
+ },
+ "firehol": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/FireholInfo"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "FireHOL level1 blocklist membership. Available on Free tier."
+ },
+ "shodan": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ShodanRepInfo"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Shodan full API enrichment. Pro tier only — omitted from the response on Free."
+ },
+ "upgrade": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ReputationUpgradeHint"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Free-tier-only pointer to the Pro-only sources that were skipped."
+ }
+ },
+ "title": "ReputationInfo",
+ "type": "object"
+ },
+ "ReputationUpgradeHint": {
+ "description": "Compact pointer that replaces the verbose pro_only sub-stubs for Free tier.\n\nBug I4: previously the abuseipdb/shodan slots carried full Pydantic models\nwith every field null + a status='pro_only' marker — ~150 tokens of pure\nnegative space per Free-tier ip_lookup response. The verdict block already\nlists those sources in sources_unavailable on Free; this hint just points\ncallers at the upgrade page in one line.",
+ "properties": {
+ "pro_only_sources": {
+ "items": {
+ "type": "string"
+ },
+ "title": "Pro Only Sources",
+ "type": "array"
+ },
+ "reason": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Reason"
+ },
+ "upgrade_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Upgrade Url"
+ }
+ },
+ "title": "ReputationUpgradeHint",
+ "type": "object"
+ },
+ "ShodanRepInfo": {
+ "description": "Shodan full API enrichment (Pro tier only). Richer than InternetDB fields at top level.",
+ "properties": {
+ "asn": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "ASN string per Shodan (e.g. 'AS13335'); may differ from top-level asn int.",
+ "title": "Asn"
+ },
+ "city": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "City name per Shodan geolocation.",
+ "title": "City"
+ },
+ "country_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Country name per Shodan geolocation.",
+ "title": "Country Name"
+ },
+ "hostnames": {
+ "description": "Hostnames observed pointing to this IP per Shodan.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Hostnames",
+ "type": "array"
+ },
+ "isp": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "ISP per Shodan (may differ from AbuseIPDB/RIPE).",
+ "title": "Isp"
+ },
+ "last_update": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "ISO 8601 timestamp of Shodan's most recent data point for this IP.",
+ "title": "Last Update"
+ },
+ "org": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Organization name owning the IP per Shodan.",
+ "title": "Org"
+ },
+ "os": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Shodan-detected operating system (fingerprint-based, best-effort).",
+ "title": "Os"
+ },
+ "ports": {
+ "description": "Open ports observed by Shodan full scan (superset of top-level InternetDB ports).",
+ "items": {
+ "type": "integer"
+ },
+ "title": "Ports",
+ "type": "array"
+ },
+ "reason": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Human-readable reason. Present when status is skipped/restricted/rate_limited/error/pro_only.",
+ "title": "Reason"
+ },
+ "status": {
+ "description": "'ok' = data fetched; 'skipped' = API key not configured; 'restricted' = 403 (IP not available on free Shodan tier); 'rate_limited' = 429 quota exceeded; 'error' = transient HTTP/network failure; 'pro_only' = returned on Free tier as upsell hint.",
+ "enum": [
+ "ok",
+ "skipped",
+ "restricted",
+ "rate_limited",
+ "error",
+ "pro_only"
+ ],
+ "title": "Status",
+ "type": "string"
+ },
+ "upgrade_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Upgrade link returned when status='pro_only'.",
+ "title": "Upgrade Url"
+ },
+ "vulns": {
+ "description": "CVE IDs Shodan has associated with banners on this IP.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Vulns",
+ "type": "array"
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "title": "ShodanRepInfo",
+ "type": "object"
+ },
+ "Verdict": {
+ "properties": {
+ "completeness": {
+ "default": "complete",
+ "description": "'complete' = every planned source returned data; 'partial' = at least one source in sources_unavailable failed or was skipped; 'minimal' = only the primary/required source returned, optional enrichment missing.",
+ "enum": [
+ "complete",
+ "partial",
+ "minimal"
+ ],
+ "title": "Completeness",
+ "type": "string"
+ },
+ "data_age_seconds": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Seconds elapsed since the oldest cached source was fetched, or null when every source was queried live for this request. Use to judge freshness.",
+ "title": "Data Age Seconds"
+ },
+ "deterministic": {
+ "description": "True when the response is fully reproducible from the listed sources for the same input at the same moment (no randomness, no model inference). False for endpoints that include probabilistic scoring or LLM output.",
+ "title": "Deterministic",
+ "type": "boolean"
+ },
+ "falsifiable_fields": {
+ "description": "Top-level response fields whose values a caller can independently re-derive from the named upstream sources (e.g. 'dns', 'ssl', 'whois'). Fields not in this list are derived/computed and cannot be directly re-verified.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Falsifiable Fields",
+ "type": "array"
+ },
+ "sources_queried": {
+ "description": "Canonical source identifiers successfully consulted for this response (e.g. 'ripe_stat', 'shodan_internetdb', 'firehol'). Agent-readable list, order not significant.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Sources Queried",
+ "type": "array"
+ },
+ "sources_unavailable": {
+ "description": "Sources that were expected but not returned — either intentionally skipped (lite mode, tier gating) or failed (quota, timeout, upstream down). Empty list means every planned source produced data.",
+ "items": {
+ "type": "string"
+ },
+ "title": "Sources Unavailable",
+ "type": "array"
+ }
+ },
+ "required": [
+ "deterministic"
+ ],
+ "title": "Verdict",
+ "type": "object"
+ },
+ "VulnInfo": {
+ "description": "Severity-enriched CVE entry attached to /v1/ip and /v1/threat_report.\n\nPhase 2 IP enrichment (v1.16.0 BREAKING): Shodan InternetDB returns a flat\nlist of CVE IDs with no severity context, forcing agents to fan out\ncve_lookup calls for triage. We resolve severity + cvss_v3 against the\nlocal cve.db in a single SQL batch so the agent can prioritise without\nextra round-trips. Unknown CVEs are emitted with severity='UNKNOWN' /\ncvss_v3=null so the ID is preserved (the agent must not infer 'benign'\nfrom the absence of a row).",
+ "properties": {
+ "cve_id": {
+ "description": "CVE identifier (e.g. 'CVE-2021-44228').",
+ "title": "Cve Id",
+ "type": "string"
+ },
+ "cvss_v3": {
+ "anyOf": [
+ {
+ "maximum": 10,
+ "minimum": 0,
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "CVSS v3 base score (0.0-10.0). Null when severity='UNKNOWN' or NVD has no v3 score.",
+ "title": "Cvss V3"
+ },
+ "severity": {
+ "description": "NVD CVSS v3 severity bucket from local cve.db. 'UNKNOWN' when the CVE is not in our database (NVD may not have classified it yet, or the ID is reserved). Treat UNKNOWN as 'do not assume benign — call cve_lookup for fresh upstream data.'",
+ "enum": [
+ "CRITICAL",
+ "HIGH",
+ "MEDIUM",
+ "LOW",
+ "UNKNOWN"
+ ],
+ "title": "Severity",
+ "type": "string"
+ }
+ },
+ "required": [
+ "cve_id",
+ "severity"
+ ],
+ "title": "VulnInfo",
+ "type": "object"
+ }
+}