RiskStrategy
A risk prioritisation strategy: an ordered list of rules that puts every open finding in a priority tier. Fields, YAML, HCL and CLI samples, and how apply treats it.
A risk prioritisation strategy: an ordered list of rules that puts every open finding in a priority tier. A finding’s tier is the position of the first rule it matches, 0 being the highest priority. A tenant may keep several strategies; one is active, and a tenant with none active uses the system default.
apiVersion |
vulnetix.com/v1 |
kind |
RiskStrategy |
| Group | Prioritisation |
| Status | Served. The config engine plans and applies it. |
| Objects per tenant | many, keyed by metadata.name |
| Token and role | An owner or admin session, or a vmt_ config management token with the owner or admin role that lists RiskStrategy in its kinds. See Management tokens. |
| CEL fields | spec.rules[].when (finding): see CEL rule expressions |
| Terraform resource | vulnetix_risk_strategy, imported by name (see Terraform and OpenTofu) |
| CLI | vulnetix config apply, CLI v3.157.0 and later. |
| Pinned schema | v1.15.0/RiskStrategy.schema.json |
| Newest schema | latest/RiskStrategy.schema.json |
| First released in | v1.1.0 |
| Print the built-in copy | vulnetix config schema RiskStrategy |
Minimal
Only the fields the kind requires. From examples/RiskStrategy/minimal.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/RiskStrategy.schema.json
apiVersion: vulnetix.com/v1
kind: RiskStrategy
metadata:
name: catch-all
spec:
rules:
- metric: REMAINING
operator: ALWAYS
HCL (resource vulnetix_risk_strategy)
# Only the arguments the kind requires. From config-schemas examples/RiskStrategy/minimal.yaml.
resource "vulnetix_risk_strategy" "catch_all" {
name = "catch-all"
rules = [
{
metric = "REMAINING"
operator = "ALWAYS"
},
]
}
CLI (applies it)
export VULNETIX_CONFIG_TOKEN=vmt_... # a config management token
vulnetix config apply -f - --manager gitops:acme/platform --auto-approve <<'EOF'
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/RiskStrategy.schema.json
apiVersion: vulnetix.com/v1
kind: RiskStrategy
metadata:
name: catch-all
spec:
rules:
- metric: REMAINING
operator: ALWAYS
EOF
Advanced
Every field the kind supports, with a when expression where it takes one. From examples/RiskStrategy/advanced.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/RiskStrategy.schema.json
apiVersion: vulnetix.com/v1
kind: RiskStrategy
metadata:
name: exploit-first
labels:
team: platform-security
annotations:
ticket: https://jira.example.com/browse/SEC-2214
spec:
description: Known exploitation first, then exposure, then score.
active: true
rules:
- { metric: MALWARE, operator: IS_TRUE, label: Malware }
- { metric: KEV_CISA, operator: IS_TRUE, label: CISA KEV }
- { metric: EXPLOIT_WEAPONIZED, operator: IS_TRUE, enabled: true }
- { metric: INTERNET_EXPOSED_PACKAGE, operator: IS_TRUE, label: Exposed package }
- metric: ATTACK_PATH
operator: IS_TRUE
attackPathId: 0b9d5f9e-3c51-4a0f-9d43-6f1f5f0c2a11
label: Edge to database
- metric: CEL
label: Reachable and likely
when: reachable && (epss >= 0.2 || exploitMaturity == 'active')
- { metric: EPSS, operator: GTE, thresholdLow: 0.1 }
- { metric: CVSS, operator: BETWEEN, thresholdLow: 7, thresholdHigh: 10 }
- { metric: EXPLOIT_POC, operator: IS_TRUE, label: Public PoC, enabled: false }
- { metric: REMAINING, operator: ALWAYS, label: Everything else }
HCL (resource vulnetix_risk_strategy)
# Every argument the kind supports. From config-schemas examples/RiskStrategy/advanced.yaml.
resource "vulnetix_risk_strategy" "exploit_first" {
name = "exploit-first"
active = true
description = "Known exploitation first, then exposure, then score."
rules = [
{
label = "Malware"
metric = "MALWARE"
operator = "IS_TRUE"
},
{
label = "CISA KEV"
metric = "KEV_CISA"
operator = "IS_TRUE"
},
{
enabled = true
metric = "EXPLOIT_WEAPONIZED"
operator = "IS_TRUE"
},
{
label = "Exposed package"
metric = "INTERNET_EXPOSED_PACKAGE"
operator = "IS_TRUE"
},
{
attack_path_id = "0b9d5f9e-3c51-4a0f-9d43-6f1f5f0c2a11"
label = "Edge to database"
metric = "ATTACK_PATH"
operator = "IS_TRUE"
},
{
label = "Reachable and likely"
metric = "CEL"
when = "reachable && (epss >= 0.2 || exploitMaturity == 'active')"
},
{
metric = "EPSS"
operator = "GTE"
threshold_low = 0.1
},
{
metric = "CVSS"
operator = "BETWEEN"
threshold_high = 10
threshold_low = 7
},
{
enabled = false
label = "Public PoC"
metric = "EXPLOIT_POC"
operator = "IS_TRUE"
},
{
label = "Everything else"
metric = "REMAINING"
operator = "ALWAYS"
},
]
labels = {
team = "platform-security"
}
annotations = {
ticket = "https://jira.example.com/browse/SEC-2214"
}
}
CLI (applies it)
export VULNETIX_CONFIG_TOKEN=vmt_... # a config management token
vulnetix config apply -f - --manager gitops:acme/platform --auto-approve <<'EOF'
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/RiskStrategy.schema.json
apiVersion: vulnetix.com/v1
kind: RiskStrategy
metadata:
name: exploit-first
labels:
team: platform-security
annotations:
ticket: https://jira.example.com/browse/SEC-2214
spec:
description: Known exploitation first, then exposure, then score.
active: true
rules:
- { metric: MALWARE, operator: IS_TRUE, label: Malware }
- { metric: KEV_CISA, operator: IS_TRUE, label: CISA KEV }
- { metric: EXPLOIT_WEAPONIZED, operator: IS_TRUE, enabled: true }
- { metric: INTERNET_EXPOSED_PACKAGE, operator: IS_TRUE, label: Exposed package }
- metric: ATTACK_PATH
operator: IS_TRUE
attackPathId: 0b9d5f9e-3c51-4a0f-9d43-6f1f5f0c2a11
label: Edge to database
- metric: CEL
label: Reachable and likely
when: reachable && (epss >= 0.2 || exploitMaturity == 'active')
- { metric: EPSS, operator: GTE, thresholdLow: 0.1 }
- { metric: CVSS, operator: BETWEEN, thresholdLow: 7, thresholdHigh: 10 }
- { metric: EXPLOIT_POC, operator: IS_TRUE, label: Public PoC, enabled: false }
- { metric: REMAINING, operator: ALWAYS, label: Everything else }
EOF
Fields
Paths use dots for nesting and [] for the items of a list. Required means required whenever the parent object is present. A default is what the server uses when the field is left out.
| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
apiVersion |
string | yes | exactly vulnetix.com/v1 |
The document format version. Every kind in this release is vulnetix.com/v1. | |
kind |
string | yes | exactly RiskStrategy |
Always RiskStrategy. | |
metadata |
object | yes | no other keys | metadata.name is stored as the strategy’s name. | |
metadata.name |
string | yes | pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
The object’s name, unique per kind within a tenant. It is the natural key: plan, apply, export and the Terraform provider all address an object by kind and name. | |
metadata.org |
string | no | format uuid |
The tenant (organization UUID) the document belongs to. When set, plan and apply refuse to run against any other tenant. | |
metadata.labels |
object | no | map of string to string (length 0 to 256); keys match ^[a-z0-9]([a-z0-9._/-]{0,61}[a-z0-9])?$ |
Free key/value labels. Stored with the object, never interpreted by the server. | |
metadata.annotations |
object | no | map of string to string (length 0 to 4096) | Free key/value notes for people and tools, such as a ticket link. | |
spec |
object | yes | no other keys | ||
spec.description |
string | no | |||
spec.active |
boolean | no | false |
Make this the tenant’s active strategy. Activating one deactivates the others, so at most one document in a set should say true. | |
spec.rules |
array of object | yes | 1 to any items | The rules in priority order: the first is tier 0. Exactly one REMAINING rule is required, and it belongs last, where it catches every finding no earlier rule matched. | |
spec.rules[].label |
string | no | The tier’s name in the console. Defaults to the metric and operator. | ||
spec.rules[].enabled |
boolean | no | true |
||
spec.rules[].metric |
string | yes | MALWARE, KEV_CISA, KEV_ENISA, KEV_VULNETIX, KEV_VULNCHECK, EXPLOIT_WEAPONIZED, EXPLOIT_ACTIVE, EXPLOIT_POC, INTERNET_EXPOSED_PACKAGE, INTERNET_EXPOSED, REACHABLE, UNREACHABLE, TRACEABLE, TRACEABLE_GRAPH, ATTACK_PATH, CVSS, CWSS, CUSTOM_CVSS, CUSTOM_CWSS, CESS, EPSS, REMAINING, CEL |
What the rule tests. Present-or-not signals: MALWARE; KEV_CISA, KEV_ENISA, KEV_VULNETIX, KEV_VULNCHECK (in that known-exploited catalogue); EXPLOIT_WEAPONIZED, EXPLOIT_ACTIVE, EXPLOIT_POC; INTERNET_EXPOSED_PACKAGE (the package was seen on a publicly reachable cloud resource of the repository); INTERNET_EXPOSED (the repository has a publicly reachable cloud resource); REACHABLE and UNREACHABLE (SCA reachability, assessed); TRACEABLE (resolves to a file and lines); TRACEABLE_GRAPH (traceable, and the file is called in the repository graph); ATTACK_PATH (matches attackPathId). Scores: CVSS and CWSS (the effective score, an analyst override preferred), CUSTOM_CVSS and CUSTOM_CWSS (the override only), CESS (0 to 10), EPSS (0 to 1). REMAINING matches everything. CEL matches when the when expression is true. | |
spec.rules[].operator |
string | no | IS_TRUE, GTE, GT, LTE, LT, BETWEEN, ALWAYS |
IS_TRUE for a present-or-not metric. GTE, GT, LTE and LT compare a score with thresholdLow; BETWEEN tests thresholdLow <= score <= thresholdHigh. ALWAYS matches everything, for REMAINING. | |
spec.rules[].thresholdLow |
number | no | |||
spec.rules[].thresholdHigh |
number | no | |||
spec.rules[].attackPathId |
string | no | format uuid |
ATTACK_PATH only, and required there: the attack path the rule matches. | |
spec.rules[].when |
string | no | length 1 to 4096 | The condition of a CEL rule, over the finding variables. It fails open: a finding the expression fails on does not match, and a rule that does not compile matches nothing and is logged. |
Rules
Conditions the schema enforces beyond each field’s own type. vulnetix config validate and the engine’s plan report a document that breaks one.
| On | Rule |
|---|---|
spec.rules[] |
When metric is one of MALWARE, KEV_CISA, KEV_ENISA, KEV_VULNETIX, KEV_VULNCHECK, EXPLOIT_WEAPONIZED, EXPLOIT_ACTIVE, EXPLOIT_POC, INTERNET_EXPOSED_PACKAGE, INTERNET_EXPOSED, REACHABLE, UNREACHABLE, TRACEABLE, TRACEABLE_GRAPH, ATTACK_PATH: operator is required and operator must be IS_TRUE. |
spec.rules[] |
When metric is one of CVSS, CWSS, CUSTOM_CVSS, CUSTOM_CWSS, CESS, EPSS: operator is required and operator must be GTE, GT, LTE, LT, BETWEEN, ALWAYS. |
spec.rules[] |
When metric is REMAINING: operator is required and operator must be ALWAYS. |
spec.rules[] |
When metric is CEL: when is required and operator is not allowed. Otherwise: when is not allowed. |
spec.rules[] |
When metric is ATTACK_PATH: attackPathId is required. Otherwise: attackPathId is not allowed. |
spec.rules[] |
When operator is one of GTE, GT, LTE, LT, BETWEEN: thresholdLow is required. |
spec.rules[] |
When operator is BETWEEN: thresholdHigh is required. |
CEL variables
spec.rules[].when is a CEL expression in the finding context, returning bool. It is evaluated by the ranking ladder in the Vulnetix site API, over the tenant’s open findings (at most 5000, most severe first). Fails open. A rule that does not compile matches nothing and is logged. A finding the expression errors on does not match. See where each expression is evaluated. The functions, limits and error codes are on CEL rule expressions.
| Variable | Type | Bound | Description |
|---|---|---|---|
id |
string |
256 chars | The advisory identifier, such as CVE-2024-3094 or a GHSA ID. |
aliases |
list(string) |
64 items of 256 chars | Other identifiers the advisory is known by. |
severity |
string |
64 chars | Lower case: critical, high, medium, low, or empty when unknown. |
cvss |
double |
CVSS base score, 0 to 10. 0 when unscored. | |
epss |
double |
EPSS probability, 0 to 1. | |
epssPercentile |
double |
EPSS percentile, 0 to 1. | |
kev |
bool |
Listed in any known-exploited catalogue: CISA KEV, VulnCheck KEV or EU KEV. | |
exploitMaturity |
string |
64 chars | The highest exploit tier, as the CLI --exploits gate grades it: poc, active, weaponized, or empty when none is known. |
exploitCount |
int |
Number of public exploits known. | |
reachability |
string |
64 chars | Lower case reachability verdict: direct, transitive, semantic, unreachable, or empty when not assessed. |
reachable |
bool |
Derived. True when reachability is direct, transitive or semantic, the same set the findings page counts as reachable. |
|
internetExposed |
bool |
The package is observed on internet-facing infrastructure. | |
eol |
bool |
The package version is end of life. | |
malicious |
bool |
The package is known malware. | |
fixAvailability |
string |
64 chars | available, partial, no_fix, or empty when unknown. |
direct |
bool |
The package is a direct dependency, not a transitive one. | |
purl |
string |
2048 chars | Package URL of the affected package, with version. |
ecosystem |
string |
64 chars | Package ecosystem, such as npm, pypi, golang, maven. |
cwe |
list(string) |
64 items of 64 chars | CWE identifiers, upper case with prefix: CWE-79. |
repo |
string |
512 chars | Repository full name, owner/name. |
branch |
string |
512 chars | Branch the finding was scanned on. |
path |
string |
1024 chars | File the finding came from, such as the manifest or lock file, relative to the repository root. |
labels |
map(string, string) |
50 keys, keys and values of 256 chars | The key/value labels of the repository the finding is in, as set on the repository’s asset record or in a RepositorySettings document: labels['tier'] == 'prod'. Empty when the repository has none, and in a runtime that does not know the repository’s asset record, so 'tier' in labels is false there. |
owner |
string |
512 chars | Who owns the repository: a team name, a user name or an email address, as set on its asset record. Empty when none is set. |
criticality |
string |
64 chars | How much the business depends on the repository: low, medium, high or critical. Empty when none is set, so criticality in ['high', 'critical'] is false for an unclassified repository. |
now |
timestamp |
The time of evaluation. Callers may fix it; otherwise it is the clock when Eval runs. |
Applying this kind
- Command.
vulnetix config apply -f <file>; the CLI line above says from which release. Plan first withvulnetix config plan: it shows each object as create, update, delete or no change, with aplanHashthat apply then holds to. Apply writes in one transaction. - Manager. Each object records the manager that applied it (
terraform,consoleorgitops:<repo>).--prunedeletes only objects its own manager owns. See The manager and the config engine. - Status. Applied, Unchanged or Deleted, with no other status fields. The phases the engine reports are explained under status phases.
How apply treats it
- Rules are diffed per item, so an unchanged rule keeps its row. The rules are stored in
RiskPrioritizationRule. spec.active: truemoves the one active strategy to this one and deactivates the others.- A rule with
whenis stored with metricCELand the expression inRiskPrioritizationRule.expr. - The expression is type-checked at plan. It is evaluated by the ranking ladder; see where each
whenruns.
Terraform and OpenTofu
The resource is vulnetix_risk_strategy in the vulnetix/resolve provider. Its arguments are the spec in snake case, beside name, labels and annotations from metadata. The Minimal and Advanced HCL are above. The provider’s own reference is vulnetix_risk_strategy.
Import an object that already exists by its name:
# Import an existing RiskStrategy by its name (metadata.name).
terraform import vulnetix_risk_strategy.catch_all catch-all
Install, authentication and how state is read: Terraform and OpenTofu.
More examples
examples/RiskStrategy/exploit-first.yaml from Vulnetix/config-schemas:
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/RiskStrategy.schema.json
apiVersion: vulnetix.com/v1
kind: RiskStrategy
metadata:
name: exploit-first
spec:
description: Known exploitation first, then exposure, then score.
active: true
rules:
- { metric: MALWARE, operator: IS_TRUE, label: Malware }
- { metric: KEV_CISA, operator: IS_TRUE, label: CISA KEV }
- { metric: EXPLOIT_WEAPONIZED, operator: IS_TRUE }
- { metric: INTERNET_EXPOSED_PACKAGE, operator: IS_TRUE, label: Exposed package }
- metric: ATTACK_PATH
operator: IS_TRUE
attackPathId: 0b9d5f9e-3c51-4a0f-9d43-6f1f5f0c2a11
label: Edge to database
- metric: CEL
label: Reachable and likely
when: reachable && epss >= 0.2
- { metric: EPSS, operator: GTE, thresholdLow: 0.1 }
- { metric: CVSS, operator: BETWEEN, thresholdLow: 7, thresholdHigh: 10 }
- { metric: REMAINING, operator: ALWAYS, label: Everything else }
Check a file with vulnetix config validate -f <file>. See Validate documents. In an editor, start the file with the schema comment.