Skip to content

SsvcPolicy

How the tenant makes its CISA SSVC decision (https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc) for a finding: Track, Track*. 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

How the tenant makes its CISA SSVC decision (https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc) for a finding: Track, Track*, Attend or Act. The decision table is the CISA Coordinator table 2.0.3 and is fixed: four decision points (exploitation, automatable, technical impact, mission and well-being) go in. This document configures how Vulnetix’s signals become those four values (CEL over the finding variables, and the repository’s criticality for mission and well-being), which table cells the organisation reads differently (mapping), which findings it decides by rule instead (overrides), and what each outcome asks of it (outcomes). A field left out takes the default, which is the policy of a tenant with no document. One per tenant.

apiVersion vulnetix.com/v1
kind SsvcPolicy
Group Prioritisation
Status Served. The config engine plans and applies it.
Objects per tenant one (metadata.name: default)
Token and role An owner or admin session, or a vmt_ config management token with the owner or admin role that lists SsvcPolicy in its kinds. See Management tokens.
CEL fields spec.exploitation.active (finding), spec.exploitation.poc (finding), spec.automatable (finding), spec.technicalImpact (finding), spec.overrides[].when (finding): see CEL rule expressions
Terraform resource vulnetix_ssvc_policy, imported by name (see Terraform and OpenTofu)
CLI vulnetix config apply, CLI v3.168.0 and later.
Pinned schema v1.15.0/SsvcPolicy.schema.json
Newest schema latest/SsvcPolicy.schema.json
First released in v1.8.0
Print the built-in copy vulnetix config schema SsvcPolicy

Minimal

Only the fields the kind requires. From examples/SsvcPolicy/minimal.yaml.

YAML

# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/SsvcPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: SsvcPolicy
metadata:
  name: default
spec: {}

HCL (resource vulnetix_ssvc_policy)

# Only the arguments the kind requires. From config-schemas examples/SsvcPolicy/minimal.yaml.

resource "vulnetix_ssvc_policy" "default" {
}

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/SsvcPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: SsvcPolicy
metadata:
  name: default
spec: {}
EOF

Advanced

Every field the kind supports, with a when expression where it takes one. From examples/SsvcPolicy/advanced.yaml.

YAML

# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/SsvcPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: SsvcPolicy
metadata:
  name: default
  labels:
    team: platform-security
  annotations:
    ticket: https://tracker.example.com/SEC-3120
spec:
  # How Vulnetix's signals become the four decision points. These are the
  # defaults written out, with a stricter automatable test.
  exploitation:
    active: kev || exploitMaturity in ['active', 'weaponized']
    poc: exploitMaturity == 'poc' || exploitCount > 0
  automatable: cvss >= 9.0 || epss >= 0.5
  technicalImpact: cvss >= 9.0 || malicious
  # Mission and well-being, from the repository's criticality.
  missionWellBeing:
    low: low
    medium: medium
    high: high
    critical: high
    unclassified: medium
  # Cells of the CISA table the organisation reads differently.
  mapping:
    - exploitation: [active]
      automatable: [no, yes]
      technicalImpact: [total]
      missionWellBeing: [medium, high]
      outcome: Act
  # Decide by rule, first match wins.
  overrides:
    - name: card data in scope
      when: labels['pci'] == 'in-scope' && severity in ['critical', 'high']
      outcome: Act
    - name: no fix and not reachable
      when: "!reachable && fixAvailability == 'no_fix'"
      outcome: Track
  outcomes:
    track:
      priority: low
      dueDays: 90
    trackStar:
      priority: medium
      dueDays: 60
    attend:
      priority: high
      dueDays: 14
    act:
      priority: immediate
      dueDays: 3

HCL (resource vulnetix_ssvc_policy)

# Every argument the kind supports. From config-schemas examples/SsvcPolicy/advanced.yaml.

