changedOutput schema / oneOf
Previous value: -[
- {
- "additionalProperties": false,
- "properties": {
- "coverage": {
- "anyOf": [
- {
- "additionalProperties": false,
- "properties": {
- "answered": {
- "description": "The radars that answered. On a failure their answers are withheld.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "failed": {
- "description": "The radars that could not be read.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "radars": {
- "description": "The radars this tool reads.",
- "items": {
- "type": "string"
- },
- "type": "array"
- }
- },
- "required": [
- "radars",
- "answered",
- "failed"
- ],
- "type": "object"
- },
- {
- "type": "null"
- }
- ],
- "description": "What the answer covers; null where the answer says nothing about it."
- },
- "data": {
- "additionalProperties": false,
- "description": "A field this version cannot label is left out of data and named in the note. Each of the kev_exposure, exposed_databases and leaked_credentials radars also carries generated_at, when the API computed its totals, and, when the API can tell, last_run_at. kev_exposure.last_run_at, exposed_databases.last_run_at and leaked_credentials.last_run_at are timestamps, not counts: each is when that radar last completed a check, a cycle whose reads succeeded, among them a Shodan search (for exposed_databases, or its LeakIX fallback) that answered at least one query, or for leaked_credentials a read of the public GitHub event stream. A cycle that read nothing does not move it, and it is not the time of every record a radar's numbers count, which cover everything still on record, not only what the last check found. A radar whose answer carries no last_run_at has none in the result, and the note says nothing about it. Shodan data is owned by Shodan, which holds its copyright (© Shodan).",
- "properties": {
- "exposed_databases": {
- "additionalProperties": false,
- "description": "exposed_databases: exposed_databases.distinct_hosts counts distinct ip:port services the check confirmed answering without authentication, and exposed_databases.engines the distinct engine types among them; exposed_databases.top_engines and exposed_databases.top_countries rank up to 15 engines and 10 countries by those services. exposed_databases.pii_likely and exposed_databases.pci_likely count services whose schema names (never record values) pass a high-confidence, precision-first gate for personal or payment-card data, so a service outside them is not shown to hold no such data. exposed_databases.window.from and exposed_databases.window.to are timestamps, not counts: when EchelonGraph's check last confirmed the oldest and the newest of the services counted, a span of checks made at different times; with a counted service that has no such time on record there is no window.",
- "properties": {
- "distinct_hosts": {
- "type": "number"
- },
- "engines": {
- "type": "number"
- },
- "generated_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "last_run_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "pci_likely": {
- "type": "number"
- },
- "pii_likely": {
- "type": "number"
- },
- "top_countries": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "country": {
- "type": "string"
- },
- "hosts": {
- "type": "number"
- }
- },
- "required": [
- "country",
- "hosts"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "top_engines": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "engine": {
- "type": "string"
- },
- "hosts": {
- "type": "number"
- }
- },
- "required": [
- "engine",
- "hosts"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "window": {
- "additionalProperties": false,
- "properties": {
- "from": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "to": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- }
- },
- "required": [
- "from",
- "to"
- ],
- "type": "object"
- }
- },
- "type": "object"
- },
- "kev_exposure": {
- "additionalProperties": false,
- "description": "kev_exposure: kev_exposure.distinct_hosts counts distinct ip:port services, not machines (a machine answering on two ports counts twice), with at least one CISA-KEV-listed CVE on record, and kev_exposure.ransomware_hosts those with a ransomware-linked one; kev_exposure.kev_cves_exposed and kev_exposure.ransomware_cves count distinct CVEs with at least one such service; kev_exposure.correlations counts service×CVE pairs, not services, so a service with three KEV CVEs counts three times. kev_exposure.top_products, kev_exposure.top_countries and kev_exposure.top_cves rank up to 12 products, 10 countries and 12 CVEs by those services; kev_exposure.top_cves[].cvss_v3_score and kev_exposure.top_cves[].epss_score are the highest CVSS v3 base score and EPSS probability recorded on that CVE's observations. kev_exposure.trend counts service×CVE pairs still on record by the week in which each was first recorded, over 12 weeks, so earlier weeks read low. kev_exposure.newest_kev lists the 15 CVEs that EchelonGraph's CVE records most recently mark as CISA-KEV-listed, each with an exposure_state: exposed, where kev_exposure.newest_kev[].exposed_hosts counts distinct ip:port services on record with it; or not_assessed, where the API's answer holds no measurement for that CVE (its 0 is not one) and no count is relayed, and this answer does not say whether the radar tracks that CVE: tracking is answered per CVE. kev_exposure.newest_kev[].cvss_v3_score and kev_exposure.newest_kev[].epss_score are the CVE record's CVSS v3 base score and EPSS probability. kev_exposure.window.from and kev_exposure.window.to are timestamps, not counts: when Shodan collected the banners of the oldest and the newest of the service×CVE pairs counted, a span of observations made at different times; with a counted pair that has no such time on record there is no window. Shodan data is owned by Shodan, which holds its copyright (© Shodan).",
- "properties": {
- "correlations": {
- "type": "number"
- },
- "distinct_hosts": {
- "type": "number"
- },
- "generated_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "kev_cves_exposed": {
- "type": "number"
- },
- "last_run_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "newest_kev": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "added_date": {
- "type": "string"
- },
- "cve_id": {
- "type": "string"
- },
- "cvss_v3_score": {
- "type": "number"
- },
- "epss_score": {
- "type": "number"
- },
- "exposed_hosts": {
- "type": "number"
- },
- "exposure_state": {
- "enum": [
- "exposed",
- "measured_zero",
- "not_assessed"
- ],
- "type": "string"
- },
- "ransomware": {
- "type": "boolean"
- },
- "severity": {
- "type": "string"
- },
- "vuln_name": {
- "type": "string"
- }
- },
- "required": [
- "cve_id",
- "exposure_state"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "ransomware_cves": {
- "type": "number"
- },
- "ransomware_hosts": {
- "type": "number"
- },
- "top_countries": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "country": {
- "type": "string"
- },
- "hosts": {
- "type": "number"
- }
- },
- "required": [
- "country",
- "hosts"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "top_cves": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "cve_id": {
- "type": "string"
- },
- "cvss_v3_score": {
- "type": "number"
- },
- "epss_score": {
- "type": "number"
- },
- "hosts": {
- "type": "number"
- },
- "ransomware": {
- "type": "boolean"
- },
- "severity": {
- "type": "string"
- }
- },
- "required": [
- "cve_id",
- "hosts"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "top_products": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "hosts": {
- "type": "number"
- },
- "product": {
- "type": "string"
- }
- },
- "required": [
- "product",
- "hosts"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "trend": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "new_exposures": {
- "type": "number"
- },
- "week": {
- "type": "string"
- }
- },
- "required": [
- "week",
- "new_exposures"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "window": {
- "additionalProperties": false,
- "properties": {
- "from": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "to": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- }
- },
- "required": [
- "from",
- "to"
- ],
- "type": "object"
- }
- },
- "type": "object"
- },
- "leaked_credentials": {
- "additionalProperties": false,
- "description": "leaked_credentials: leaked_credentials.total counts (repository, secret) pairs, not distinct secrets, so one secret in three repositories counts three times; leaked_credentials.distinct_secrets counts each secret once and leaked_credentials.distinct_repos counts repositories; leaked_credentials.top_providers and leaked_credentials.top_types rank up to 15 providers and secret types by those pairs. None is validated: each is a credential-shaped string that passed EchelonGraph's filters, at most structurally checked and never tested against its provider. leaked_credentials.window.from and leaked_credentials.window.to are timestamps, not counts: when EchelonGraph's detector last found the oldest and the newest of the pairs counted.",
- "properties": {
- "distinct_repos": {
- "type": "number"
- },
- "distinct_secrets": {
- "type": "number"
- },
- "generated_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "last_run_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "top_providers": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "count": {
- "type": "number"
- },
- "provider": {
- "type": "string"
- }
- },
- "required": [
- "provider",
- "count"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "top_types": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "count": {
- "type": "number"
- },
- "secret_type": {
- "type": "string"
- }
- },
- "required": [
- "secret_type",
- "count"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "total": {
- "type": "number"
- },
- "window": {
- "additionalProperties": false,
- "properties": {
- "from": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "to": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- }
- },
- "required": [
- "from",
- "to"
- ],
- "type": "object"
- }
- },
- "type": "object"
- },
- "mcp_servers": {
- "additionalProperties": false,
- "description": "mcp_servers: counts and timestamps only, no hostname. mcp_servers.total counts hostnames the AI-exposure radar has checked for an MCP server, each once by its latest verdict on record, and not every one is an MCP server; EchelonGraph's own control servers are left out, and mcp_servers.own_controls_excluded counts them; hostnames whose owner opted out of scanning are not checked again while the opt-out stands, so their last verdict is left out too, and mcp_servers.withheld_opted_out counts them. mcp_servers.total is mcp_servers.protected plus mcp_servers.pending_readjudication plus mcp_servers.not_assessed. mcp_servers.protected counts hostnames whose /mcp endpoint asked for credentials and whose OAuth protected-resource metadata validated under RFC 9728; mcp_servers.prm_via divides them by where that document was found: mcp_servers.prm_via.header, mcp_servers.prm_via.wellknown_path and mcp_servers.prm_via.wellknown_root. mcp_servers.pending_readjudication counts verdicts of a rule since replaced, not yet re-checked. mcp_servers.not_assessed counts the rest, whose protection the radar could not assess (not assessed does not mean unprotected), and mcp_servers.not_assessed_by_reason puts each in one bucket. mcp_servers.not_assessed_by_reason.identified_no_challenge holds servers that identified themselves as MCP servers and did not ask for credentials at the handshake. That is normal in MCP: authorization is optional in the spec, and a server can enforce it at tools/call instead, which EchelonGraph never sends, so this bucket is not a finding of exposure. mcp_servers.not_assessed_by_reason.resource_mismatch, mcp_servers.not_assessed_by_reason.cross_origin_pointer, mcp_servers.not_assessed_by_reason.bare_challenge_no_prm, mcp_servers.not_assessed_by_reason.pointer_unreachable, mcp_servers.not_assessed_by_reason.pointer_invalid, mcp_servers.not_assessed_by_reason.no_authorization_servers, mcp_servers.not_assessed_by_reason.wellknown_unreachable and mcp_servers.not_assessed_by_reason.metadata_invalid hold endpoints that asked for credentials but whose RFC 9728 metadata did not validate, by why, so none of them is shown to lack protection; mcp_servers.not_assessed_by_reason.challenge_unadjudicated holds endpoints that asked for credentials before that check existed, not yet re-checked. mcp_servers.not_assessed_by_reason.no_http_answer holds hostnames that gave no HTTP answer, and mcp_servers.not_assessed_by_reason.not_identified_as_mcp hostnames whose HTTP answer identified no MCP server. mcp_servers.era divides every counted hostname by protocol era: mcp_servers.era.legacy; mcp_servers.era.dual, a lower bound; mcp_servers.era.modern, not proven modern-only; mcp_servers.era.unknown; and mcp_servers.era.not_measured, recorded before the era probe and not re-checked since. mcp_servers.transport divides them by transport: mcp_servers.transport.streamable_http, mcp_servers.transport.legacy_sse, mcp_servers.transport.unknown and mcp_servers.transport.not_measured. mcp_servers.window.from and mcp_servers.window.to are when the oldest and the newest of the verdicts counted were last checked. mcp_servers.last_run_at is a timestamp, not a count: when the AI-exposure radar, which checks other AI services too, last completed a check; mcp_servers.enabled is whether one completed within 45 minutes of the answer, and mcp_servers.counted_at is when the API read the counts. A partition whose buckets do not add up to the count it divides is left out and named; an answer that does not say it is the MCP-server counts, or whose mcp_servers.total, mcp_servers.protected, mcp_servers.pending_readjudication and mcp_servers.not_assessed are missing or contradict each other, is a failure.",
- "properties": {
- "counted_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "enabled": {
- "type": "boolean"
- },
- "era": {
- "additionalProperties": false,
- "properties": {
- "dual": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "legacy": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "modern": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "not_measured": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "unknown": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- }
- },
- "required": [
- "legacy",
- "dual",
- "modern",
- "unknown",
- "not_measured"
- ],
- "type": "object"
- },
- "last_run_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "not_assessed": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "not_assessed_by_reason": {
- "additionalProperties": false,
- "properties": {
- "bare_challenge_no_prm": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "challenge_unadjudicated": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "cross_origin_pointer": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "identified_no_challenge": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "metadata_invalid": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "no_authorization_servers": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "no_http_answer": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "not_identified_as_mcp": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "pointer_invalid": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "pointer_unreachable": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "resource_mismatch": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "wellknown_unreachable": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- }
- },
- "required": [
- "identified_no_challenge",
- "resource_mismatch",
- "cross_origin_pointer",
- "bare_challenge_no_prm",
- "pointer_unreachable",
- "pointer_invalid",
- "no_authorization_servers",
- "wellknown_unreachable",
- "metadata_invalid",
- "challenge_unadjudicated",
- "no_http_answer",
- "not_identified_as_mcp"
- ],
- "type": "object"
- },
- "own_controls_excluded": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "pending_readjudication": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "prm_via": {
- "additionalProperties": false,
- "properties": {
- "header": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "wellknown_path": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "wellknown_root": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- }
- },
- "required": [
- "header",
- "wellknown_path",
- "wellknown_root"
- ],
- "type": "object"
- },
- "protected": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "total": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "transport": {
- "additionalProperties": false,
- "properties": {
- "legacy_sse": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "not_measured": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "streamable_http": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- },
- "unknown": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- }
- },
- "required": [
- "streamable_http",
- "legacy_sse",
- "unknown",
- "not_measured"
- ],
- "type": "object"
- },
- "window": {
- "additionalProperties": false,
- "properties": {
- "from": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "to": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- }
- },
- "required": [
- "from",
- "to"
- ],
- "type": "object"
- },
- "withheld_opted_out": {
- "maximum": 9007199254740991,
- "minimum": 0,
- "type": "integer"
- }
- },
- "required": [
- "total",
- "protected",
- "pending_readjudication",
- "not_assessed"
- ],
- "type": "object"
- },
- "shadow_ai": {
- "additionalProperties": false,
- "description": "shadow_ai: the result is regrouped by what each number counts. shadow_ai.confirmed_exposed counts services EchelonGraph's probes found answering without an authentication gate (liveness active, or rechecking during a re-check): confirmed_exposed.total is the sum of confirmed_exposed.by_category, and confirmed_exposed.last_24h counts those first recorded in the last 24 h. Of the shadow_ai numbers, only confirmed_exposed counts exposed services. confirmed_exposed.window.from and confirmed_exposed.window.to are timestamps, not counts: when EchelonGraph's verifier ran the probe that last decided the oldest and the newest of those services, a span of probes made at different times. shadow_ai.observed counts every Certificate Transparency or Shodan observation on record, whatever its verification state: its numbers are observed, not exposed. They are observed.total; observed.by_category (the same observations by category); observed.last_24h (those first recorded in the last 24 h); observed.trend_30d (observations per UTC day over the last 30 days); and observed.top_products, observed.top_countries and observed.top_issuers (up to ten products, countries and issuers ranked by observations, where an issuer is the certificate's CA for a Certificate Transparency observation and the hosting operator Shodan reports for a Shodan one). shadow_ai.authentication counts observations by probe outcome: authentication.observed where a probe observed an authentication gate (a 401/403, a login page or an auth marker), and authentication.not_determined where the service answered but no probe could tell. Neither authentication count is part of confirmed_exposed, and observed.total minus confirmed_exposed.total is not a count of secured services. The shadow-AI poller block carries only running and last_run_at: last_run_at is when the radar's leader last completed a Certificate Transparency (crt.sh) cycle, and its running is true only when that was within 30 minutes of the answer. Shodan data is owned by Shodan, which holds its copyright (© Shodan).",
- "properties": {
- "authentication": {
- "additionalProperties": false,
- "properties": {
- "not_determined": {
- "type": "number"
- },
- "observed": {
- "type": "number"
- }
- },
- "type": "object"
- },
- "confirmed_exposed": {
- "additionalProperties": false,
- "properties": {
- "by_category": {
- "additionalProperties": {
- "type": "number"
- },
- "propertyNames": {
- "type": "string"
- },
- "type": "object"
- },
- "last_24h": {
- "type": "number"
- },
- "total": {
- "type": "number"
- },
- "window": {
- "additionalProperties": false,
- "properties": {
- "from": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "to": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- }
- },
- "required": [
- "from",
- "to"
- ],
- "type": "object"
- }
- },
- "type": "object"
- },
- "observed": {
- "additionalProperties": false,
- "properties": {
- "by_category": {
- "additionalProperties": {
- "type": "number"
- },
- "propertyNames": {
- "type": "string"
- },
- "type": "object"
- },
- "last_24h": {
- "type": "number"
- },
- "last_observation": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "top_countries": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "count": {
- "type": "number"
- },
- "country": {
- "type": "string"
- }
- },
- "required": [
- "country",
- "count"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "top_issuers": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "count": {
- "type": "number"
- },
- "issuer": {
- "type": "string"
- }
- },
- "required": [
- "issuer",
- "count"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "top_products": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "count": {
- "type": "number"
- },
- "product": {
- "type": "string"
- }
- },
- "required": [
- "product",
- "count"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "total": {
- "type": "number"
- },
- "trend_30d": {
- "items": {
- "additionalProperties": false,
- "properties": {
- "count": {
- "type": "number"
- },
- "date": {
- "type": "string"
- }
- },
- "required": [
- "date",
- "count"
- ],
- "type": "object"
- },
- "type": "array"
- }
- },
- "type": "object"
- },
- "poller": {
- "additionalProperties": false,
- "properties": {
- "last_run_at": {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- "running": {
- "type": "boolean"
- }
- },
- "required": [
- "running",
- "last_run_at"
- ],
- "type": "object"
- }
- },
- "type": "object"
- }
- },
- "required": [
- "kev_exposure",
- "exposed_databases",
- "leaked_credentials",
- "shadow_ai",
- "mcp_servers"
- ],
- "type": "object"
- },
- "freshness": {
- "anyOf": [
- {
- "additionalProperties": false,
- "properties": {
- "exposed_databases": {
- "additionalProperties": false,
- "properties": {
- "last_run_at": {
- "anyOf": [
- {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- {
- "type": "null"
- }
- ]
- }
- },
- "required": [
- "last_run_at"
- ],
- "type": "object"
- },
- "kev_exposure": {
- "additionalProperties": false,
- "properties": {
- "last_run_at": {
- "anyOf": [
- {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- {
- "type": "null"
- }
- ]
- }
- },
- "required": [
- "last_run_at"
- ],
- "type": "object"
- },
- "leaked_credentials": {
- "additionalProperties": false,
- "properties": {
- "last_run_at": {
- "anyOf": [
- {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- {
- "type": "null"
- }
- ]
- }
- },
- "required": [
- "last_run_at"
- ],
- "type": "object"
- },
- "mcp_servers": {
- "additionalProperties": false,
- "properties": {
- "enabled": {
- "type": [
- "boolean",
- "null"
- ]
- },
- "last_run_at": {
- "anyOf": [
- {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- {
- "type": "null"
- }
- ]
- }
- },
- "required": [
- "last_run_at",
- "enabled"
- ],
- "type": "object"
- },
- "shadow_ai": {
- "additionalProperties": false,
- "properties": {
- "last_run_at": {
- "anyOf": [
- {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- {
- "type": "null"
- }
- ]
- },
- "running": {
- "type": [
- "boolean",
- "null"
- ]
- }
- },
- "required": [
- "last_run_at",
- "running"
- ],
- "type": "object"
- }
- },
- "required": [
- "kev_exposure",
- "exposed_databases",
- "leaked_credentials",
- "shadow_ai",
- "mcp_servers"
- ],
- "type": "object"
- },
- {
- "type": "null"
- }
- ],
- "description": "The producing radar's last completed check (last_run_at), where the API serves one."
- },
- "measured_at": {
- "anyOf": [
- {
- "description": "An RFC 3339 instant.",
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "When the underlying observation was made, as the API states it; null when the answer does not say or holds no observation."
- },
- "method": {
- "description": "How the numbers were produced.",
- "type": "string"
- },
- "notes": {
- "description": "Caveats, one sentence each.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "state": {
- "description": "measured: a measurement of what was asked; an exposure count is measured only with measured_at and method. not_assessed: the answer holds no dated measurement of what was asked, so no count in it is presented as one; it can still relay a count, as what the source holds on record, undated, and its notes (and exposure_state, where the result carries it) say what each count is.",
- "enum": [
- "measured",
- "not_assessed"
- ],
- "type": "string"
- }
- },
- "required": [
- "state",
- "measured_at",
- "method",
- "coverage",
- "freshness",
- "notes",
- "data"
- ],
- "type": "object"
- },
- {
- "additionalProperties": false,
- "properties": {
- "coverage": {
- "anyOf": [
- {
- "additionalProperties": false,
- "properties": {
- "answered": {
- "description": "The radars that answered. On a failure their answers are withheld.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "failed": {
- "description": "The radars that could not be read.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "radars": {
- "description": "The radars this tool reads.",
- "items": {
- "type": "string"
- },
- "type": "array"
- }
- },
- "required": [
- "radars",
- "answered",
- "failed"
- ],
- "type": "object"
- },
- {
- "type": "null"
- }
- ]
- },
- "error": {
- "additionalProperties": false,
- "properties": {
- "kind": {
- "description": "What failed: the request (network, timeout), the answer (http, not_json, not_object, unexpected_shape), the input, or this server.",
- "enum": [
- "network",
- "timeout",
- "http",
- "not_json",
- "not_object",
- "invalid_input",
- "internal",
- "unexpected_shape",
- "radars"
- ],
- "type": "string"
- },
- "message": {
- "description": "The cause: the API's own message, or what went wrong.",
- "type": "string"
- },
- "path": {
- "description": "The API path requested, when a request was made.",
- "type": [
- "string",
- "null"
- ]
- },
- "radars": {
- "description": "Each radar that could not be read, and why.",
- "items": {
- "additionalProperties": false,
- "properties": {
- "kind": {
- "enum": [
- "network",
- "timeout",
- "http",
- "not_json",
- "not_object",
- "unexpected_shape"
- ],
- "type": "string"
- },
- "message": {
- "type": "string"
- },
- "path": {
- "type": "string"
- },
- "radar": {
- "type": "string"
- },
- "status": {
- "anyOf": [
- {
- "maximum": 9007199254740991,
- "minimum": -9007199254740991,
- "type": "integer"
- },
- {
- "type": "null"
- }
- ]
- }
- },
- "required": [
- "radar",
- "kind",
- "path",
- "status",
- "message"
- ],
- "type": "object"
- },
- "type": "array"
- },
- "status": {
- "anyOf": [
- {
- "maximum": 9007199254740991,
- "minimum": -9007199254740991,
- "type": "integer"
- },
- {
- "type": "null"
- }
- ],
- "description": "The HTTP status, when the API answered one."
- }
- },
- "required": [
- "kind",
- "path",
- "status",
- "message"
- ],
- "type": "object"
- },
- "freshness": {
- "type": "null"
- },
- "measured_at": {
- "type": "null"
- },
- "method": {
- "type": "null"
- },
- "notes": {
- "description": "Caveats, one sentence each.",
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "state": {
- "description": "failed: the lookup did not complete. invalid_input: the input was refused, so nothing was looked up. Neither is a finding.",
- "enum": [
- "failed",
- "invalid_input"
- ],
- "type": "string"
- }
- },
- "required": [
- "state",
- "measured_at",
- "method",
- "coverage",
- "freshness",
- "notes",
- "error"
- ],
- "type": "object"
- }
-]New value: +[
+ {
+ "additionalProperties": {},
+ "properties": {
+ "coverage": {
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "answered": {
+ "description": "The radars that answered. On a failure their answers are withheld.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "failed": {
+ "description": "The radars that could not be read.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "radars": {
+ "description": "The radars this tool reads.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "radars",
+ "answered",
+ "failed"
+ ],
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "What the answer covers; null where the answer says nothing about it."
+ },
+ "data": {
+ "additionalProperties": false,
+ "description": "A field this version cannot label is left out of data and named in the note. Each of the kev_exposure, exposed_databases and leaked_credentials radars also carries generated_at, when the API computed its totals, and, when the API can tell, last_run_at. kev_exposure.last_run_at, exposed_databases.last_run_at and leaked_credentials.last_run_at are timestamps, not counts: each is when that radar last completed a check, a cycle whose reads succeeded, among them a Shodan search (for exposed_databases, or its LeakIX fallback) that answered at least one query, or for leaked_credentials a read of the public GitHub event stream. A cycle that read nothing does not move it, and it is not the time of every record a radar's numbers count, which cover everything still on record, not only what the last check found. A radar whose answer carries no last_run_at has none in the result, and the note says nothing about it. Shodan data is owned by Shodan, which holds its copyright (© Shodan).",
+ "properties": {
+ "exposed_databases": {
+ "additionalProperties": false,
+ "description": "exposed_databases: exposed_databases.distinct_hosts counts distinct ip:port services the check confirmed answering without authentication, and exposed_databases.engines the distinct engine types among them; exposed_databases.top_engines and exposed_databases.top_countries rank up to 15 engines and 10 countries by those services. exposed_databases.pii_likely and exposed_databases.pci_likely count services whose schema names (never record values) pass a high-confidence, precision-first gate for personal or payment-card data, so a service outside them is not shown to hold no such data. exposed_databases.window.from and exposed_databases.window.to are timestamps, not counts: when EchelonGraph's check last confirmed the oldest and the newest of the services counted, a span of checks made at different times; with a counted service that has no such time on record there is no window.",
+ "properties": {
+ "distinct_hosts": {
+ "type": "number"
+ },
+ "engines": {
+ "type": "number"
+ },
+ "generated_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "last_run_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "pci_likely": {
+ "type": "number"
+ },
+ "pii_likely": {
+ "type": "number"
+ },
+ "top_countries": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "country": {
+ "type": "string"
+ },
+ "hosts": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "country",
+ "hosts"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "top_engines": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "engine": {
+ "type": "string"
+ },
+ "hosts": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "engine",
+ "hosts"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "window": {
+ "additionalProperties": false,
+ "properties": {
+ "from": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "to": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "to"
+ ],
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "kev_exposure": {
+ "additionalProperties": false,
+ "description": "kev_exposure: kev_exposure.distinct_hosts counts distinct ip:port services, not machines (a machine answering on two ports counts twice), with at least one CISA-KEV-listed CVE on record, and kev_exposure.ransomware_hosts those with a ransomware-linked one; kev_exposure.kev_cves_exposed and kev_exposure.ransomware_cves count distinct CVEs with at least one such service; kev_exposure.correlations counts service×CVE pairs, not services, so a service with three KEV CVEs counts three times. kev_exposure.top_products, kev_exposure.top_countries and kev_exposure.top_cves rank up to 12 products, 10 countries and 12 CVEs by those services; kev_exposure.top_cves[].cvss_v3_score and kev_exposure.top_cves[].epss_score are the highest CVSS v3 base score and EPSS probability recorded on that CVE's observations. kev_exposure.trend counts service×CVE pairs still on record by the week in which each was first recorded, over 12 weeks, so earlier weeks read low. kev_exposure.newest_kev lists the 15 CVEs that EchelonGraph's CVE records most recently mark as CISA-KEV-listed, each with an exposure_state: exposed, where kev_exposure.newest_kev[].exposed_hosts counts distinct ip:port services on record with it; or not_assessed, where the API's answer holds no measurement for that CVE (its 0 is not one) and no count is relayed, and this answer does not say whether the radar tracks that CVE: tracking is answered per CVE. kev_exposure.newest_kev[].cvss_v3_score and kev_exposure.newest_kev[].epss_score are the CVE record's CVSS v3 base score and EPSS probability. kev_exposure.window.from and kev_exposure.window.to are timestamps, not counts: when Shodan collected the banners of the oldest and the newest of the service×CVE pairs counted, a span of observations made at different times; with a counted pair that has no such time on record there is no window. Shodan data is owned by Shodan, which holds its copyright (© Shodan).",
+ "properties": {
+ "correlations": {
+ "type": "number"
+ },
+ "distinct_hosts": {
+ "type": "number"
+ },
+ "generated_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "kev_cves_exposed": {
+ "type": "number"
+ },
+ "last_run_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "newest_kev": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "added_date": {
+ "type": "string"
+ },
+ "cve_id": {
+ "type": "string"
+ },
+ "cvss_v3_score": {
+ "type": "number"
+ },
+ "epss_score": {
+ "type": "number"
+ },
+ "exposed_hosts": {
+ "type": "number"
+ },
+ "exposure_state": {
+ "enum": [
+ "exposed",
+ "measured_zero",
+ "not_assessed"
+ ],
+ "type": "string"
+ },
+ "ransomware": {
+ "type": "boolean"
+ },
+ "severity": {
+ "type": "string"
+ },
+ "vuln_name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "cve_id",
+ "exposure_state"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "ransomware_cves": {
+ "type": "number"
+ },
+ "ransomware_hosts": {
+ "type": "number"
+ },
+ "top_countries": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "country": {
+ "type": "string"
+ },
+ "hosts": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "country",
+ "hosts"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "top_cves": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "cve_id": {
+ "type": "string"
+ },
+ "cvss_v3_score": {
+ "type": "number"
+ },
+ "epss_score": {
+ "type": "number"
+ },
+ "hosts": {
+ "type": "number"
+ },
+ "ransomware": {
+ "type": "boolean"
+ },
+ "severity": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "cve_id",
+ "hosts"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "top_products": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "hosts": {
+ "type": "number"
+ },
+ "product": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "product",
+ "hosts"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "trend": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "new_exposures": {
+ "type": "number"
+ },
+ "week": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "week",
+ "new_exposures"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "window": {
+ "additionalProperties": false,
+ "properties": {
+ "from": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "to": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "to"
+ ],
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "leaked_credentials": {
+ "additionalProperties": false,
+ "description": "leaked_credentials: leaked_credentials.total counts (repository, secret) pairs, not distinct secrets, so one secret in three repositories counts three times; leaked_credentials.distinct_secrets counts each secret once and leaked_credentials.distinct_repos counts repositories; leaked_credentials.top_providers and leaked_credentials.top_types rank up to 15 providers and secret types by those pairs. None is validated: each is a credential-shaped string that passed EchelonGraph's filters, at most structurally checked and never tested against its provider. leaked_credentials.window.from and leaked_credentials.window.to are timestamps, not counts: when EchelonGraph's detector last found the oldest and the newest of the pairs counted.",
+ "properties": {
+ "distinct_repos": {
+ "type": "number"
+ },
+ "distinct_secrets": {
+ "type": "number"
+ },
+ "generated_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "last_run_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "top_providers": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "type": "number"
+ },
+ "provider": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "provider",
+ "count"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "top_types": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "type": "number"
+ },
+ "secret_type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "secret_type",
+ "count"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "total": {
+ "type": "number"
+ },
+ "window": {
+ "additionalProperties": false,
+ "properties": {
+ "from": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "to": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "to"
+ ],
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "mcp_servers": {
+ "additionalProperties": false,
+ "description": "mcp_servers: counts and timestamps only, no hostname. mcp_servers.total counts hostnames the AI-exposure radar has checked for an MCP server, each once by its latest verdict on record, and not every one is an MCP server; EchelonGraph's own control servers are left out, and mcp_servers.own_controls_excluded counts them; hostnames whose owner opted out of scanning are not checked again while the opt-out stands, so their last verdict is left out too, and mcp_servers.withheld_opted_out counts them. mcp_servers.total is mcp_servers.protected plus mcp_servers.pending_readjudication plus mcp_servers.not_assessed. mcp_servers.protected counts hostnames whose /mcp endpoint asked for credentials and whose OAuth protected-resource metadata validated under RFC 9728; mcp_servers.prm_via divides them by where that document was found: mcp_servers.prm_via.header, mcp_servers.prm_via.wellknown_path and mcp_servers.prm_via.wellknown_root. mcp_servers.pending_readjudication counts verdicts of a rule since replaced, not yet re-checked. mcp_servers.not_assessed counts the rest, whose protection the radar could not assess (not assessed does not mean unprotected), and mcp_servers.not_assessed_by_reason puts each in one bucket. mcp_servers.not_assessed_by_reason.identified_no_challenge holds servers that identified themselves as MCP servers and did not ask for credentials at the handshake. That is normal in MCP: authorization is optional in the spec, and a server can enforce it at tools/call instead, which EchelonGraph never sends, so this bucket is not a finding of exposure. mcp_servers.not_assessed_by_reason.resource_mismatch, mcp_servers.not_assessed_by_reason.cross_origin_pointer, mcp_servers.not_assessed_by_reason.bare_challenge_no_prm, mcp_servers.not_assessed_by_reason.pointer_unreachable, mcp_servers.not_assessed_by_reason.pointer_invalid, mcp_servers.not_assessed_by_reason.no_authorization_servers, mcp_servers.not_assessed_by_reason.wellknown_unreachable and mcp_servers.not_assessed_by_reason.metadata_invalid hold endpoints that asked for credentials but whose RFC 9728 metadata did not validate, by why, so none of them is shown to lack protection; mcp_servers.not_assessed_by_reason.challenge_unadjudicated holds endpoints that asked for credentials before that check existed, not yet re-checked. mcp_servers.not_assessed_by_reason.no_http_answer holds hostnames that gave no HTTP answer, and mcp_servers.not_assessed_by_reason.not_identified_as_mcp hostnames whose HTTP answer identified no MCP server. mcp_servers.era divides every counted hostname by protocol era: mcp_servers.era.legacy; mcp_servers.era.dual, a lower bound; mcp_servers.era.modern, not proven modern-only; mcp_servers.era.unknown; and mcp_servers.era.not_measured, recorded before the era probe and not re-checked since. mcp_servers.transport divides them by transport: mcp_servers.transport.streamable_http, mcp_servers.transport.legacy_sse, mcp_servers.transport.unknown and mcp_servers.transport.not_measured. mcp_servers.window.from and mcp_servers.window.to are when the oldest and the newest of the verdicts counted were last checked. mcp_servers.last_run_at is a timestamp, not a count: when the AI-exposure radar, which checks other AI services too, last completed a check; mcp_servers.enabled is whether one completed within 45 minutes of the answer, and mcp_servers.counted_at is when the API read the counts. A partition whose buckets do not add up to the count it divides is left out and named; an answer that does not say it is the MCP-server counts, or whose mcp_servers.total, mcp_servers.protected, mcp_servers.pending_readjudication and mcp_servers.not_assessed are missing or contradict each other, is a failure.",
+ "properties": {
+ "counted_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "era": {
+ "additionalProperties": false,
+ "properties": {
+ "dual": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "legacy": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "modern": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "not_measured": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "unknown": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "legacy",
+ "dual",
+ "modern",
+ "unknown",
+ "not_measured"
+ ],
+ "type": "object"
+ },
+ "last_run_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "not_assessed": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "not_assessed_by_reason": {
+ "additionalProperties": false,
+ "properties": {
+ "bare_challenge_no_prm": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "challenge_unadjudicated": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "cross_origin_pointer": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "identified_no_challenge": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "metadata_invalid": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "no_authorization_servers": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "no_http_answer": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "not_identified_as_mcp": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "pointer_invalid": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "pointer_unreachable": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "resource_mismatch": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "wellknown_unreachable": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "identified_no_challenge",
+ "resource_mismatch",
+ "cross_origin_pointer",
+ "bare_challenge_no_prm",
+ "pointer_unreachable",
+ "pointer_invalid",
+ "no_authorization_servers",
+ "wellknown_unreachable",
+ "metadata_invalid",
+ "challenge_unadjudicated",
+ "no_http_answer",
+ "not_identified_as_mcp"
+ ],
+ "type": "object"
+ },
+ "own_controls_excluded": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "pending_readjudication": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "prm_via": {
+ "additionalProperties": false,
+ "properties": {
+ "header": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "wellknown_path": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "wellknown_root": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "header",
+ "wellknown_path",
+ "wellknown_root"
+ ],
+ "type": "object"
+ },
+ "protected": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "total": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "transport": {
+ "additionalProperties": false,
+ "properties": {
+ "legacy_sse": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "not_measured": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "streamable_http": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "unknown": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "streamable_http",
+ "legacy_sse",
+ "unknown",
+ "not_measured"
+ ],
+ "type": "object"
+ },
+ "window": {
+ "additionalProperties": false,
+ "properties": {
+ "from": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "to": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "to"
+ ],
+ "type": "object"
+ },
+ "withheld_opted_out": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "total",
+ "protected",
+ "pending_readjudication",
+ "not_assessed"
+ ],
+ "type": "object"
+ },
+ "shadow_ai": {
+ "additionalProperties": false,
+ "description": "shadow_ai: the result is regrouped by what each number counts. shadow_ai.confirmed_exposed counts services EchelonGraph's probes found answering without an authentication gate (liveness active, or rechecking during a re-check): confirmed_exposed.total is the sum of confirmed_exposed.by_category, and confirmed_exposed.last_24h counts those first recorded in the last 24 h. Of the shadow_ai numbers, only confirmed_exposed counts exposed services. confirmed_exposed.window.from and confirmed_exposed.window.to are timestamps, not counts: when EchelonGraph's verifier ran the probe that last decided the oldest and the newest of those services, a span of probes made at different times. shadow_ai.observed counts every Certificate Transparency or Shodan observation on record, whatever its verification state: its numbers are observed, not exposed. They are observed.total; observed.by_category (the same observations by category); observed.last_24h (those first recorded in the last 24 h); observed.trend_30d (observations per UTC day over the last 30 days); and observed.top_products, observed.top_countries and observed.top_issuers (up to ten products, countries and issuers ranked by observations, where an issuer is the certificate's CA for a Certificate Transparency observation and the hosting operator Shodan reports for a Shodan one). shadow_ai.authentication counts observations by probe outcome: authentication.observed where a probe observed an authentication gate (a 401/403, a login page or an auth marker), and authentication.not_determined where the service answered but no probe could tell. Neither authentication count is part of confirmed_exposed, and observed.total minus confirmed_exposed.total is not a count of secured services. The shadow-AI poller block carries only running and last_run_at: last_run_at is when the radar's leader last completed a Certificate Transparency (crt.sh) cycle, and its running is true only when that was within 30 minutes of the answer. Shodan data is owned by Shodan, which holds its copyright (© Shodan).",
+ "properties": {
+ "authentication": {
+ "additionalProperties": false,
+ "properties": {
+ "not_determined": {
+ "type": "number"
+ },
+ "observed": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
+ "confirmed_exposed": {
+ "additionalProperties": false,
+ "properties": {
+ "by_category": {
+ "additionalProperties": {
+ "type": "number"
+ },
+ "propertyNames": {
+ "type": "string"
+ },
+ "type": "object"
+ },
+ "last_24h": {
+ "type": "number"
+ },
+ "total": {
+ "type": "number"
+ },
+ "window": {
+ "additionalProperties": false,
+ "properties": {
+ "from": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "to": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "from",
+ "to"
+ ],
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "observed": {
+ "additionalProperties": false,
+ "properties": {
+ "by_category": {
+ "additionalProperties": {
+ "type": "number"
+ },
+ "propertyNames": {
+ "type": "string"
+ },
+ "type": "object"
+ },
+ "last_24h": {
+ "type": "number"
+ },
+ "last_observation": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "top_countries": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "type": "number"
+ },
+ "country": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "country",
+ "count"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "top_issuers": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "type": "number"
+ },
+ "issuer": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "issuer",
+ "count"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "top_products": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "type": "number"
+ },
+ "product": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "product",
+ "count"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "total": {
+ "type": "number"
+ },
+ "trend_30d": {
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "type": "number"
+ },
+ "date": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "date",
+ "count"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "poller": {
+ "additionalProperties": false,
+ "properties": {
+ "last_run_at": {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ "running": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "running",
+ "last_run_at"
+ ],
+ "type": "object"
+ }
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "kev_exposure",
+ "exposed_databases",
+ "leaked_credentials",
+ "shadow_ai",
+ "mcp_servers"
+ ],
+ "type": "object"
+ },
+ "data_truncated": {
+ "description": "false: data is the answer whole. true: the structured result would pass 30,000 characters of JSON with data whole, so data is cut (DATA CUT): the entries of some of its lists keep only some of their fields, or the lists only some of their entries, truncated gives each, and no string is shortened. The same call with full: true returns data whole.",
+ "type": "boolean"
+ },
+ "freshness": {
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "exposed_databases": {
+ "additionalProperties": false,
+ "properties": {
+ "last_run_at": {
+ "anyOf": [
+ {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "last_run_at"
+ ],
+ "type": "object"
+ },
+ "kev_exposure": {
+ "additionalProperties": false,
+ "properties": {
+ "last_run_at": {
+ "anyOf": [
+ {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "last_run_at"
+ ],
+ "type": "object"
+ },
+ "leaked_credentials": {
+ "additionalProperties": false,
+ "properties": {
+ "last_run_at": {
+ "anyOf": [
+ {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "last_run_at"
+ ],
+ "type": "object"
+ },
+ "mcp_servers": {
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "last_run_at": {
+ "anyOf": [
+ {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "last_run_at",
+ "enabled"
+ ],
+ "type": "object"
+ },
+ "shadow_ai": {
+ "additionalProperties": false,
+ "properties": {
+ "last_run_at": {
+ "anyOf": [
+ {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "running": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "last_run_at",
+ "running"
+ ],
+ "type": "object"
+ }
+ },
+ "required": [
+ "kev_exposure",
+ "exposed_databases",
+ "leaked_credentials",
+ "shadow_ai",
+ "mcp_servers"
+ ],
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The producing radar's last completed check (last_run_at), where the API serves one."
+ },
+ "measured_at": {
+ "anyOf": [
+ {
+ "description": "An RFC 3339 instant.",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "When the underlying observation was made, as the API states it; null when the answer does not say or holds no observation."
+ },
+ "method": {
+ "description": "How the numbers were produced.",
+ "type": "string"
+ },
+ "notes": {
+ "description": "Caveats, one sentence each.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "state": {
+ "description": "measured: a measurement of what was asked; an exposure count is measured only with measured_at and method. not_assessed: the answer holds no dated measurement of what was asked, so no count in it is presented as one; it can still relay a count, as what the source holds on record, undated, and its notes (and exposure_state, where the result carries it) say what each count is.",
+ "enum": [
+ "measured",
+ "not_assessed"
+ ],
+ "type": "string"
+ },
+ "truncated": {
+ "additionalProperties": {
+ "additionalProperties": false,
+ "properties": {
+ "returned": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "thinned": {
+ "const": true,
+ "type": "boolean"
+ },
+ "total": {
+ "maximum": 9007199254740991,
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "total",
+ "returned"
+ ],
+ "type": "object"
+ },
+ "description": "Present when data_truncated is true: each list of data that was cut, by its path in data (a dot between the names of an object and a list inside it), with total, its entries in the answer, returned, the entries data holds, and thinned, true when each of them keeps only some of its fields.",
+ "propertyNames": {
+ "type": "string"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "state",
+ "measured_at",
+ "method",
+ "coverage",
+ "freshness",
+ "notes",
+ "data_truncated",
+ "data"
+ ],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "coverage": {
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "answered": {
+ "description": "The radars that answered. On a failure their answers are withheld.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "failed": {
+ "description": "The radars that could not be read.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "radars": {
+ "description": "The radars this tool reads.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "radars",
+ "answered",
+ "failed"
+ ],
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "error": {
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "description": "What failed: the request (network, timeout), the answer (http, not_json, not_object, unexpected_shape), the input, or this server.",
+ "enum": [
+ "network",
+ "timeout",
+ "http",
+ "not_json",
+ "not_object",
+ "invalid_input",
+ "internal",
+ "unexpected_shape",
+ "radars"
+ ],
+ "type": "string"
+ },
+ "message": {
+ "description": "The cause: the API's own message, or what went wrong.",
+ "type": "string"
+ },
+ "path": {
+ "description": "The API path requested, when a request was made.",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "radars": {
+ "description": "Each radar that could not be read, and why.",
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "network",
+ "timeout",
+ "http",
+ "not_json",
+ "not_object",
+ "unexpected_shape"
+ ],
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "path": {
+ "type": "string"
+ },
+ "radar": {
+ "type": "string"
+ },
+ "status": {
+ "anyOf": [
+ {
+ "maximum": 9007199254740991,
+ "minimum": -9007199254740991,
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "radar",
+ "kind",
+ "path",
+ "status",
+ "message"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "status": {
+ "anyOf": [
+ {
+ "maximum": 9007199254740991,
+ "minimum": -9007199254740991,
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The HTTP status, when the API answered one."
+ }
+ },
+ "required": [
+ "kind",
+ "path",
+ "status",
+ "message"
+ ],
+ "type": "object"
+ },
+ "freshness": {
+ "type": "null"
+ },
+ "measured_at": {
+ "type": "null"
+ },
+ "method": {
+ "type": "null"
+ },
+ "notes": {
+ "description": "Caveats, one sentence each.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "state": {
+ "description": "failed: the lookup did not complete. invalid_input: the input was refused, so nothing was looked up. Neither is a finding.",
+ "enum": [
+ "failed",
+ "invalid_input"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "state",
+ "measured_at",
+ "method",
+ "coverage",
+ "freshness",
+ "notes",
+ "error"
+ ],
+ "type": "object"
+ }
+]