JailPolicy
A jail policy: the ordered rules vulnetix jail evaluates for a repository over everything ever reported for it (vulnerabilities past their SLA. Fields, YAML, HCL and CLI samples, and how apply treats it.
A jail policy: the ordered rules vulnetix jail evaluates for a repository over everything ever reported for it (vulnerabilities past their SLA, dependencies past end of life, migrations that have not landed, hygiene categories that regressed). A policy without repository is the tenant’s default; a policy with one is a per-repository override that replaces the default outright. Separate from QualityGate, which judges a single scan.
apiVersion |
vulnetix.com/v1 |
kind |
JailPolicy |
| Group | Scanners and gates |
| 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 JailPolicy in its kinds. See Management tokens. |
| CEL fields | spec.rules[].when (finding): see CEL rule expressions |
| Terraform resource | vulnetix_jail_policy, imported by name (see Terraform and OpenTofu) |
| CLI | vulnetix config apply, CLI v3.157.0 and later. |
| Pinned schema | v1.15.0/JailPolicy.schema.json |
| Newest schema | latest/JailPolicy.schema.json |
| First released in | v1.1.0 |
| Print the built-in copy | vulnetix config schema JailPolicy |
Minimal
Only the fields the kind requires. From examples/JailPolicy/minimal.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/JailPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: JailPolicy
metadata:
name: org-default
spec: {}
HCL (resource vulnetix_jail_policy)
# Only the arguments the kind requires. From config-schemas examples/JailPolicy/minimal.yaml.
resource "vulnetix_jail_policy" "org_default" {
name = "org-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/JailPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: JailPolicy
metadata:
name: org-default
spec: {}
EOF
Advanced
Every field the kind supports, with a when expression where it takes one. From examples/JailPolicy/advanced.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/JailPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: JailPolicy
metadata:
name: payments-api
labels:
team: payments
annotations:
ticket: https://tracker.example.com/SEC-1980
spec:
repository: acme/payments-api
description: Payments ships with no overdue criticals, no live secrets, and moment.js on its way out.
enabled: true
enforcementMode: enforce
defaultStalenessDays: 7
onStale: fail
rules:
- kind: VULN
label: No reachable critical npm finding past its SLA
enabled: true
category: SCA
toolName: vulnetix
ecosystem: npm
severityFloor: critical
flags: [reachable, hasFix]
aggregate: COUNT_PAST_SLA
operator: GT
thresholdLow: 0
onBreach: BLOCK
stalenessDays: 3
onStale: warn
ratchet: false
when: "fixAvailability == 'available' && (kev || epss >= 0.1)"
- kind: GOAL
label: Off moment.js
targetPattern: pkg:npm/moment@*
aggregate: COUNT
operator: GT
thresholdLow: 0
deadline: 2027-03-31
ratchet: true
onBreach: WARN
- kind: HYGIENE
label: Secrets stay between 0 and 2
category: SECRETS
aggregate: COUNT
operator: OUTSIDE
thresholdLow: 0
thresholdHigh: 2
onStale: pass
HCL (resource vulnetix_jail_policy)
# Every argument the kind supports. From config-schemas examples/JailPolicy/advanced.yaml.
resource "vulnetix_jail_policy" "payments_api" {
name = "payments-api"
default_staleness_days = 7
description = "Payments ships with no overdue criticals, no live secrets, and moment.js on its way out."
enabled = true
enforcement_mode = "enforce"
on_stale = "fail"
repository = "acme/payments-api"
rules = [
{
aggregate = "COUNT_PAST_SLA"
category = "SCA"
ecosystem = "npm"
enabled = true
flags = ["reachable", "hasFix"]
kind = "VULN"
label = "No reachable critical npm finding past its SLA"
on_breach = "BLOCK"
on_stale = "warn"
operator = "GT"
ratchet = false
severity_floor = "critical"
staleness_days = 3
threshold_low = 0
tool_name = "vulnetix"
when = "fixAvailability == 'available' && (kev || epss >= 0.1)"
},
{
aggregate = "COUNT"
deadline = "2027-03-31"
kind = "GOAL"
label = "Off moment.js"
on_breach = "WARN"
operator = "GT"
ratchet = true
target_pattern = "pkg:npm/moment@*"
threshold_low = 0
},
{
aggregate = "COUNT"
category = "SECRETS"
kind = "HYGIENE"
label = "Secrets stay between 0 and 2"
on_stale = "pass"
operator = "OUTSIDE"
threshold_high = 2
threshold_low = 0
},
]
labels = {
team = "payments"
}
annotations = {
ticket = "https://tracker.example.com/SEC-1980"
}
}
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/JailPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: JailPolicy
metadata:
name: payments-api
labels:
team: payments
annotations:
ticket: https://tracker.example.com/SEC-1980
spec:
repository: acme/payments-api
description: Payments ships with no overdue criticals, no live secrets, and moment.js on its way out.
enabled: true
enforcementMode: enforce
defaultStalenessDays: 7
onStale: fail
rules:
- kind: VULN
label: No reachable critical npm finding past its SLA
enabled: true
category: SCA
toolName: vulnetix
ecosystem: npm
severityFloor: critical
flags: [reachable, hasFix]
aggregate: COUNT_PAST_SLA
operator: GT
thresholdLow: 0
onBreach: BLOCK
stalenessDays: 3
onStale: warn
ratchet: false
when: "fixAvailability == 'available' && (kev || epss >= 0.1)"
- kind: GOAL
label: Off moment.js
targetPattern: pkg:npm/moment@*
aggregate: COUNT
operator: GT
thresholdLow: 0
deadline: 2027-03-31
ratchet: true
onBreach: WARN
- kind: HYGIENE
label: Secrets stay between 0 and 2
category: SECRETS
aggregate: COUNT
operator: OUTSIDE
thresholdLow: 0
thresholdHigh: 2
onStale: pass
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 JailPolicy |
Always JailPolicy. | |
metadata |
object | yes | no other keys | metadata.name is stored as the policy’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.repository |
string | no | pattern ^[^\s/]+(/[^\s/]+)+$ |
The repository this policy overrides the default for. Leave it out for the tenant default; a tenant has at most one default and one policy per repository. | |
spec.description |
string | no | |||
spec.enabled |
boolean | no | true |
||
spec.enforcementMode |
string | no | enforce |
enforce, warn |
enforce applies each rule’s onBreach. warn downgrades every breach to a warning, which disarms the jail without a CLI release. |
spec.defaultStalenessDays |
integer | no | 7 |
0 to 365 | How old the evidence a rule reads may be, in days, for rules that do not set stalenessDays. |
spec.onStale |
string | no | fail |
fail, warn, pass |
What a rule does when the evidence it needs is stale or absent, for rules that do not set their own. fail is the default: a gate that passes on missing evidence is worse than no gate. |
spec.rules |
array of object | no | The rules, in order. Every enabled rule is evaluated on every run; order decides presentation and which rule gives the headline reason, and never short-circuits. Rules are matched to stored rules by label, so labels must be unique within a policy. | ||
spec.rules[].kind |
string | yes | VULN, EOL, GOAL, HYGIENE |
VULN: vulnerability findings. EOL: end-of-life dependencies. GOAL: a migration target, selected by targetPattern. HYGIENE: findings from the other scanner categories. | |
spec.rules[].label |
string | yes | length 1 to any | The rule’s name in jail output and the console, and its key within the policy. | |
spec.rules[].enabled |
boolean | no | true |
||
spec.rules[].category |
string | no | SCA, SAST, SECRETS, IAC, OCI, LICENSE, MALWARE |
Only findings from this scanner category. A HYGIENE rule without one draws from SECRETS, LICENSE, IAC, OCI and MALWARE. | |
spec.rules[].toolName |
string | no | length 1 to any | Only findings reported by this tool. | |
spec.rules[].ecosystem |
string | no | length 1 to any | Only dependencies from this package ecosystem. | |
spec.rules[].severityFloor |
string | no | low, medium, high, critical |
Only findings at or above this severity. | |
spec.rules[].flags |
array of string | no | items unique; each item: kev, malicious, hasFix, reachable, direct |
Every flag listed must hold for a finding to count. kev: in a known-exploited catalogue. malicious: known malware. hasFix: a fixed version exists. reachable: the vulnerable code is called. direct: a direct dependency. | |
spec.rules[].targetPattern |
string | no | length 1 to any | GOAL only, and required there: a path.Match glob over the identifier of each fact (a purl, a package name, a rule id). | |
spec.rules[].aggregate |
string | yes | COUNT, COUNT_PAST_SLA, MAX_AGE_DAYS, PERCENT |
COUNT: how many. COUNT_PAST_SLA: how many are past the remediation window of the tenant’s RemediationPolicy (VULN only). MAX_AGE_DAYS: the age of the oldest, in days. PERCENT: the share of the population, 0 to 100. | |
spec.rules[].operator |
string | yes | GT, GTE, LT, LTE, EQ, NEQ, BETWEEN, OUTSIDE |
How the aggregate is compared with the thresholds. BETWEEN and OUTSIDE use both thresholds, inclusive. | |
spec.rules[].thresholdLow |
number | yes | The threshold, or the lower bound for BETWEEN and OUTSIDE. | ||
spec.rules[].thresholdHigh |
number | no | The upper bound for BETWEEN and OUTSIDE; required by them. It must not be below thresholdLow. | ||
spec.rules[].deadline |
string | no | format date-time, or format date |
GOAL only. Until this time a breach is downgraded to a warning, whatever onBreach says. | |
spec.rules[].ratchet |
boolean | no | false |
GOAL only. Breach when the value rose since the last assessment of the same rule on the same branch, so a migration holds its ground before the deadline. With no earlier assessment the value is recorded and the rule passes. | |
spec.rules[].onBreach |
string | no | BLOCK |
BLOCK, WARN |
|
spec.rules[].stalenessDays |
integer | no | 0 to 365 | Overrides the policy’s defaultStalenessDays for this rule. | |
spec.rules[].onStale |
string | no | fail, warn, pass |
Overrides the policy’s onStale for this rule. | |
spec.rules[].when |
string | no | length 1 to 4096 | An extra condition each fact must meet to be counted, over the finding variables. A when that does not compile, or that uses a finding variable the facts of the rule kind do not carry yet, skips the rule with a warning. A fact it fails on leaves the rule indeterminate (verdict indeterminate, CLI exit 3). The factory gate fails closed instead: the rule breaches. |
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 operator is one of BETWEEN, OUTSIDE: thresholdHigh is required. |
spec.rules[] |
When aggregate is COUNT_PAST_SLA: kind must be VULN. |
spec.rules[] |
When kind is GOAL: targetPattern is required. Otherwise: ratchet must be false and deadline is not allowed. |
CEL variables
spec.rules[].when is a CEL expression in the finding context, returning bool. It is evaluated by the jail evaluator in the VDB API, which vulnetix jail and vulnetix scan --jail call, and by the Software Factory gate node for its COUNT rules. In the VDB API a rule whose when does not compile, or uses a variable its rule kind lacks, is skipped with a warning, and a fact the expression errors on makes the rule indeterminate (vulnetix jail exits 3). In the factory gate the rule fails closed: it breaches, labelled “when could not be evaluated”. 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. |
Jail evaluates when over the facts it already holds, and those do not carry every finding variable. A rule whose when names a variable its kind does not fill in is skipped with a warning when the jail gate runs, rather than counting against a zero value:
Rule kind |
Variables when may use |
|---|---|
VULN |
id, severity, cvss, kev, malicious, reachable, fixAvailability, direct, purl, ecosystem, labels, owner, criticality, now |
EOL |
id, severity, eol, direct, purl, ecosystem, labels, owner, criticality, now |
HYGIENE |
id, severity, cvss, labels, owner, criticality, now |
GOAL |
id, labels, owner, criticality, now |
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
- A policy is addressed by
metadata.name. The header fields merge: a field the document leaves out keeps its value. spec.repositoryis the scope, so leaving it out means the tenant default.spec.rules, when present, is the whole ordered list. Rules are matched to stored rules bylabeland updated in place, which keeps each rule’s id (exemptions and the assessment history cite it). A label the document drops is deleted, and renaming a label is a delete and a create. Labels must be unique within a policy.- Plan runs the console’s policy and rule validators, so a policy the console refuses cannot be applied as code. A
whenthat does not compile is reported with its rule and position.
Terraform and OpenTofu
The resource is vulnetix_jail_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_jail_policy.
Import an object that already exists by its name:
# Import an existing JailPolicy by its name (metadata.name).
terraform import vulnetix_jail_policy.org_default org-default
Install, authentication and how state is read: Terraform and OpenTofu.
More examples
examples/JailPolicy/org-default.yaml from Vulnetix/config-schemas:
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/JailPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: JailPolicy
metadata:
name: org-default
spec:
description: Nothing ships with overdue criticals, retired runtimes or leaked secrets.
enforcementMode: enforce
defaultStalenessDays: 7
onStale: fail
rules:
- kind: VULN
label: No critical past its SLA
severityFloor: critical
aggregate: COUNT_PAST_SLA
operator: GT
thresholdLow: 0
- kind: VULN
label: Reachable KEV findings
flags: [kev, reachable]
aggregate: COUNT
operator: GT
thresholdLow: 0
when: epss >= 0.1
- kind: EOL
label: Retired runtimes
aggregate: MAX_AGE_DAYS
operator: GT
thresholdLow: 30
onBreach: WARN
- kind: HYGIENE
label: No live secrets
category: SECRETS
aggregate: COUNT
operator: GT
thresholdLow: 0
stalenessDays: 1
examples/JailPolicy/repo-goal.yaml from Vulnetix/config-schemas:
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/JailPolicy.schema.json
apiVersion: vulnetix.com/v1
kind: JailPolicy
metadata:
name: payments-api
spec:
repository: acme/payments-api
enforcementMode: warn
rules:
- kind: GOAL
label: Off moment.js
targetPattern: pkg:npm/moment@*
aggregate: COUNT
operator: GT
thresholdLow: 0
deadline: 2027-03-31
ratchet: true
- kind: VULN
label: High findings stay between 0 and 5
severityFloor: high
aggregate: COUNT
operator: OUTSIDE
thresholdLow: 0
thresholdHigh: 5
Check a file with vulnetix config validate -f <file>. See Validate documents. In an editor, start the file with the schema comment.