resource "vulnetix_ssvc_policy" "default" {
  automatable = "cvss >= 9.0 || epss >= 0.5"
  exploitation = {
    active = "kev || exploitMaturity in ['active', 'weaponized']"
    poc    = "exploitMaturity == 'poc' || exploitCount > 0"
  }
  mapping = [
    {
      automatable        = ["no", "yes"]
      exploitation       = ["active"]
      mission_well_being = ["medium", "high"]
      outcome            = "Act"
      technical_impact   = ["total"]
    },
  ]
  mission_well_being = {
    critical     = "high"
    high         = "high"
    low          = "low"
    medium       = "medium"
    unclassified = "medium"
  }
  outcomes = {
    act = {
      due_days = 3
      priority = "immediate"
    }
    attend = {
      due_days = 14
      priority = "high"
    }
    track = {
      due_days = 90
      priority = "low"
    }
    track_star = {
      due_days = 60
      priority = "medium"
    }
  }
  overrides = [
    {
      name    = "card data in scope"
      outcome = "Act"
      when    = "labels['pci'] == 'in-scope' && severity in ['critical', 'high']"
    },
    {
      name    = "no fix and not reachable"
      outcome = "Track"
      when    = "!reachable && fixAvailability == 'no_fix'"
    },
  ]
  technical_impact = "cvss >= 9.0 || malicious"
  labels = {
    team = "platform-security"
  }
  annotations = {
    ticket = "https://tracker.example.com/SEC-3120"
  }
}

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/SsvcPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: SsvcPolicy
metadata:
  name: default
  labels:
    team: platform-security
  annotations:
    ticket: https://tracker.example.com/SEC-3120
