Files
rengine/shared/definitions/asset_query.py
T
Yogesh Ojha d781b4f6a8 fix(results): group and column menus fit the list
The option list takes the height the menu has instead of a fixed window.
Vulnerability grouping lists finding attributes first, then where they were
found.

Claude-Session: https://claude.ai/code/session_01VqBkGyjzd8GYYSSMaQHgMG
2026-09-26 21:34:03 +05:30

3351 lines
94 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from dataclasses import field as dc_field
from enum import StrEnum
from shared.definitions.ai_services import AI_FIELD, AI_MODEL_FIELD, AI_QUERY_VALUES
from shared.definitions.domain_posture import (
QUERY_VALUES as POSTURE_VALUES,
)
from shared.definitions.endpoints import (
ENDPOINT_CLASSES,
INTEREST_KEYS,
SOURCE_LABELS,
)
from shared.definitions.evidence import EVIDENCE_ORDER, Evidence
from shared.definitions.hygiene import (
QUERY_VALUES as HYGIENE_VALUES,
)
from shared.definitions.interest import (
BAND_ORDER,
KIND_KEYS,
)
from shared.definitions.interest import (
SOURCE_LABELS as INTEREST_SOURCE_LABELS,
)
from shared.definitions.ports import (
PORT_SOURCE_LABELS,
SERVICE_CLASS_LABELS,
)
from shared.definitions.secrets import (
DETECTOR_KEYS as SECRET_DETECTOR_KEYS,
)
from shared.definitions.secrets import (
GROUP_ORDER as SECRET_GROUP_KEYS,
)
from shared.definitions.secrets import (
STATE_ORDER as SECRET_STATE_KEYS,
)
from shared.definitions.secrets import (
SecretSource as _SecretSource,
)
from shared.definitions.software import (
CAVEAT_ORDER as CAVEAT_KEYS,
)
from shared.definitions.software import (
CONFIDENCE_ORDER as CONFIDENCE_KEYS,
)
from shared.definitions.software import (
VERSION_SOURCE_LABELS,
)
from shared.definitions.surface import SURFACE_NOUN, SurfaceDimension
from shared.definitions.threat_intel import SIGNAL_ORDER
from shared.definitions.vulnerabilities import (
PROTOCOLS,
SCANNER_LABELS,
SEVERITY_ORDER,
VULN_STATES,
)
SECRET_SOURCE_KEYS: tuple[str, ...] = tuple(v.value for v in _SecretSource)
MAX_QUERY_LENGTH = 2000
MAX_QUERY_NODES = 40
MAX_FREE_TERMS = 8
MAX_NUMBER = 2_147_483_647
COUNT_CAP = 10_000
SNIPPET_RADIUS = 70
SNIPPET_LENGTH = 190
class FieldType(StrEnum):
STRING = "string"
ENUM = "enum"
NUMBER = "number"
BYTES = "bytes"
DURATION = "duration"
DATE = "date"
IP = "ip"
TEXT = "text"
FLAG = "flag"
class Op(StrEnum):
MATCH = ":"
EQ = "="
NE = "!="
GT = ">"
GTE = ">="
LT = "<"
LTE = "<="
RE = "~"
NRE = "!~"
_STRING_OPS = (Op.MATCH, Op.EQ, Op.NE, Op.RE, Op.NRE)
_ENUM_OPS = (Op.MATCH, Op.EQ, Op.NE)
_SCALAR_OPS = (Op.MATCH, Op.EQ, Op.NE, Op.GT, Op.GTE, Op.LT, Op.LTE)
_TEXT_OPS = (Op.MATCH, Op.EQ, Op.NE)
OPS_BY_TYPE: dict[FieldType, tuple[Op, ...]] = {
FieldType.STRING: _STRING_OPS,
FieldType.ENUM: _ENUM_OPS,
FieldType.NUMBER: _SCALAR_OPS,
FieldType.BYTES: _SCALAR_OPS,
FieldType.DURATION: _SCALAR_OPS,
FieldType.DATE: _SCALAR_OPS,
FieldType.IP: _ENUM_OPS,
FieldType.TEXT: _TEXT_OPS,
FieldType.FLAG: (Op.MATCH,),
}
OP_HELP: dict[str, str] = {
":": "Contains, or equals for numbers and flags",
"=": "Exact match",
"!=": "Not equal",
">": "Greater than",
">=": "Greater than or equal",
"<": "Less than",
"<=": "Less than or equal",
"~": "Matches regular expression",
"!~": "Does not match regular expression",
}
CONNECTORS: dict[str, str] = {
"and": "Both sides must match. Two terms side by side mean and.",
"or": "Either side may match.",
"not": "Excludes what follows. A leading - or ! does the same.",
"( )": "Groups part of a query, as in is:live and (status:403 or status:401).",
'" "': "Treats the value as one literal phrase.",
"[a,b]": "Any of the listed values, as in tech:[nginx,apache].",
"a..b": "Inclusive range, as in status:200..399.",
}
@dataclass(frozen=True)
class QueryField:
name: str
type: FieldType
group: str
description: str
example: str
aliases: tuple[str, ...] = ()
values: tuple[str, ...] = ()
facet: str | None = None
free_text: bool = False
evidence: str | None = None
unit: str | None = None
dynamic_sub: str | None = None
GROUPS: tuple[str, ...] = (
"Scope",
"Host",
"HTTP",
"Response",
"Hygiene",
"AI",
"Network",
"Certificates",
"Findings",
"Flags",
)
CERT_STATES: tuple[str, ...] = ("expired", "expiring", "self-signed", "valid")
STATUS_CLASSES: tuple[str, ...] = ("2xx", "3xx", "4xx", "5xx", "none")
FLAGS: dict[str, str] = {
"live": "Responded with 2xx or 3xx",
"web": "Answered on HTTP at all",
"new": "Absent from the previous scan of this target",
"resolved": "Resolves to at least one IP",
"auth": "Login wall or 401/403",
"cdn": "Served through a CDN",
"cloud": "Hosted on a cloud provider's address",
"waf": "A WAF was fingerprinted",
"screenshot": "A screenshot was captured",
"important": "Flagged important",
"wildcard": "Matched a wildcard DNS record",
"issue": "Has a cert, exposure or availability problem",
"sensitive": "An admin or database port is open",
"http2": "Negotiated HTTP/2",
"redirect": "Final URL differs from the probed URL",
"vulnerable": "A vulnerability scan reported a finding on it",
"kev": "A known-exploited weakness was found on it",
"exposed": "Flagged as an exposure by a rule, a correlation or AI",
}
FIELDS: tuple[QueryField, ...] = (
QueryField(
name="target",
type=FieldType.STRING,
group="Scope",
description="Target this host belongs to.",
example="target:acme.com",
aliases=("scope",),
facet="target",
),
QueryField(
name="host",
type=FieldType.STRING,
group="Host",
description="Hostname of the asset.",
example="host:api",
aliases=("name", "subdomain"),
free_text=True,
evidence="host",
),
QueryField(
name="url",
type=FieldType.STRING,
group="Host",
description="Probed URL, or the final URL after redirects.",
example="url:/admin",
free_text=True,
evidence="url",
),
QueryField(
name="path",
type=FieldType.STRING,
group="Host",
description="Path component of the probed URL.",
example="path:/api/v1",
evidence="path",
),
QueryField(
name="cname",
type=FieldType.STRING,
group="Host",
description="CNAME target the host points at.",
example="cname:s3.amazonaws.com",
free_text=True,
evidence="cname",
),
QueryField(
name="source",
type=FieldType.ENUM,
group="Host",
description="Tool or feed that discovered the host.",
example="source:crtsh",
facet="source",
free_text=True,
evidence="source",
),
QueryField(
name="discovered",
type=FieldType.DATE,
group="Host",
description="When the host was first seen in this scan.",
example="discovered:<7d",
aliases=("found", "seen"),
),
QueryField(
name="status",
type=FieldType.NUMBER,
group="HTTP",
description="HTTP status code. Accepts a class such as 4xx.",
example="status:>=500",
aliases=("code", "http.status"),
values=STATUS_CLASSES,
),
QueryField(
name="title",
type=FieldType.STRING,
group="HTTP",
description="Page title of the response.",
example='title:"index of"',
free_text=True,
evidence="title",
),
QueryField(
name="server",
type=FieldType.STRING,
group="HTTP",
description="Server header banner.",
example="server:nginx",
aliases=("webserver",),
free_text=True,
evidence="server",
),
QueryField(
name="tech",
type=FieldType.ENUM,
group="HTTP",
description="Technology fingerprinted on the response.",
example="tech:[jenkins,tomcat]",
facet="tech",
free_text=True,
evidence="tech",
),
QueryField(
name="content_type",
type=FieldType.STRING,
group="HTTP",
description="Content-Type of the response.",
example="content_type:json",
aliases=("ctype", "mime"),
evidence="content_type",
),
QueryField(
name="size",
type=FieldType.BYTES,
group="HTTP",
description="Response body size. Understands kb and mb.",
example="size:>500kb",
aliases=("length", "content_length"),
unit="bytes",
),
QueryField(
name="words",
type=FieldType.NUMBER,
group="HTTP",
description="Word count of the response body.",
example="words:<20",
),
QueryField(
name="paths",
type=FieldType.NUMBER,
group="HTTP",
description="Number of endpoints discovered on the host.",
example="paths:>50",
aliases=("endpoints",),
),
QueryField(
name="lines",
type=FieldType.NUMBER,
group="HTTP",
description="Line count of the response body.",
example="lines:>2000",
),
QueryField(
name="time",
type=FieldType.DURATION,
group="HTTP",
description="Response time. Understands ms and s.",
example="time:>3s",
aliases=("rt", "response_time"),
unit="seconds",
),
QueryField(
name="redirect",
type=FieldType.STRING,
group="HTTP",
description="Location header the response redirects to.",
example="redirect:login",
aliases=("location",),
free_text=True,
evidence="redirect",
),
QueryField(
name="favicon",
type=FieldType.STRING,
group="HTTP",
description="mmh3 favicon hash.",
example="favicon:-1278323681",
free_text=True,
evidence="favicon",
),
QueryField(
name="screenshot",
type=FieldType.STRING,
group="HTTP",
description=(
"Perceptual hash of the rendered page. Matches every host whose "
"screenshot renders the same way."
),
example="screenshot:0030242430d41010",
aliases=("render",),
),
QueryField(
name="body",
type=FieldType.TEXT,
group="Response",
description=(
"Words in the captured response body. Matches whole words and "
"prefixes. Quote a value to match a phrase."
),
example='body:"internal server"',
aliases=("response",),
free_text=True,
evidence="body",
),
QueryField(
name="header",
type=FieldType.TEXT,
group="Response",
description=(
"Words in the response headers. Use header.<name>:<value> to pin "
"it to one header."
),
example="header.x-powered-by:php",
aliases=("headers",),
free_text=True,
evidence="header",
dynamic_sub="header name",
),
QueryField(
name="ip",
type=FieldType.IP,
group="Network",
description="Resolved IP address. Accepts a CIDR range.",
example="ip:10.0.0.0/8",
free_text=True,
evidence="ip",
),
QueryField(
name="asn",
type=FieldType.NUMBER,
group="Network",
description="Autonomous system number.",
example="asn:15169",
),
QueryField(
name="org",
type=FieldType.STRING,
group="Network",
description="Autonomous system owner.",
example='org:"digital ocean"',
aliases=("asn_org",),
free_text=True,
evidence="org",
),
QueryField(
name="cdn",
type=FieldType.STRING,
group="Network",
description="CDN in front of the host. yes or no filters on presence.",
example="cdn:cloudflare",
free_text=True,
evidence="cdn",
),
QueryField(
name="waf",
type=FieldType.STRING,
group="Network",
description="WAF fingerprinted on the host. yes or no filters on presence.",
example="waf:no",
free_text=True,
evidence="waf",
),
QueryField(
name="port",
type=FieldType.NUMBER,
group="Network",
description="Open port on a resolved IP.",
example="port:[22,3389]",
),
QueryField(
name="service",
type=FieldType.ENUM,
group="Network",
description="Service name on an open port.",
example="service:ssh",
facet="service",
),
QueryField(
name="hygiene",
type=FieldType.ENUM,
group="Hygiene",
description=(
"A hardening check the host fails on any of its responses. "
"Also takes warning, info, any or none."
),
example="hygiene:cookie_no_secure",
aliases=("hardening",),
values=HYGIENE_VALUES,
facet="hygiene",
),
QueryField(
name="posture",
type=FieldType.ENUM,
group="Hygiene",
description=(
"A domain posture check the host's zone fails: SPF, DMARC, DKIM, "
"MTA-STS, DNSSEC or CAA. Also takes warning, info, any or none."
),
example="posture:dmarc_missing",
aliases=("dns_posture", "mail"),
values=POSTURE_VALUES,
facet="posture",
),
QueryField(
name=AI_FIELD,
type=FieldType.ENUM,
group="AI",
description=(
"AI service identified on any of the host's web assets. "
"Takes a service, a category, yes or no."
),
example="ai:ollama",
aliases=("ai_service",),
values=AI_QUERY_VALUES,
),
QueryField(
name=AI_MODEL_FIELD,
type=FieldType.STRING,
group="AI",
description="Model an AI service on the host listed. yes or no filters on presence.",
example="ai.model~llama",
),
QueryField(
name="cert",
type=FieldType.ENUM,
group="Certificates",
description="Certificate state.",
example="cert:expired",
values=CERT_STATES,
),
QueryField(
name="cert.cn",
type=FieldType.STRING,
group="Certificates",
description="Certificate subject common name.",
example="cert.cn:*.internal",
free_text=True,
evidence="cert",
),
QueryField(
name="cert.san",
type=FieldType.STRING,
group="Certificates",
description="Subject alternative name on the certificate.",
example="cert.san:staging",
free_text=True,
evidence="cert",
),
QueryField(
name="cert.issuer",
type=FieldType.STRING,
group="Certificates",
description="Certificate issuer.",
example='cert.issuer:"let\'s encrypt"',
free_text=True,
evidence="cert",
),
QueryField(
name="cert.expires",
type=FieldType.DATE,
group="Certificates",
description="Certificate expiry date.",
example="cert.expires:<30d",
),
QueryField(
name="tls.version",
type=FieldType.STRING,
group="Certificates",
description="Negotiated TLS version.",
example="tls.version:tls10",
),
QueryField(
name="content_hash",
type=FieldType.STRING,
group="Response",
description="Hash of the response body.",
example="content_hash:8f4e2a",
aliases=("body_hash",),
),
QueryField(
name="tracking",
type=FieldType.STRING,
group="Response",
description="Analytics, tag manager or advertising account ID.",
example="tracking=UA-58851320",
),
QueryField(
name="jarm",
type=FieldType.STRING,
group="Certificates",
description="JARM TLS fingerprint.",
example="jarm:29d3fd00029d29d0",
),
QueryField(
name="cert.fingerprint",
type=FieldType.STRING,
group="Certificates",
description="Hash of the certificate.",
example="cert.fingerprint:9f2b1c",
aliases=("cert.hash",),
),
QueryField(
name="header_hash",
type=FieldType.STRING,
group="Response",
description="Hash of the response header set.",
example="header_hash:3ab91c",
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the host.",
example="is:live",
aliases=("has",),
values=tuple(FLAGS),
),
QueryField(
name="vuln",
type=FieldType.ENUM,
group="Findings",
description="Severity of a finding recorded on this host.",
example="vuln:critical",
aliases=("finding", "severity"),
values=SEVERITY_ORDER,
facet="vuln",
),
QueryField(
name="cve",
type=FieldType.STRING,
group="Findings",
description="Published vulnerability identifier reported on this host.",
example="cve:CVE-2021-44228",
),
QueryField(
name="exposure",
type=FieldType.ENUM,
group="Findings",
description="The reason this host was flagged as an exposure.",
example="exposure:admin_interface",
aliases=("interest", "worth"),
values=KIND_KEYS,
facet="interest",
),
QueryField(
name="flagged",
type=FieldType.ENUM,
group="Findings",
description="The source of the flag: a rule, a correlation, or AI.",
example="flagged:ai",
values=tuple(INTEREST_SOURCE_LABELS),
facet="flagged",
),
QueryField(
name="exposure_band",
type=FieldType.ENUM,
group="Findings",
description="Strength of the exposure on this host.",
example="exposure_band:critical",
aliases=("interest_band", "band"),
values=BAND_ORDER,
),
)
EVIDENCE_LABELS: dict[str, str] = {
"host": "Hostname",
"url": "URL",
"path": "Path",
"cname": "CNAME",
"source": "Source",
"title": "Title",
"server": "Server",
"tech": "Tech",
"content_type": "Content type",
"redirect": "Redirect",
"favicon": "Favicon",
"screenshot": "Rendered page",
"body": "Response body",
"header": "Response headers",
"ip": "IP",
"org": "Network owner",
"cdn": "CDN",
"waf": "WAF",
"cert": "Certificate",
}
@dataclass(frozen=True)
class GroupDimension:
key: str
label: str
description: str
GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="target",
label="Target",
description="Rows belonging to the same target",
),
GroupDimension(
key="ip",
label="IP address",
description="Names sharing a resolved address",
),
GroupDimension(
key="favicon",
label="Favicon",
description="Names serving the same favicon",
),
GroupDimension(
key="screenshot",
label="Rendered page",
description="Names whose screenshots render the same page",
),
GroupDimension(
key="title",
label="Page title",
description="Names serving the same page title",
),
GroupDimension(
key="cname",
label="CNAME target",
description="Names pointing at the same alias",
),
GroupDimension(
key="tech",
label="Technology",
description="Names running the same technology",
),
GroupDimension(
key="content_hash",
label="Identical content",
description="Names serving a byte-identical response",
),
GroupDimension(
key="jarm",
label="TLS fingerprint",
description="Names sharing a JARM TLS fingerprint",
),
GroupDimension(
key="cert.fingerprint",
label="Certificate",
description="Names presenting the same certificate",
),
GroupDimension(
key="cert.issuer",
label="Certificate issuer",
description="Names whose certificate has the same issuer",
),
GroupDimension(
key="header_hash",
label="Header set",
description="Names returning an identical set of response headers",
),
GroupDimension(
key="server",
label="Server banner",
description="Names reporting the same server header",
),
GroupDimension(
key="cdn",
label="CDN",
description="Names fronted by the same CDN",
),
GroupDimension(
key="status",
label="Status class",
description="Names by HTTP response class",
),
GroupDimension(
key="hygiene",
label="Hygiene check",
description="Names failing the same hardening check",
),
GroupDimension(
key="posture",
label="Posture check",
description="Names whose zone fails the same posture check",
),
)
RENDER_SAMPLE_HOSTS = 12
MAX_GROUPS = 50
EXAMPLE_GROUPS: tuple[str, ...] = (
"Takeover risk",
"Exposed services",
"Non-production",
"Access control",
"Certificates",
"Origin exposure",
"Hygiene",
"Change",
"Health",
"Exposures",
)
@dataclass(frozen=True)
class QueryExample:
query: str
description: str
group: str
generic: bool = False
EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="is:resolved and not is:web",
description="Resolving hosts with no HTTP service",
group="Takeover risk",
generic=True,
),
QueryExample(
query="cname:. and not is:web",
description="CNAME records with no HTTP service",
group="Takeover risk",
),
QueryExample(
query="cname:elb.amazonaws.com and status:404",
description="Load balancer aliases returning 404",
group="Takeover risk",
),
QueryExample(
query=(
"cname:[myshopify.com,github.io,herokuapp.com,azurewebsites.net,"
"netlify.app,pantheonsite.io,wpengine.com,ghost.io]"
),
description="Aliases pointing to third-party hosting",
group="Takeover risk",
),
QueryExample(
query="cname:. and not is:resolved",
description="CNAME records that fail to resolve",
group="Takeover risk",
),
QueryExample(
query="is:wildcard",
description="Hosts matched only by a wildcard record",
group="Takeover risk",
),
QueryExample(
query="is:sensitive",
description="Administrative or database ports exposed",
group="Exposed services",
),
QueryExample(
query="port:[3306,5432,27017,6379,9200,11211,5984]",
description="Database ports reachable from the internet",
group="Exposed services",
),
QueryExample(
query="port:[3306,5432,1433,27017,6379,9200,11211,5984] and hosts:>10",
description="Database ports on addresses shared by more than ten hostnames",
group="Exposed services",
),
QueryExample(
query="port:[22,23,3389,5900]",
description="Remote administration ports reachable",
group="Exposed services",
),
QueryExample(
query='body:"index of" and status:200',
description="Directory listings enabled",
group="Exposed services",
),
QueryExample(
query=(
'body:"begin rsa private key" or body:"begin private key" '
"or body:aws_secret_access_key"
),
description="Private key material in a response body",
group="Exposed services",
),
QueryExample(
query='body:traceback or body:"stack trace" or title:exception',
description="Application stack traces in responses",
group="Exposed services",
),
QueryExample(
query='body:"sql syntax" or body:"odbc driver"',
description="Database error messages in responses",
group="Exposed services",
),
QueryExample(
query="title:phpinfo or body:phpinfo",
description="phpinfo diagnostic pages exposed",
group="Exposed services",
),
QueryExample(
query="body:swagger or url:swagger or title:swagger",
description="API documentation publicly accessible",
group="Exposed services",
),
QueryExample(
query=(
"tech:[jenkins,gitlab,grafana,kibana,prometheus,sonarqube,jira,"
"confluence] and status:200"
),
description="Developer tooling exposed to the internet",
group="Exposed services",
),
QueryExample(
query="host:[jenkins,gitlab,grafana,kibana,jira,confluence,vault,consul]",
description="Hostnames indicating internal tooling",
group="Exposed services",
),
QueryExample(
query="content_type:json and status:200",
description="JSON endpoints served directly",
group="Exposed services",
),
QueryExample(
query="host:[staging,dev,test,uat,qa,sandbox,preprod,demo]",
description="Non-production hostnames publicly resolvable",
group="Non-production",
generic=True,
),
QueryExample(
query=(
"host:[staging,dev,test,uat,qa,sandbox,preprod] and is:live and not is:auth"
),
description="Non-production hosts served without authentication",
group="Non-production",
),
QueryExample(
query="(host:admin or host:portal or host:console) and is:live and not is:auth",
description="Administrative interfaces without authentication",
group="Non-production",
),
QueryExample(
query="is:auth and is:live",
description="Authenticated interfaces currently serving",
group="Access control",
),
QueryExample(
query="is:auth",
description="Hosts requiring authentication",
group="Access control",
),
QueryExample(
query="header.access-control-allow-origin:*",
description="Wildcard cross-origin policy on responses",
group="Access control",
),
QueryExample(
query="cert:expired and is:live",
description="Reachable hosts serving an expired certificate",
group="Certificates",
),
QueryExample(
query="cert:expiring",
description="Certificates expiring within 30 days",
group="Certificates",
generic=True,
),
QueryExample(
query="cert:self-signed",
description="Self-signed certificates in use",
group="Certificates",
),
QueryExample(
query="cert.cn:internal or cert.san:internal or cert.san:local",
description="Internal hostnames disclosed in certificates",
group="Certificates",
),
QueryExample(
query="cert.san:*. and is:live",
description="Wildcard certificates in use",
group="Certificates",
),
QueryExample(
query="tls.version:[tls10,tls11]",
description="Obsolete TLS versions negotiated",
group="Certificates",
),
QueryExample(
query="is:live and not cdn:yes and waf:no",
description="Hosts served without CDN or WAF",
group="Origin exposure",
generic=True,
),
QueryExample(
query="server:[apache,nginx,iis] and not cdn:yes",
description="Origin server banners disclosed",
group="Origin exposure",
),
QueryExample(
query="tech:[php,tomcat,jboss,weblogic,coldfusion] and status:200",
description="Runtimes with significant CVE history",
group="Origin exposure",
),
QueryExample(
query="hygiene:warning",
description="Hosts failing a warning-level hardening check",
group="Hygiene",
generic=True,
),
QueryExample(
query="hygiene:cors_credentials",
description="CORS credentials allowed with a wildcard or null origin",
group="Hygiene",
),
QueryExample(
query="hygiene:cookie_no_secure",
description="Cookies set over https without Secure",
group="Hygiene",
),
QueryExample(
query="hygiene:cookie_no_httponly",
description="Session cookies readable by scripts",
group="Hygiene",
),
QueryExample(
query="hygiene:no_frame_protection and is:live",
description="Pages that can be framed by another site",
group="Hygiene",
),
QueryExample(
query="hygiene:[csp_unsafe_inline,csp_wildcard_script]",
description="Content-Security-Policy that does not restrict scripts",
group="Hygiene",
),
QueryExample(
query="hygiene:no_https_redirect",
description="Plaintext http with no redirect to https",
group="Hygiene",
),
QueryExample(
query="hygiene:[server_version,runtime_disclosed] and not cdn:yes",
description="Origin software versions disclosed in headers",
group="Hygiene",
),
QueryExample(
query="hygiene:none and is:live",
description="Reachable hosts passing every hardening check",
group="Hygiene",
),
QueryExample(
query="is:new and (status:2xx or status:3xx)",
description="Newly reachable since the previous scan",
group="Change",
generic=True,
),
QueryExample(
query="is:new and is:auth",
description="New authenticated interfaces since the previous scan",
group="Change",
),
QueryExample(
query="is:new and cert:expired",
description="New hosts serving an expired certificate",
group="Change",
),
QueryExample(
query="is:new",
description="Hosts absent from the previous scan",
group="Change",
),
QueryExample(
query="discovered:<7d and is:live",
description="Reachable hosts discovered in the last 7 days",
group="Change",
),
QueryExample(
query="status:>=500 or title:exception",
description="Server-side errors returned",
group="Health",
generic=True,
),
QueryExample(
query="not is:resolved",
description="Hostnames that no longer resolve",
group="Health",
),
QueryExample(
query="size:<500 and status:200",
description="Minimal-content responses returning 200",
group="Health",
),
QueryExample(
query="time:>5s",
description="Responses slower than 5 seconds",
group="Health",
),
QueryExample(
query="is:redirect and not is:live",
description="Redirects terminating in an error",
group="Health",
),
QueryExample(
query="is:exposed",
description="Everything flagged as an exposure",
group="Exposures",
generic=True,
),
QueryExample(
query="is:exposed and is:new",
description="Flagged for the first time by this scan",
group="Exposures",
),
QueryExample(
query="flagged:ai",
description="Flagged by AI judgement",
group="Exposures",
),
QueryExample(
query="exposure_band:critical",
description="Exposures in the critical band",
group="Exposures",
),
QueryExample(
query="exposure:admin_interface and not is:auth",
description="Administrative surfaces answering without a login",
group="Exposures",
),
)
@dataclass(frozen=True)
class QueryRegistry:
key: str
noun: str
noun_plural: str
fields: tuple[QueryField, ...]
flags: dict[str, str]
groups: tuple[str, ...]
dimensions: tuple[GroupDimension, ...]
examples: tuple[QueryExample, ...]
example_groups: tuple[str, ...]
by_name: dict[str, QueryField] = dc_field(init=False, repr=False)
canonical: dict[str, str] = dc_field(init=False, repr=False)
dynamic_parent: str | None = dc_field(init=False, repr=False)
def __post_init__(self) -> None:
set_ = object.__setattr__
set_(self, "by_name", {f.name: f for f in self.fields})
set_(
self,
"canonical",
{
**{f.name: f.name for f in self.fields},
**{a: f.name for f in self.fields for a in f.aliases},
},
)
dynamic = next((f.name for f in self.fields if f.dynamic_sub), None)
set_(self, "dynamic_parent", dynamic)
HOST_QUERY = QueryRegistry(
key="host",
noun=SURFACE_NOUN[SurfaceDimension.WEB_ASSETS.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.WEB_ASSETS.value][1],
fields=FIELDS,
flags=FLAGS,
groups=GROUPS,
dimensions=GROUP_DIMENSIONS,
examples=EXAMPLES,
example_groups=EXAMPLE_GROUPS,
)
IP_GROUPS: tuple[str, ...] = (
"Scope",
"Address",
"Network",
"Services",
"Hosts",
"Findings",
"Flags",
)
IP_FLAGS: dict[str, str] = {
"new": "Not recorded by an earlier scan of this target",
"alive": "Responded to a probe",
"open": "One or more ports are open",
"sensitive": "An admin or database port is open",
"hosted": "One or more hostnames resolve to it",
"web": "An HTTP service was probed on it",
"cdn": "Fronted by a CDN",
"ptr": "Has a PTR record",
"private": "In a private address range",
"v4": "IPv4 address",
"v6": "IPv6 address",
"vulnerable": "A vulnerability scan reported a finding on it",
"kev": "A known-exploited weakness was found on it",
}
IP_EXPOSURE: dict[str, str] = {
"open": "Open ports",
"responding": "Responding",
"quiet": "No response",
}
IP_FIELDS: tuple[QueryField, ...] = (
QueryField(
name="target",
type=FieldType.STRING,
group="Scope",
description="Target this address belongs to.",
example="target:acme.com",
aliases=("scope",),
facet="target",
),
QueryField(
name="ip",
type=FieldType.IP,
group="Address",
description="IP address. Accepts a CIDR range.",
example="ip:104.16.0.0/12",
aliases=("address", "addr"),
free_text=True,
),
QueryField(
name="ptr",
type=FieldType.STRING,
group="Address",
description="PTR record for the address.",
example="ptr:.internal",
aliases=("reverse", "rdns"),
free_text=True,
),
QueryField(
name="asn",
type=FieldType.NUMBER,
group="Network",
description="Autonomous system number announcing the address.",
example="asn:13335",
),
QueryField(
name="org",
type=FieldType.STRING,
group="Network",
description="Operator of the autonomous system.",
example='org:"digital ocean"',
aliases=("asn_org", "operator"),
free_text=True,
),
QueryField(
name="country",
type=FieldType.ENUM,
group="Network",
description="Two-letter registered country of the network block.",
example="country:DE",
facet="country",
),
QueryField(
name="prefix",
type=FieldType.STRING,
group="Network",
description="Announced prefix containing the address.",
example="prefix:104.16",
free_text=True,
),
QueryField(
name="cdn",
type=FieldType.STRING,
group="Network",
description="CDN in front of the address. yes or no filters on presence.",
example="cdn:cloudflare",
free_text=True,
),
QueryField(
name="port",
type=FieldType.NUMBER,
group="Services",
description="Open port on the address.",
example="port:[22,3389]",
facet="port",
),
QueryField(
name="service",
type=FieldType.ENUM,
group="Services",
description="Service name on an open port.",
example="service:ssh",
facet="service",
free_text=True,
),
QueryField(
name="ports",
type=FieldType.NUMBER,
group="Services",
description="Number of open ports on the address.",
example="ports:>10",
aliases=("port_count",),
),
QueryField(
name="host",
type=FieldType.STRING,
group="Hosts",
description="Hostname that resolves to the address.",
example="host:api",
aliases=("name", "subdomain"),
free_text=True,
),
QueryField(
name="hosts",
type=FieldType.NUMBER,
group="Hosts",
description="Number of hostnames resolving to the address.",
example="hosts:>5",
aliases=("host_count",),
),
QueryField(
name="assets",
type=FieldType.NUMBER,
group="Hosts",
description="Number of HTTP services probed on the address.",
example="assets:>0",
aliases=("asset_count",),
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the address.",
example="is:sensitive",
aliases=("has",),
values=tuple(IP_FLAGS),
),
QueryField(
name="vuln",
type=FieldType.ENUM,
group="Findings",
description="Severity of a finding recorded on this address.",
example="vuln:critical",
aliases=("finding",),
values=SEVERITY_ORDER,
),
QueryField(
name="cve",
type=FieldType.STRING,
group="Findings",
description="Published vulnerability identifier reported on this address.",
example="cve:CVE-2021-44228",
),
)
IP_GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="target",
label="Target",
description="Rows belonging to the same target",
),
GroupDimension(
key="asn",
label="Autonomous system",
description="Addresses announced by the same AS",
),
GroupDimension(
key="org",
label="Network operator",
description="Addresses run by the same operator",
),
GroupDimension(
key="prefix",
label="Announced prefix",
description="Addresses inside the same announced prefix",
),
GroupDimension(
key="country",
label="Country",
description="Addresses registered in the same country",
),
GroupDimension(
key="cdn",
label="CDN",
description="Addresses fronted by the same CDN",
),
GroupDimension(
key="port",
label="Open port",
description="Addresses exposing the same port",
),
GroupDimension(
key="service",
label="Service",
description="Addresses running the same service",
),
)
IP_EXAMPLE_GROUPS: tuple[str, ...] = (
"Exposed services",
"Origin exposure",
"Hosting",
"Hygiene",
"Change",
)
IP_EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="is:sensitive",
description="Administrative or database ports exposed",
group="Exposed services",
generic=True,
),
QueryExample(
query="is:new",
description="Addresses not recorded by the previous scan",
group="Change",
generic=True,
),
QueryExample(
query="is:new and is:sensitive",
description="Administrative ports on addresses that are new this scan",
group="Change",
),
QueryExample(
query="port:[3306,5432,27017,6379,9200,11211,5984]",
description="Database ports reachable from the internet",
group="Exposed services",
),
QueryExample(
query="port:[22,23,3389,5900]",
description="Remote administration ports reachable",
group="Exposed services",
),
QueryExample(
query="service:[ftp,telnet,rlogin,vnc]",
description="Cleartext remote-access services running",
group="Exposed services",
),
QueryExample(
query="ports:>10",
description="Addresses exposing more than ten ports",
group="Exposed services",
),
QueryExample(
query="is:open and not is:cdn",
description="Open ports on addresses without a CDN",
group="Origin exposure",
generic=True,
),
QueryExample(
query="is:web and not is:cdn",
description="HTTP served directly from the origin address",
group="Origin exposure",
),
QueryExample(
query="is:cdn and is:open",
description="Open ports on CDN-fronted addresses",
group="Origin exposure",
),
QueryExample(
query="hosts:>10",
description="Addresses shared by more than ten hostnames",
group="Hosting",
generic=True,
),
QueryExample(
query="is:open and not is:hosted",
description="Open ports on addresses with no hostname",
group="Hosting",
),
QueryExample(
query="is:v6",
description="IPv6 addresses in the attack surface",
group="Hosting",
),
QueryExample(
query="is:alive and not is:ptr",
description="Responding addresses with no PTR record",
group="Hosting",
),
QueryExample(
query="is:private",
description="Private addresses published in public DNS",
group="Hygiene",
generic=True,
),
QueryExample(
query="is:hosted and not is:alive",
description="Hostnames resolving to an unresponsive address",
group="Hygiene",
),
QueryExample(
query="ptr:. and not is:hosted",
description="Addresses with a PTR record but no known hostname",
group="Hygiene",
),
)
IP_QUERY = QueryRegistry(
key="ip",
noun=SURFACE_NOUN[SurfaceDimension.IPS.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.IPS.value][1],
fields=IP_FIELDS,
flags=IP_FLAGS,
groups=IP_GROUPS,
dimensions=IP_GROUP_DIMENSIONS,
examples=IP_EXAMPLES,
example_groups=IP_EXAMPLE_GROUPS,
)
SERVICE_GROUPS: tuple[str, ...] = (
"Scope",
"Service",
"Software",
"Address",
"Hosts",
"Findings",
"Flags",
)
SERVICE_FLAGS: dict[str, str] = {
"new": "Not open in an earlier scan of this target",
"http": "Answered an HTTP request",
"tls": "Negotiates TLS",
"sensitive": "An administrative or datastore port",
"named": "Software identified",
"passive": "Reported by an internet-wide scanner, not confirmed by this scan",
"confirmed": "Observed directly by this scan",
"cdn": "On a CDN-fronted address",
"hosted": "A hostname resolves to the address",
"private": "In a private address range",
"v4": "IPv4 address",
"v6": "IPv6 address",
"vulnerable": "A vulnerability scan reported a finding on it",
"kev": "A known-exploited weakness was found on it",
}
SERVICE_EXPOSURE: dict[str, str] = dict(SERVICE_CLASS_LABELS)
SERVICE_FIELDS: tuple[QueryField, ...] = (
QueryField(
name="target",
type=FieldType.STRING,
group="Scope",
description="Target this service belongs to.",
example="target:acme.com",
aliases=("scope",),
facet="target",
),
QueryField(
name="port",
type=FieldType.NUMBER,
group="Service",
description="Port number. Accepts a list or a range.",
example="port:[22,3389]",
facet="port",
),
QueryField(
name="service",
type=FieldType.STRING,
group="Service",
description="Service running on the port.",
example="service:ssh",
facet="service",
free_text=True,
),
QueryField(
name="class",
type=FieldType.ENUM,
group="Service",
description="Service class.",
example="class:database",
aliases=("kind",),
values=tuple(SERVICE_EXPOSURE),
facet="class",
),
QueryField(
name="protocol",
type=FieldType.ENUM,
group="Service",
description="Transport protocol.",
example="protocol:tcp",
values=("tcp", "udp"),
),
QueryField(
name="source",
type=FieldType.ENUM,
group="Service",
description="The source that observed the service.",
example="source:naabu",
values=tuple(PORT_SOURCE_LABELS),
facet="source",
),
QueryField(
name="product",
type=FieldType.STRING,
group="Software",
description="Software named by the service banner.",
example="product:OpenSSH",
free_text=True,
),
QueryField(
name="version",
type=FieldType.STRING,
group="Software",
description="Version reported by the service.",
example="version:8.9",
free_text=True,
),
QueryField(
name="banner",
type=FieldType.TEXT,
group="Software",
description="Text the service returned on connect.",
example="banner:ubuntu",
free_text=True,
),
QueryField(
name=AI_FIELD,
type=FieldType.ENUM,
group="Software",
description=(
"AI service identified on the port. Takes a service, a category, yes or no."
),
example="ai:ollama",
aliases=("ai_service",),
values=AI_QUERY_VALUES,
),
QueryField(
name=AI_MODEL_FIELD,
type=FieldType.STRING,
group="Software",
description="Model an AI service on the port listed. yes or no filters on presence.",
example="ai.model~llama",
),
QueryField(
name="ip",
type=FieldType.IP,
group="Address",
description="Address the service listens on. Accepts a CIDR range.",
example="ip:104.16.0.0/12",
aliases=("address", "addr"),
free_text=True,
),
QueryField(
name="asn",
type=FieldType.NUMBER,
group="Address",
description="Autonomous system announcing the address.",
example="asn:13335",
facet="asn",
),
QueryField(
name="org",
type=FieldType.STRING,
group="Address",
description="Operator of the announcing network.",
example="org:cloudflare",
aliases=("as_name", "asn_org"),
free_text=True,
),
QueryField(
name="country",
type=FieldType.ENUM,
group="Address",
description="Two-letter country of the address.",
example="country:DE",
facet="country",
),
QueryField(
name="cdn",
type=FieldType.STRING,
group="Address",
description="CDN in front of the address. yes or no filters on presence.",
example="cdn:cloudflare",
free_text=True,
),
QueryField(
name="host",
type=FieldType.STRING,
group="Hosts",
description="Hostname resolving to the address.",
example="host:api",
aliases=("name", "subdomain"),
free_text=True,
),
QueryField(
name="status",
type=FieldType.NUMBER,
group="Hosts",
description="HTTP status returned on this port.",
example="status:200",
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the service.",
example="is:sensitive",
aliases=("has",),
values=tuple(SERVICE_FLAGS),
),
QueryField(
name="vuln",
type=FieldType.ENUM,
group="Findings",
description="Severity of a finding recorded on this service.",
example="vuln:critical",
aliases=("finding",),
values=SEVERITY_ORDER,
),
QueryField(
name="cve",
type=FieldType.STRING,
group="Findings",
description="Published vulnerability identifier reported on this service.",
example="cve:CVE-2021-44228",
),
)
SERVICE_GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="target",
label="Target",
description="Rows belonging to the same target",
),
GroupDimension(
key="service",
label="Service",
description="Ports running the same service",
),
GroupDimension(
key="class",
label="Service class",
description="Services of the same class",
),
GroupDimension(
key="port",
label="Port",
description="Services on the same port number",
),
GroupDimension(
key="product",
label="Software",
description="Services running the same software",
),
GroupDimension(
key="ip",
label="Address",
description="Services on the same address",
),
GroupDimension(
key="asn",
label="Autonomous system",
description="Services in the same announced network",
),
GroupDimension(
key="org",
label="Network operator",
description="Services on the same operator's network",
),
GroupDimension(
key="country",
label="Country",
description="Services in the same country",
),
GroupDimension(
key="source",
label="Evidence",
description="Services with the same evidence",
),
)
SERVICE_EXAMPLE_GROUPS: tuple[str, ...] = (
"Critical exposure",
"Change",
"Remote access",
"Web surface",
"Evidence",
"Hygiene",
)
SERVICE_EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="class:database",
description="Data stores reachable from the internet",
group="Critical exposure",
generic=True,
),
QueryExample(
query="service:[redis,memcached,mongodb,elasticsearch]",
description="Data stores with no authentication by default",
group="Critical exposure",
),
QueryExample(
query="service:docker",
description="Container runtime API exposed",
group="Critical exposure",
),
QueryExample(
query="is:sensitive and not is:cdn",
description="Administrative ports on an origin address",
group="Critical exposure",
),
QueryExample(
query="is:new",
description="Ports that were closed at the previous scan",
group="Change",
generic=True,
),
QueryExample(
query="is:new and is:sensitive",
description="Administrative ports opened since the previous scan",
group="Change",
),
QueryExample(
query="class:remote",
description="Interactive administration reachable",
group="Remote access",
generic=True,
),
QueryExample(
query="service:ssh and not port:22",
description="SSH on a non-standard port",
group="Remote access",
),
QueryExample(
query="service:[telnet,ftp,rlogin,rexec]",
description="Protocols that carry credentials in the clear",
group="Remote access",
),
QueryExample(
query="service:rdp or service:vnc",
description="Remote desktop reachable from the internet",
group="Remote access",
),
QueryExample(
query="is:http and not port:[80,443]",
description="Web services on a non-standard port",
group="Web surface",
generic=True,
),
QueryExample(
query="is:http and not is:tls",
description="Web services without TLS",
group="Web surface",
),
QueryExample(
query="class:web and not is:http",
description="Web ports with no HTTP response",
group="Web surface",
),
QueryExample(
query="status:401 or status:403",
description="Services requiring authentication",
group="Web surface",
),
QueryExample(
query="is:passive",
description="Ports reported by external scanners, not confirmed",
group="Evidence",
generic=True,
),
QueryExample(
query="is:named",
description="Services with identified software",
group="Evidence",
generic=True,
),
QueryExample(
query="is:confirmed and not is:named",
description="Open ports with no service identified",
group="Evidence",
),
QueryExample(
query="class:mail and not is:tls",
description="Mail services without transport encryption",
group="Hygiene",
),
QueryExample(
query="not is:hosted",
description="Services on addresses with no known hostname",
group="Hygiene",
),
QueryExample(
query="class:infra",
description="Directory, file and management services",
group="Hygiene",
generic=True,
),
)
SERVICE_QUERY = QueryRegistry(
key="service",
noun=SURFACE_NOUN[SurfaceDimension.SERVICES.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.SERVICES.value][1],
fields=SERVICE_FIELDS,
flags=SERVICE_FLAGS,
groups=SERVICE_GROUPS,
dimensions=SERVICE_GROUP_DIMENSIONS,
examples=SERVICE_EXAMPLES,
example_groups=SERVICE_EXAMPLE_GROUPS,
)
VULN_GROUPS: tuple[str, ...] = (
"Scope",
"Finding",
"Classification",
"Asset",
"Review",
"Flags",
)
VULN_FLAGS: dict[str, str] = {
"new": "Not reported by an earlier scan of this target",
"kev": "Listed in CISA KEV",
"ransomware": "Listed in CISA ransomware campaigns",
"overdue": "Past the CISA remediation deadline",
"weaponised": "A public exploit is published",
"untestable": "No scanner template covers this CVE",
"cve": "Carries a published vulnerability identifier",
"exploitable": "Known exploited, or above the EPSS threshold",
"corroborated": "A second signal at the same location names the same CVE or weakness class",
"proven": "The asset produced an out-of-band interaction",
"recorded": "The request and response that produced it were stored",
"extracted": "The check pulled a value out of the response",
"web": "Found on an HTTP asset",
"cdn": "On an asset served through a CDN",
"triaged": "Reviewed",
"open": "Not reviewed",
"suppressed": "Reviewed and set aside as a false positive or accepted risk",
}
VULN_FIELDS: tuple[QueryField, ...] = (
QueryField(
name="target",
type=FieldType.STRING,
group="Scope",
description="Target this finding belongs to.",
example="target:acme.com",
aliases=("scope",),
facet="target",
),
QueryField(
name="name",
type=FieldType.STRING,
group="Finding",
description="Title of the check that fired.",
example="name:log4j",
aliases=("title",),
free_text=True,
evidence="name",
),
QueryField(
name="template",
type=FieldType.STRING,
group="Finding",
description="Identifier of the check that fired.",
example="template:CVE-2021-44228",
aliases=("check", "rule", "id"),
facet="template",
free_text=True,
evidence="template",
),
QueryField(
name="severity",
type=FieldType.ENUM,
group="Finding",
description="Severity the check declares.",
example="severity:[critical,high]",
aliases=("sev",),
values=SEVERITY_ORDER,
facet="severity",
),
QueryField(
name="type",
type=FieldType.ENUM,
group="Finding",
description="Protocol the check speaks.",
example="type:ssl",
aliases=("protocol",),
values=PROTOCOLS,
facet="protocol",
),
QueryField(
name="scanner",
type=FieldType.ENUM,
group="Finding",
description="Tool that produced the finding.",
example="scanner:nuclei",
values=tuple(SCANNER_LABELS),
facet="scanner",
),
QueryField(
name="tag",
type=FieldType.STRING,
group="Finding",
description="Tag carried by the check.",
example="tag:takeover",
aliases=("tags",),
facet="tag",
),
QueryField(
name="matcher",
type=FieldType.STRING,
group="Finding",
description="Named matcher inside the check that fired.",
example="matcher:unauthenticated",
),
QueryField(
name="extracted",
type=FieldType.STRING,
group="Finding",
description="Value the check pulled out of the response.",
example="extracted:admin",
free_text=True,
evidence="extracted",
),
QueryField(
name="author",
type=FieldType.STRING,
group="Finding",
description="Author of the check.",
example="author:pdteam",
),
QueryField(
name="exploit",
type=FieldType.NUMBER,
group="Classification",
description="Exploitation rank, 0-100, from the signals behind the finding.",
example="exploit:>=80",
aliases=("rank",),
),
QueryField(
name="poc",
type=FieldType.NUMBER,
group="Classification",
description="Number of published proof-of-concept exploits.",
example="poc:>0",
aliases=("exploits",),
),
QueryField(
name="signal",
type=FieldType.ENUM,
group="Classification",
description="Exploitation signal carried by the finding.",
example="signal:ransom_path",
values=SIGNAL_ORDER,
facet="signal",
),
QueryField(
name="cve",
type=FieldType.STRING,
group="Classification",
description="Published vulnerability identifier.",
example="cve:CVE-2021-44228",
facet="cve",
free_text=True,
evidence="cve",
),
QueryField(
name="cwe",
type=FieldType.STRING,
group="Classification",
description="Weakness class.",
example="cwe:CWE-89",
),
QueryField(
name="cvss",
type=FieldType.NUMBER,
group="Classification",
description="CVSS base score.",
example="cvss>=9",
),
QueryField(
name="epss",
type=FieldType.NUMBER,
group="Classification",
description="Probability of exploitation in the next 30 days, 0 to 1.",
example="epss>=0.5",
),
QueryField(
name="host",
type=FieldType.STRING,
group="Asset",
description="Hostname the finding sits on.",
example="host:api",
aliases=("name.host", "subdomain"),
facet="host",
free_text=True,
evidence="host",
),
QueryField(
name="location",
type=FieldType.STRING,
group="Asset",
description="Exact place the check matched.",
example="location:/actuator",
aliases=("matched", "url", "path"),
free_text=True,
evidence="location",
),
QueryField(
name="ip",
type=FieldType.IP,
group="Asset",
description="Address behind the finding. Accepts a CIDR range.",
example="ip:104.16.0.0/12",
aliases=("address", "addr"),
),
QueryField(
name="port",
type=FieldType.NUMBER,
group="Asset",
description="Port the finding is on.",
example="port:8080",
),
QueryField(
name="status",
type=FieldType.NUMBER,
group="Asset",
description="HTTP status the asset returned to this scan.",
example="status:200",
),
QueryField(
name="tech",
type=FieldType.STRING,
group="Asset",
description="Technology fingerprinted on the asset.",
example="tech:nginx",
),
QueryField(
name="asn",
type=FieldType.NUMBER,
group="Asset",
description="Autonomous system announcing the address.",
example="asn:13335",
),
QueryField(
name="country",
type=FieldType.ENUM,
group="Asset",
description="Two-letter country of the address.",
example="country:DE",
),
QueryField(
name="state",
type=FieldType.ENUM,
group="Review",
description="Review decision recorded for this finding.",
example="state:false_positive",
values=VULN_STATES,
facet="state",
),
QueryField(
name="evidence",
type=FieldType.ENUM,
group="Finding",
description="How the finding was established: observed, corroborated or proven.",
example="evidence:corroborated",
values=tuple(e for e in EVIDENCE_ORDER if e != Evidence.INFERRED.value),
facet="evidence",
),
QueryField(
name="seen",
type=FieldType.DATE,
group="Review",
description="When this scan recorded the finding.",
example="seen:<24h",
aliases=("discovered", "age"),
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the finding.",
example="is:kev",
aliases=("has",),
values=tuple(VULN_FLAGS),
),
)
VULN_GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="severity",
label="Severity",
description="Findings of the same severity",
),
GroupDimension(
key="template",
label="Check",
description="The same weakness across every asset it was found on",
),
GroupDimension(
key="tag",
label="Category",
description="Findings the checks categorise the same way",
),
GroupDimension(
key="cve",
label="CVE",
description="Findings sharing a published identifier",
),
GroupDimension(
key="evidence",
label="Evidence",
description="Findings with the same evidence",
),
GroupDimension(
key="state",
label="Review state",
description="Findings at the same point in review",
),
GroupDimension(
key="scanner",
label="Scanner",
description="Findings from the same tool",
),
GroupDimension(
key="type",
label="Protocol",
description="Findings on the same protocol",
),
GroupDimension(
key="host",
label="Web asset",
description="Everything found on one hostname",
),
GroupDimension(
key="location",
label="Location",
description="Every check that fired at the same place",
),
GroupDimension(
key="port",
label="Port",
description="Findings on the same port",
),
GroupDimension(
key="target",
label="Target",
description="Rows belonging to the same target",
),
)
VULN_EXAMPLE_GROUPS: tuple[str, ...] = (
"Priority",
"Change",
"Access control",
"Data exposure",
"Infrastructure",
"Review",
)
VULN_EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="is:kev",
description="Weaknesses with confirmed exploitation in the wild",
group="Priority",
generic=True,
),
QueryExample(
query="severity:critical",
description="Findings rated critical",
group="Priority",
generic=True,
),
QueryExample(
query="is:corroborated",
description="Findings a second check confirms at the same location",
group="Priority",
generic=True,
),
QueryExample(
query="is:corroborated and severity:[critical,high]",
description="Severe findings that more than one check agrees on",
group="Priority",
),
QueryExample(
query="evidence:proven",
description="Findings the asset answered with an out-of-band callback",
group="Priority",
),
QueryExample(
query="is:exploitable and not is:suppressed",
description="Exploited or likely to be, and not suppressed",
group="Priority",
),
QueryExample(
query="severity:[critical,high] and not is:cdn",
description="Severe findings on origin infrastructure",
group="Priority",
),
QueryExample(
query="cvss>=9",
description="Findings scored 9.0 or above",
group="Priority",
),
QueryExample(
query="is:new",
description="Findings absent from the previous scan of this target",
group="Change",
generic=True,
),
QueryExample(
query="is:new and severity:[critical,high]",
description="Severe findings that appeared since the previous scan",
group="Change",
),
QueryExample(
query="seen:<24h and is:cve",
description="Published vulnerabilities recorded in the last day",
group="Change",
),
QueryExample(
query="tag:default-login",
description="Default credentials accepted",
group="Access control",
generic=True,
),
QueryExample(
query="tag:panel and severity:[critical,high,medium]",
description="Administrative interfaces reachable from the internet",
group="Access control",
),
QueryExample(
query="tag:[unauth,auth-bypass]",
description="Protected functionality reachable without credentials",
group="Access control",
),
QueryExample(
query="tag:[exposure,disclosure]",
description="Files and data disclosed over HTTP",
group="Data exposure",
generic=True,
),
QueryExample(
query="tag:[backup,config,files]",
description="Source, configuration and backups reachable over HTTP",
group="Data exposure",
),
QueryExample(
query="is:extracted",
description="Findings that pulled a concrete value out of the response",
group="Data exposure",
),
QueryExample(
query="tag:takeover",
description="Hostnames pointing at claimable third-party infrastructure",
group="Infrastructure",
generic=True,
),
QueryExample(
query="type:ssl",
description="Transport security defects",
group="Infrastructure",
),
QueryExample(
query="type:[network,dns]",
description="Findings outside the web surface",
group="Infrastructure",
),
QueryExample(
query="tag:misconfig",
description="Misconfigured services",
group="Infrastructure",
),
QueryExample(
query="is:open and severity:[critical,high]",
description="Severe findings not reviewed",
group="Review",
generic=True,
),
QueryExample(
query="state:false_positive",
description="Findings a reviewer rejected",
group="Review",
),
QueryExample(
query="is:triaged",
description="Findings with a review decision",
group="Review",
),
)
VULN_QUERY = QueryRegistry(
key="vulnerability",
noun=SURFACE_NOUN[SurfaceDimension.VULNERABILITIES.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.VULNERABILITIES.value][1],
fields=VULN_FIELDS,
flags=VULN_FLAGS,
groups=VULN_GROUPS,
dimensions=VULN_GROUP_DIMENSIONS,
examples=VULN_EXAMPLES,
example_groups=VULN_EXAMPLE_GROUPS,
)
ENDPOINT_GROUPS: tuple[str, ...] = (
"Scope",
"Location",
"Parameters",
"Response",
"Evidence",
"Findings",
"Flags",
)
ENDPOINT_FLAGS: dict[str, str] = {
"new": "Absent from the previous scan of this target",
"param": "Accepts at least one query parameter",
"probed": "Requested by this scan",
"live": "Answered with 2xx or 3xx",
"redirect": "Answered with a redirect",
"auth": "Answered 401 or 403",
"missing": "Answered 404 or 410",
"api": "An API route, by path shape or content type",
"js": "JavaScript, including bundles and source maps",
"static": "Static content",
"interesting": "Matched an interest pattern in the path or a parameter",
"sensitive": "A credential, backup or version control path",
"orphan": "Not linked from any crawled page",
"archive-only": "Recorded by an archive and unreachable in this scan",
"linked": "Reached from a page on the live site",
"crawled": "Reached by the crawler",
"root": "The site root",
"titled": "Has a page title",
"vulnerable": "A vulnerability scan reported a finding on it",
"kev": "A known-exploited weakness was found on it",
}
ENDPOINT_FIELDS: tuple[QueryField, ...] = (
QueryField(
name="target",
type=FieldType.STRING,
group="Scope",
description="Target this endpoint belongs to.",
example="target:acme.com",
aliases=("scope",),
facet="target",
),
QueryField(
name="url",
type=FieldType.STRING,
group="Location",
description="Full URL.",
example="url:/wp-admin",
free_text=True,
evidence="URL",
),
QueryField(
name="path",
type=FieldType.STRING,
group="Location",
description="Path, without the host or the query string.",
example="path:/api/v1/users",
free_text=True,
evidence="Path",
),
QueryField(
name="dir",
type=FieldType.STRING,
group="Location",
description="Directory the endpoint sits in. dir: matches the whole branch, dir= only that folder.",
example='dir:"/admin/"',
aliases=("directory", "folder"),
facet="dir",
),
QueryField(
name="file",
type=FieldType.STRING,
group="Location",
description="File name, without the directory.",
example="file:config.json",
aliases=("filename",),
),
QueryField(
name="ext",
type=FieldType.STRING,
group="Location",
description="File extension.",
example="ext:[bak,old,sql]",
aliases=("extension",),
facet="ext",
),
QueryField(
name="host",
type=FieldType.STRING,
group="Location",
description="Hostname the endpoint is on.",
example="host:api.example.com",
facet="host",
free_text=True,
evidence="Host",
),
QueryField(
name="port",
type=FieldType.NUMBER,
group="Location",
description="Port the endpoint is served on.",
example="port:8443",
),
QueryField(
name="scheme",
type=FieldType.ENUM,
group="Location",
description="URL scheme.",
example="scheme:http",
values=("http", "https"),
),
QueryField(
name="depth",
type=FieldType.NUMBER,
group="Location",
description="Directory depth of the endpoint.",
example="depth:>3",
),
QueryField(
name="class",
type=FieldType.ENUM,
group="Location",
description="The kind of content the endpoint serves.",
example="class:api",
aliases=("kind",),
values=ENDPOINT_CLASSES,
facet="class",
),
QueryField(
name="param",
type=FieldType.STRING,
group="Parameters",
description="Name of a query parameter the endpoint accepts.",
example="param:redirect",
aliases=("parameter",),
facet="param",
),
QueryField(
name="params",
type=FieldType.NUMBER,
group="Parameters",
description="Number of parameters the endpoint accepts.",
example="params:>2",
),
QueryField(
name="interest",
type=FieldType.ENUM,
group="Parameters",
description="Interest pattern matched by path or parameter shape.",
example="interest:open_redirect",
values=INTEREST_KEYS,
facet="interest",
),
QueryField(
name="status",
type=FieldType.NUMBER,
group="Response",
description="HTTP status the endpoint answered with. Empty until probed.",
example="status:200..299",
facet="status",
),
QueryField(
name="type",
type=FieldType.STRING,
group="Response",
description="Content type of the response.",
example="type:json",
aliases=("content_type", "mime"),
),
QueryField(
name="title",
type=FieldType.STRING,
group="Response",
description="Page title.",
example='title:"index of"',
free_text=True,
evidence="Title",
),
QueryField(
name="length",
type=FieldType.BYTES,
group="Response",
description="Response body size.",
example="length:>100000",
aliases=("size",),
unit="bytes",
),
QueryField(
name="words",
type=FieldType.NUMBER,
group="Response",
description="Word count of the response body.",
example="words:<10",
),
QueryField(
name="tech",
type=FieldType.STRING,
group="Response",
description="Technology fingerprinted on the response.",
example="tech:django",
facet="tech",
),
QueryField(
name="method",
type=FieldType.STRING,
group="Response",
description="HTTP method the endpoint was seen with.",
example="method:POST",
),
QueryField(
name="source",
type=FieldType.ENUM,
group="Evidence",
description="The source that discovered the endpoint. An endpoint may have several.",
example="source:crawl",
values=tuple(SOURCE_LABELS),
facet="source",
),
QueryField(
name="from",
type=FieldType.STRING,
group="Evidence",
description="The URL that linked to this one.",
example="from:/sitemap.xml",
aliases=("found_on", "referrer"),
),
QueryField(
name="seen",
type=FieldType.DATE,
group="Evidence",
description="When this scan recorded the endpoint.",
example="seen:<7d",
),
QueryField(
name="vuln",
type=FieldType.ENUM,
group="Findings",
description="Severity of a vulnerability reported at this location.",
example="vuln:critical",
values=SEVERITY_ORDER,
),
QueryField(
name="cve",
type=FieldType.STRING,
group="Findings",
description="CVE reported at this location.",
example="cve:CVE-2024",
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the endpoint.",
example="is:orphan",
values=tuple(ENDPOINT_FLAGS),
),
)
ENDPOINT_GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="target",
label="Target",
description="Rows belonging to the same target",
),
GroupDimension(
key="dir",
label="Directory",
description="Endpoints in the same directory",
),
GroupDimension(
key="host",
label="Host",
description="Endpoints on the same host",
),
GroupDimension(
key="class",
label="Kind",
description="Endpoints of the same kind",
),
GroupDimension(
key="ext",
label="Extension",
description="Endpoints with the same file extension",
),
GroupDimension(
key="status",
label="Status",
description="Endpoints answering with the same status",
),
GroupDimension(
key="source",
label="Source",
description="Endpoints found the same way",
),
GroupDimension(
key="param",
label="Parameter",
description="Endpoints accepting the same parameter",
),
GroupDimension(
key="interest",
label="Interest",
description="Endpoints flagged for the same reason",
),
GroupDimension(
key="tech",
label="Technology",
description="Endpoints running the same technology",
),
)
ENDPOINT_EXAMPLE_GROUPS: tuple[str, ...] = (
"Attack surface",
"Hidden surface",
"Change",
"API",
"Evidence",
"Hygiene",
)
ENDPOINT_EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="is:param and is:live",
description="Live endpoints that accept input",
group="Attack surface",
generic=True,
),
QueryExample(
query="interest:open_redirect or interest:ssrf",
description="Parameters carrying a destination URL",
group="Attack surface",
),
QueryExample(
query="interest:traversal and is:live",
description="Live endpoints taking a file or path parameter",
group="Attack surface",
),
QueryExample(
query="is:sensitive",
description="Credential, backup and version control paths",
group="Attack surface",
generic=True,
),
QueryExample(
query="ext:[bak,old,sql,zip,tar,gz]",
description="Archive and backup files in the web root",
group="Attack surface",
),
QueryExample(
query="interest:admin and status:200",
description="Administrative paths answering without authentication",
group="Attack surface",
),
QueryExample(
query="is:orphan",
description="Endpoints not linked from any crawled page",
group="Hidden surface",
generic=True,
),
QueryExample(
query="is:archive-only",
description="Recorded by an archive and unreachable in this scan",
group="Hidden surface",
),
QueryExample(
query="source:archive and is:live",
description="Archived endpoints that respond",
group="Hidden surface",
),
QueryExample(
query="depth:>4 and is:live",
description="Deeply nested live endpoints",
group="Hidden surface",
),
QueryExample(
query="is:new",
description="Endpoints absent from the previous scan",
group="Change",
generic=True,
),
QueryExample(
query="is:new and is:param",
description="New input surface since the last scan",
group="Change",
),
QueryExample(
query="class:api",
description="API routes",
group="API",
generic=True,
),
QueryExample(
query="interest:api_doc",
description="Swagger, OpenAPI and GraphQL descriptions of the API",
group="API",
),
QueryExample(
query="class:api and is:param and not is:auth",
description="API routes taking input with no auth wall",
group="API",
),
QueryExample(
query="not is:probed",
description="Endpoints not requested by this scan",
group="Evidence",
generic=True,
),
QueryExample(
query="source:response_mining",
description="Found in stored responses",
group="Evidence",
),
QueryExample(
query="source:robots or source:sitemap",
description="Paths declared in robots.txt or a sitemap",
group="Evidence",
),
QueryExample(
query="is:vulnerable",
description="Endpoints a vulnerability scan reported a finding on",
group="Evidence",
),
QueryExample(
query="interest:debug_endpoint",
description="Actuator, metrics and diagnostic routes",
group="Hygiene",
generic=True,
),
QueryExample(
query="status:500..599",
description="Endpoints answering with a server error",
group="Hygiene",
),
QueryExample(
query="is:redirect and not is:live",
description="Redirects ending in an error",
group="Hygiene",
),
QueryExample(
query="type:json and not class:api",
description="JSON served outside the API surface",
group="Hygiene",
),
)
ENDPOINT_QUERY = QueryRegistry(
key="endpoint",
noun=SURFACE_NOUN[SurfaceDimension.ENDPOINTS.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.ENDPOINTS.value][1],
fields=ENDPOINT_FIELDS,
flags=ENDPOINT_FLAGS,
groups=ENDPOINT_GROUPS,
dimensions=ENDPOINT_GROUP_DIMENSIONS,
examples=ENDPOINT_EXAMPLES,
example_groups=ENDPOINT_EXAMPLE_GROUPS,
)
# ---------- software ----------
VERSION_SOURCE_KEYS: tuple[str, ...] = tuple(VERSION_SOURCE_LABELS)
SOFTWARE_GROUPS: tuple[str, ...] = (
"Software",
"Vulnerability",
"Confidence",
"Asset",
"Flags",
)
SOFTWARE_FLAGS: dict[str, str] = {
"new": "Not inferred by an earlier scan of this target",
"kev": "Listed in CISA KEV",
"ransomware": "Listed in CISA ransomware campaigns",
"overdue": "Past the CISA remediation deadline",
"likely": "EPSS score of 0.088 or above",
"firm": "Every part of the match is verified",
"conditional": "NVD names a further component this scan did not identify",
"backport": "A distribution build. Fixes may land without a version change",
"fingerprinted": "Version read from the page body",
"stated": "Version stated in a server header",
"web": "Inferred from a web asset",
"service": "Inferred from a service banner",
"corroborated": "A check at the same host names the same CVE",
"fixable": "A newer release in this scan is not affected",
}
SOFTWARE_FIELDS: tuple[QueryField, ...] = (
QueryField(
name="cve",
type=FieldType.STRING,
group="Vulnerability",
description="Published identifier of the weakness.",
example="cve:CVE-2021-23017",
free_text=True,
evidence="cve",
),
QueryField(
name="software",
type=FieldType.STRING,
group="Software",
description="Name of the software as the scan read it.",
example="software:nginx",
aliases=("name",),
free_text=True,
evidence="software",
),
QueryField(
name="product",
type=FieldType.STRING,
group="Software",
description="Product as NVD files it.",
example="product:http_server",
),
QueryField(
name="vendor",
type=FieldType.STRING,
group="Software",
description="Vendor as NVD files it.",
example="vendor:apache",
),
QueryField(
name="version",
type=FieldType.STRING,
group="Software",
description="Version the asset reported.",
example="version:2.4.52",
),
QueryField(
name="source",
type=FieldType.ENUM,
group="Software",
description="Source of the reported version.",
example="source:banner",
values=tuple(VERSION_SOURCE_KEYS),
facet="source",
),
QueryField(
name="severity",
type=FieldType.ENUM,
group="Vulnerability",
description="Severity NVD scored the CVE at.",
example="severity:critical",
values=SEVERITY_ORDER,
facet="severity",
),
QueryField(
name="cvss",
type=FieldType.NUMBER,
group="Vulnerability",
description="CVSS base score.",
example="cvss>=9",
),
QueryField(
name="epss",
type=FieldType.NUMBER,
group="Vulnerability",
description="Probability of exploitation in the next 30 days.",
example="epss>=0.5",
),
QueryField(
name="rank",
type=FieldType.NUMBER,
group="Vulnerability",
description="Exploitation rank built from the intelligence signals.",
example="rank>=40",
),
QueryField(
name="confidence",
type=FieldType.ENUM,
group="Confidence",
description="Verification level of the match.",
example="confidence:high",
values=tuple(CONFIDENCE_KEYS),
facet="confidence",
),
QueryField(
name="caveat",
type=FieldType.ENUM,
group="Confidence",
description="A part of the match this scan did not verify.",
example="caveat:backport",
values=tuple(CAVEAT_KEYS),
facet="caveat",
),
QueryField(
name="evidence",
type=FieldType.ENUM,
group="Confidence",
description="Inferred from the version alone, or corroborated by a check at the same host.",
example="evidence:corroborated",
values=(Evidence.INFERRED.value, Evidence.CORROBORATED.value),
facet="evidence",
),
QueryField(
name="target",
type=FieldType.STRING,
group="Asset",
description="Target the asset belongs to.",
example="target:example.com",
),
QueryField(
name="host",
type=FieldType.STRING,
group="Asset",
description="Hostname running the software.",
example="host:www.example.com",
free_text=True,
evidence="host",
),
QueryField(
name="ip",
type=FieldType.IP,
group="Asset",
description="Address running the software.",
example="ip:203.0.113.10",
),
QueryField(
name="port",
type=FieldType.NUMBER,
group="Asset",
description="Port the software answers on.",
example="port:443",
),
QueryField(
name="seen",
type=FieldType.DATE,
group="Asset",
description="When this scan inferred it.",
example="seen:<24h",
aliases=("discovered", "age"),
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the match.",
example="is:kev",
aliases=("has",),
values=tuple(SOFTWARE_FLAGS),
),
)
SOFTWARE_GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="target", label="Target", description="Rows belonging to the same target"
),
GroupDimension(
key="cve", label="CVE", description="Every asset affected by one published CVE"
),
GroupDimension(
key="product",
label="Product",
description="Everything running the same software",
),
GroupDimension(
key="version",
label="Version",
description="Everything running the same release",
),
GroupDimension(
key="host", label="Host", description="Everything inferred on one hostname"
),
GroupDimension(
key="severity", label="Severity", description="Matches of the same severity"
),
GroupDimension(
key="confidence",
label="Confidence",
description="Matches with the same amount verified",
),
GroupDimension(
key="evidence",
label="Evidence",
description="Matches with the same evidence",
),
)
SOFTWARE_EXAMPLE_GROUPS: tuple[str, ...] = (
"Priority",
"Change",
"Software",
"Confidence",
)
SOFTWARE_EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="is:kev",
description="Software carrying a CVE with confirmed exploitation",
group="Priority",
generic=True,
),
QueryExample(
query="is:kev and is:firm",
description="Confirmed exploited and fully verified",
group="Priority",
),
QueryExample(
query="severity:critical and is:stated",
description="Critical CVEs where the server stated its own version",
group="Priority",
generic=True,
),
QueryExample(
query="epss>=0.5",
description="EPSS probability of 0.5 or above",
group="Priority",
),
QueryExample(
query="is:kev and is:fixable",
description="Known exploited CVEs with an unaffected release in this scan",
group="Priority",
generic=True,
),
QueryExample(
query="is:new",
description="Matches absent from the previous scan of this target",
group="Change",
generic=True,
),
QueryExample(
query="is:new and severity:[critical,high]",
description="Severe matches that appeared since the previous scan",
group="Change",
),
QueryExample(
query="software:nginx",
description="Everything running nginx",
group="Software",
generic=True,
),
QueryExample(
query="product:http_server and cvss>=9",
description="Apache builds carrying a critically scored CVE",
group="Software",
),
QueryExample(
query="software:wordpress and is:new",
description="WordPress matches absent from the previous scan",
group="Software",
),
QueryExample(
query="confidence:high",
description="Matches with no unverified part",
group="Confidence",
generic=True,
),
QueryExample(
query="caveat:backport",
description="Distribution builds that may patch without a version change",
group="Confidence",
),
QueryExample(
query="not caveat:conditional",
description="Matches NVD does not tie to a further component",
group="Confidence",
),
QueryExample(
query="evidence:corroborated",
description="Matches that a check at the same host also names",
group="Confidence",
),
)
SOFTWARE_QUERY = QueryRegistry(
key="software",
noun=SURFACE_NOUN[SurfaceDimension.SOFTWARE.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.SOFTWARE.value][1],
fields=SOFTWARE_FIELDS,
flags=SOFTWARE_FLAGS,
groups=SOFTWARE_GROUPS,
dimensions=SOFTWARE_GROUP_DIMENSIONS,
examples=SOFTWARE_EXAMPLES,
example_groups=SOFTWARE_EXAMPLE_GROUPS,
)
# ---------- secrets ----------
SECRET_GROUPS: tuple[str, ...] = (
"Secret",
"State",
"Asset",
"Flags",
)
SECRET_FLAGS: dict[str, str] = {
"new": "Not read by an earlier scan of this target",
"exposed": "A private value readable in a stored response",
"public": "A value meant to be in the page",
"expired": "A token whose expiry is in the past",
"secret": "A private value, not a public identifier",
"shared": "Read on more than one host",
}
SECRET_FIELDS: tuple[QueryField, ...] = (
QueryField(
name="secret",
type=FieldType.ENUM,
group="Secret",
description="Kind of value the detector matched.",
example="secret:aws_access_key",
aliases=("kind",),
values=SECRET_DETECTOR_KEYS,
facet="kind",
),
QueryField(
name="group",
type=FieldType.ENUM,
group="Secret",
description="Family the detector belongs to.",
example="group:cloud",
values=SECRET_GROUP_KEYS,
facet="group",
),
QueryField(
name="vendor",
type=FieldType.STRING,
group="Secret",
description="Provider the value belongs to.",
example="vendor:stripe",
facet="vendor",
),
QueryField(
name="subject",
type=FieldType.STRING,
group="Secret",
description="Who or what the value names, such as an email domain or DSN host.",
example="subject:acme.com",
free_text=True,
evidence="subject",
facet="subject",
),
QueryField(
name="fingerprint",
type=FieldType.STRING,
group="Secret",
description="Identity of one value across scans and targets.",
example="fingerprint=3a9f…",
),
QueryField(
name="state",
type=FieldType.ENUM,
group="State",
description="Whether the value is exposed, public by design or expired.",
example="state:exposed",
values=SECRET_STATE_KEYS,
facet="state",
),
QueryField(
name="source",
type=FieldType.ENUM,
group="State",
description="Where in the response it was read.",
example="source:body",
values=SECRET_SOURCE_KEYS,
facet="source",
),
QueryField(
name="target",
type=FieldType.STRING,
group="Asset",
description="Target the value belongs to.",
example="target:example.com",
),
QueryField(
name="host",
type=FieldType.STRING,
group="Asset",
description="Hostname the value was read on.",
example="host:www.example.com",
free_text=True,
evidence="host",
),
QueryField(
name="url",
type=FieldType.STRING,
group="Asset",
description="URL the value was read from.",
example="url:/config.json",
free_text=True,
evidence="url",
),
QueryField(
name="seen",
type=FieldType.DATE,
group="Asset",
description="When this scan read it.",
example="seen:<24h",
aliases=("discovered", "age"),
),
QueryField(
name="is",
type=FieldType.FLAG,
group="Flags",
description="Property of the value.",
example="is:exposed",
aliases=("has",),
values=tuple(SECRET_FLAGS),
),
)
SECRET_GROUP_DIMENSIONS: tuple[GroupDimension, ...] = (
GroupDimension(
key="value",
label="Value",
description="One row per distinct value, however many places carry it",
),
GroupDimension(
key="target", label="Target", description="Values belonging to the same target"
),
GroupDimension(key="secret", label="Kind", description="Values of the same kind"),
GroupDimension(
key="group", label="Family", description="Values in the same family"
),
GroupDimension(
key="vendor", label="Provider", description="Values from the same provider"
),
GroupDimension(key="state", label="State", description="Values in the same state"),
GroupDimension(key="host", label="Host", description="Values read on one host"),
GroupDimension(
key="subject",
label="Subject",
description="Values naming the same subject",
),
)
SECRET_EXAMPLE_GROUPS: tuple[str, ...] = (
"Priority",
"Change",
"Kind",
"Contacts",
)
SECRET_EXAMPLES: tuple[QueryExample, ...] = (
QueryExample(
query="is:exposed",
description="Private values readable in a response",
group="Priority",
generic=True,
),
QueryExample(
query="group:cloud and is:exposed",
description="Exposed cloud provider credentials",
group="Priority",
),
QueryExample(
query="is:exposed and is:new",
description="Exposed values absent from the previous scan",
group="Change",
),
QueryExample(
query="secret:jwt and is:expired",
description="Expired JSON web tokens",
group="Kind",
),
QueryExample(
query="secret:aws_access_key",
description="AWS access keys",
group="Kind",
generic=True,
),
QueryExample(
query="secret:email",
description="Email addresses printed in responses",
group="Contacts",
generic=True,
),
QueryExample(
query="secret:email and subject:example.com",
description="Addresses on one email domain",
group="Contacts",
),
)
SECRET_QUERY = QueryRegistry(
key="secrets",
noun=SURFACE_NOUN[SurfaceDimension.SECRETS.value][0],
noun_plural=SURFACE_NOUN[SurfaceDimension.SECRETS.value][1],
fields=SECRET_FIELDS,
flags=SECRET_FLAGS,
groups=SECRET_GROUPS,
dimensions=SECRET_GROUP_DIMENSIONS,
examples=SECRET_EXAMPLES,
example_groups=SECRET_EXAMPLE_GROUPS,
)