Skip to content

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.

Checked against config-schemas@v1.15.0, terraform-provider-resolve@82cb23c, cli@v4.9.1

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 with vulnetix config plan: it shows each object as create, update, delete or no change, with a planHash that apply then holds to. Apply writes in one transaction.
  • Manager. Each object records the manager that applied it (terraform, console or gitops:<repo>). --prune deletes 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: true moves the one active strategy to this one and deactivates the others.
  • A rule with when is stored with metric CEL and the expression in RiskPrioritizationRule.expr.
  • The expression is type-checked at plan. It is evaluated by the ranking ladder; see where each when runs.

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.

Edit this pageSources: config-schemas/schemas/RiskStrategy.schema.json, config-schemas/schemas/common.schema.json, config-schemas/examples/RiskStrategy/minimal.yaml, config-schemas/examples/RiskStrategy/advanced.yaml, config-schemas/examples/RiskStrategy/exploit-first.yaml, config-schemas/cel/variables.go, terraform-provider-resolve/examples/resources/vulnetix_risk_strategy/minimal.tf, terraform-provider-resolve/examples/resources/vulnetix_risk_strategy/advanced.tf, vdb-site/docs/platform-iac-engine.md, vdb-site/api/internal/handler/config_engine_kinds.go, vdb-site/api/internal/config/riskstrategy.go, vdb-site/api/internal/handler/risk_cel.go
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.