spec:
  # How Vulnetix's signals become the four decision points. These are the
  # defaults written out, with a stricter automatable test.
  exploitation:
    active: kev || exploitMaturity in ['active', 'weaponized']
    poc: exploitMaturity == 'poc' || exploitCount > 0
  automatable: cvss >= 9.0 || epss >= 0.5
  technicalImpact: cvss >= 9.0 || malicious
  # Mission and well-being, from the repository's criticality.
  missionWellBeing:
    low: low
    medium: medium
    high: high
    critical: high
    unclassified: medium
  # Cells of the CISA table the organisation reads differently.
  mapping:
    - exploitation: [active]
      automatable: [no, yes]
      technicalImpact: [total]
      missionWellBeing: [medium, high]
      outcome: Act
  # Decide by rule, first match wins.
  overrides:
    - name: card data in scope
      when: labels['pci'] == 'in-scope' && severity in ['critical', 'high']
      outcome: Act
    - name: no fix and not reachable
      when: "!reachable && fixAvailability == 'no_fix'"
      outcome: Track
  outcomes:
    track:
      priority: low
      dueDays: 90
    trackStar:
      priority: medium
      dueDays: 60
    attend:
      priority: high
      dueDays: 14
    act:
      priority: immediate
      dueDays: 3
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 SsvcPolicy Always SsvcPolicy.
metadata object yes no other keys Metadata for a kind the tenant has exactly one of. The name is fixed to default.
metadata.name string yes exactly default; 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.exploitation object no no other keys When a finding counts as exploited. Active wins when both conditions hold; when neither does, exploitation is none.
spec.exploitation.active string no `kev exploitMaturity in [‘active’, ‘weaponized’]`
spec.exploitation.poc string no `exploitMaturity == ‘poc’ exploitCount > 0`
spec.automatable string no `cvss >= 9.0 epss >= 0.7`
spec.technicalImpact string no `cvss >= 9.0 malicious`
spec.missionWellBeing object no no other keys The mission and well-being value of a finding in a repository, by the repository’s criticality (RepositorySettings spec.criticality). unclassified is a repository with no criticality.
spec.missionWellBeing.low string no low low, medium, high Mission and well-being: the combination of mission prevalence and public well-being impact.
spec.missionWellBeing.medium string no medium low, medium, high Mission and well-being: the combination of mission prevalence and public well-being impact.
spec.missionWellBeing.high string no high low, medium, high Mission and well-being: the combination of mission prevalence and public well-being impact.
spec.missionWellBeing.critical string no high low, medium, high Mission and well-being: the combination of mission prevalence and public well-being impact.
spec.missionWellBeing.unclassified string no medium low, medium, high Mission and well-being: the combination of mission prevalence and public well-being impact.
spec.mapping array of object no 0 to 36 items Reads cells of the decision table differently. A rule matches every cell whose decision points are among the values it lists; a decision point it leaves out matches any value. When several rules match a cell, the last one wins. A rule changes a cell for the whole tenant: to decide by a property of the finding or its repository, use overrides.
spec.mapping[].exploitation array of string no 1 to any items; items unique; each item: none, poc, active
spec.mapping[].automatable array of string no 1 to any items; items unique; each item: no, yes
spec.mapping[].technicalImpact array of string no 1 to any items; items unique; each item: partial, total
spec.mapping[].missionWellBeing array of string no 1 to any items; items unique; each item: low, medium, high
spec.mapping[].outcome string yes Track, Track*, Attend, Act The SSVC decision. Track and Track* are remediated in the standard update cycle (Track* is watched more closely), Attend sooner, Act as soon as possible.
spec.overrides array of object no 0 to 50 items Decides the findings a condition holds for by rule, after the table and the mapping. The first rule that holds sets the outcome, so list the most specific first.
spec.overrides[].name string no length 0 to 100 Shown beside the decision when this rule set it.
spec.overrides[].when string yes length 1 to 4096 CEL over the finding variables, including the repository’s labels, owner and criticality.
spec.overrides[].outcome string yes Track, Track*, Attend, Act The SSVC decision. Track and Track* are remediated in the standard update cycle (Track* is watched more closely), Attend sooner, Act as soon as possible.
spec.outcomes object no no other keys What each outcome asks of the organisation. The priority is what a finding with that outcome is ranked as; dueDays is its remediation window, and 0 or left out means the standard update cycle.
spec.outcomes.track object no no other keys
spec.outcomes.track.priority string no low, medium, high, immediate low, medium, high or immediate. Defaults: Track low, Track* medium, Attend high, Act immediate.
spec.outcomes.track.dueDays integer no 0 0 to 365 The remediation window in days. 0 or left out is the standard update cycle.
spec.outcomes.trackStar object no no other keys
spec.outcomes.trackStar.priority string no low, medium, high, immediate low, medium, high or immediate. Defaults: Track low, Track* medium, Attend high, Act immediate.
spec.outcomes.trackStar.dueDays integer no 0 0 to 365 The remediation window in days. 0 or left out is the standard update cycle.
spec.outcomes.attend object no no other keys
spec.outcomes.attend.priority string no low, medium, high, immediate low, medium, high or immediate. Defaults: Track low, Track* medium, Attend high, Act immediate.
spec.outcomes.attend.dueDays integer no 0 0 to 365 The remediation window in days. 0 or left out is the standard update cycle.
spec.outcomes.act object no no other keys
spec.outcomes.act.priority string no low, medium, high, immediate low, medium, high or immediate. Defaults: Track low, Track* medium, Attend high, Act immediate.
spec.outcomes.act.dueDays integer no 0 0 to 365 The remediation window in days. 0 or left out is the standard update cycle.

CEL variables

spec.exploitation.active, spec.exploitation.poc, spec.automatable, spec.technicalImpact and spec.overrides[].when are CEL expressions in the finding context, returning bool. It is evaluated by the SSVC decision in the Vulnetix site API, on every decision and summary (the summary covers at most 5000 open findings, most severe first, cached for 60 seconds), and by the VDB API for the CLI scan and the remediation plan. The decision point takes its lower value and the decision is still made. An override that fails does not hold. 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.
  • Singleton. There is one per tenant, and its name is always default.

How the decision is made

Vulnetix makes the CISA SSVC decision, Track, Track*, Attend or Act, for a finding from four decision points. The decision table is the CISA Coordinator table 2.0.3 (CERT/CC) and is fixed. This kind configures how Vulnetix’s signals become the four values, which table cells the organization reads differently, which findings it decides by rule, and what each outcome asks of it.

Decision point Values Default
Exploitation none, poc, active active: kev || exploitMaturity in ['active', 'weaponized']. poc: exploitMaturity == 'poc' || exploitCount > 0. Active wins over poc.
Automatable no, yes cvss >= 9.0 || epss >= 0.7
Technical impact partial, total total: cvss >= 9.0 || malicious
Mission and well-being low, medium, high The repository’s criticality: low, medium, high, and critical (high). A repository with no criticality is medium.

The expressions are CEL over the finding context, so a policy can use any finding variable, including the repository’s labels, owner and criticality. The defaults approximate CISA’s definitions from the signals Vulnetix holds. Automatable in particular is defined by the kill chain, and the finding context carries no CVSS vector, so the default reads a critical score or a high EPSS as automatable. A tenant that holds better information tightens the expression. mapping[] reads cells of the table differently for the whole tenant (the last matching rule wins), overrides[] decides by rule after the table and the mapping (the first match wins, and the decision records which rule set it), and outcomes sets each outcome’s priority and dueDays (0 is the standard update cycle).

How apply treats it

  • A singleton, named default. The engine stores only what differs from the default policy, so a document that spells a default reads back without it.
  • Plan compiles every expression against the finding context and refuses unknown values. A stored policy that no longer compiles is not applied: the tenant decides with the default and the routes report a warning.
  • Delete deletes the row, which returns the tenant to the default policy.
  • It needs saas migration 20261228010000_ssvc_policy. Until it runs, a tenant decides with the default policy, and a write or an apply is refused naming the migration.
  • Decisions are made when they are read, from the same facts as the risk ranking, with the repository’s asset record. Nothing is stored per finding, so a policy change shows at once. The routes are GET, PUT and DELETE /v1/ssvc/policy, POST /v1/ssvc/decide and GET /v1/ssvc/summary.

In the CLI scan and the remediation plan

The VDB API decides with this policy in two more places: the CLI’s SCA response (POST /v2/cli/sca, the vulnetix:ssvc property of each vulnerability) and the remediation plan (GET /v2/vuln/{id}/remediation-plan, response.ssvc, which takes an optional ?repo=owner/name). Mission and well-being come from the repository’s criticality, and a repository with no asset record is decided as medium.

The policy applies only when the tenant has stored one. With no stored policy, the community credential, a database without the SsvcPolicy table, or a stored policy that no longer compiles, both keep the decision they made before policies existed, which is simpler and ignores criticality. A decision made under a policy adds vulnetix:ssvc:priority, :source, :exploitation, :automatable, :technicalImpact, :missionWellBeing, and :rule and :dueDays when they apply, to a scanned vulnerability, and policy, missionWellBeing, source, rule and dueDays to the plan’s ssvc block. The fields that existed keep their meaning.

Terraform and OpenTofu

The resource is vulnetix_ssvc_policy 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_ssvc_policy.

Import an object that already exists by its name (default):

# Import an existing SsvcPolicy by its name (metadata.name).
terraform import vulnetix_ssvc_policy.default default

Install, authentication and how state is read: Terraform and OpenTofu.

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/SsvcPolicy.schema.json, config-schemas/schemas/common.schema.json, config-schemas/examples/SsvcPolicy/minimal.yaml, config-schemas/examples/SsvcPolicy/advanced.yaml, config-schemas/cel/variables.go, terraform-provider-resolve/examples/resources/vulnetix_ssvc_policy/minimal.tf, terraform-provider-resolve/examples/resources/vulnetix_ssvc_policy/advanced.tf, vdb-site/docs/platform-iac-engine.md, vdb-site/api/internal/handler/config_engine_kinds.go, vdb-site/api/internal/config/ssvcpolicy.go, vdb-site/api/internal/handler/ssvc.go, vdb-site/docs/ssvc.md, config-schemas/ssvc/policy.go, config-schemas/ssvc/ssvc.go
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.