Evaluate policies
En esta página
- How it works#
- Use in CI#
- Examples#
- Health score#
- How the score is calculated#
- Built-in policies#
- Configure built-in policies#
- Configuration reference#
- Write custom policies#
- Metadata annotations#
- Output contract#
- Input and built-in functions#
- Debug policies#
- Inspect raw evaluation results#
- Share policies as OCI bundles#
- Publish a bundle#
- Use a bundle#
{{< summary-bar feature_name="Evaluate policies" >}}
docker scout policy lets you evaluate images against a configurable policy
set using the CLI. You can use the built-in defaults, adjust thresholds to
match your requirements, or write custom policies in
Rego.
How it works#
When you run docker scout policy, the CLI indexes the image into an SBOM,
enriches it with CVE and VEX data, then evaluates each configured policy
in-process. No data is sent to the Scout service, and an
organization is not required for most use cases.
Policies come from three sources, which can be combined:
- Built-in defaults: a curated set embedded in the CLI, used when no other source is given.
- OCI policy bundles: Rego packaged as an OCI artifact and pulled from a
registry with
--policy-bundle. - Local
.regofiles: for authoring and iterating on custom policies with--policy-fileor--policy-dir.
Use in CI#
Use the Docker Scout GitHub Action to evaluate policies as part of your workflow:
- name: Evaluate policies
uses: docker/scout-action@v1
with:
command: policy
image: ${{ env.IMAGE_NAME }}
organization: <ORG>
For other CI platforms, install the
Docker Scout CLI plugin on your runner and run
docker scout policy <image> --exit-code.
To gate a build on policy compliance compared to an environment, use the
compare command with a policy configuration:
- uses: docker/scout-action@v1
with:
command: compare
image: ${{ env.IMAGE_NAME }}
to-env: production
exit-on: policy
policy-config: policies.json
organization: <ORG>
See Configure built-in policies for the
policy-config file format.
Examples#
Evaluate an image against the built-in policy set:
$ docker scout policy myorg/app:latest
Use --exit-code to fail the pipeline when any policy is not met:
$ docker scout policy myorg/app:latest --exit-code
To customize which policies run and their thresholds, pass a policy-config file:
$ docker scout policy myorg/app:latest --policy-config policies.json
Policy results are also surfaced by docker scout quickview and
docker scout compare, which accept the same --policy-file,
--policy-dir, --policy-bundle, and --policy-config flags.
Other useful flags:
# Evaluate a specific platform for a multi-platform image
$ docker scout policy myorg/app:latest --platform linux/arm64
# Show results for a specific policy only
$ docker scout policy myorg/app:latest --only-policy "No copyleft licenses"
# Write the report to a file
$ docker scout policy myorg/app:latest --output report.txt
Health score#
docker scout policy, docker scout quickview, and docker scout compare also
report a health score alongside the policy results: a numeric percentage and an
A-F letter grade, computed entirely from the local policy evaluation. No data is
sent to the Scout service to compute it.
$ docker scout policy myorg/app:latest
...
Health score B (83%)
docker scout compare shows the score for both images:
Health score Analyzed B (83%) Comparison C (65%)
How the score is calculated#
The score is a weighted pass ratio:
- Each policy contributes a weight to the total, taken from its
custom.weightmetadata annotation (see Metadata annotations) or aweightoverride in the policy-config file. Policies without a declared weight default to10. - A policy that passes (reports zero violations) contributes its full weight to the scored points.
- A policy that fails still counts its weight toward the total, but contributes nothing to the scored points, which lowers the score.
- A policy with no data to evaluate (unknown) also counts toward the total without contributing to the scored points.
- A policy with a weight of
0is excluded from the score entirely.
The percentage is scored / total * 100, mapped to a letter grade using strict
greater-than thresholds:
| Score | Grade |
|---|---|
| > 90% | A |
| > 70% | B |
| > 50% | C |
| > 30% | D |
| > 10% | E |
| <= 10% | F |
Thresholds are exclusive of their lower bound, so an exact boundary value grades down: a score of exactly 90% is a B, not an A; exactly 70% is a C, and so on.
If every policy is excluded from the score (for example, all policies are
configured with weight: 0), no health score is shown at all.
To change a policy's contribution to the score, set custom.weight in the
policy's Rego metadata, or override it per policy in the policy-config file:
{
"policies": [
{
"name": "copyleft-license",
"weight": 0
}
]
}
Built-in policies#
The following policies are available by default:
| Policy | What it checks |
|---|---|
| No fixable critical or high vulnerabilities | Critical/high CVEs that have a fix available |
| No high-profile vulnerabilities | Curated list of well-known CVEs (Log4Shell, XZ backdoor, and others) |
| No copyleft licenses | Packages under AGPL, GPL, LGPL, MPL, and similar licenses |
| No outdated base images | Base image is behind the latest digest of its tag |
| Supply chain attestations | Provenance and SBOM attestations are attached |
| Default non-root user | Image is configured to run as a non-root user |
| No unapproved base images | Base image matches a configurable allowlist |
Configure built-in policies#
A JSON policy-config file controls which policies run and their thresholds.
Pass it with --policy-config.
{
"policies": [
{
"name": "fixable-vulnerabilities",
"config": {
"severities": ["CRITICAL"],
"grace_period_days": 14
}
},
{
"name": "no-stale-base-images",
"enabled": false
}
]
}
policies[].name: the policy's stable ID (see the following table).policies[].enabled: set tofalseto skip the policy. Policies not listed are enabled by default.policies[].weight: overrides the policy'scustom.weightmetadata annotation, which determines its contribution to the health score. A weight of0excludes the policy from the score. When omitted, the policy's own annotation is used, or10if it has none.policies[].config: an object passed to the policy asdata.config.
Configuration reference#
The following table lists the configurable keys for each built-in policy.
| Policy (stable ID) | config key | Default | Description |
|---|---|---|---|
fixable-vulnerabilities |
severities |
["CRITICAL","HIGH"] |
Severity levels that count as a violation |
fixable-vulnerabilities |
fixable_only |
true |
When true, only vulnerabilities with a known fix count |
fixable-vulnerabilities |
package_types |
[] |
Allowlist of PURL package types to consider; empty means all |
fixable-vulnerabilities |
grace_period_days |
0 |
Days a newly disclosed CVE is exempt |
high-profile-vulnerabilities |
cves |
Default high-profile CVEs | CVE IDs considered high-profile |
high-profile-vulnerabilities |
ignored_cves |
[] |
CVE IDs excluded from causing a failure |
high-profile-vulnerabilities |
include_cisa_kev |
true |
Also flag vulnerabilities in the CISA KEV catalog |
copyleft-license |
licenses |
AGPL/GPL/LGPL/MPL/… | SPDX license IDs treated as copyleft |
copyleft-license |
ignored_packages |
[] |
Package URLs exempted from the check |
approved-base-images |
allowed_base_images |
["*"] |
Glob patterns of allowed base image references |
approved-base-images |
allowed_distros_only |
true |
When enabled, base image must use an allowed OS distribution |
approved-base-images |
allowed_distros |
curated list | OS distributions considered allowed |
supply-chain-attestations |
required_attestations |
provenance and SBOM predicate types | Attestation predicate types that must be present |
Default high-profile CVEs#
The built-in cves list includes the following CVEs. Docker updates this list
as new high-profile vulnerabilities are disclosed.
| CVE ID | Common name |
|---|---|
| CVE-2014-0160 | Heartbleed |
| CVE-2014-6271 | Shellshock |
| CVE-2021-44228 | Log4Shell |
| CVE-2021-45046 | Log4j follow-up |
| CVE-2022-22965 | Spring4Shell |
| CVE-2023-38545 | curl SOCKS5 heap overflow |
| CVE-2023-44487 | HTTP/2 Rapid Reset |
| CVE-2024-3094 | XZ Utils backdoor |
To override the list, set cves in your policy-config file:
{
"policies": [
{
"name": "high-profile-vulnerabilities",
"config": {
"cves": ["CVE-2021-44228", "CVE-2024-3094"]
}
}
]
}
Write custom policies#
Policies are Rego modules in the docker.scout package. A policy declares a
boolean pass rule and a violation set. Use --policy-file for a single
file or --policy-dir to load a directory recursively.
# METADATA
# title: No packages from internal registry
# description: Flags packages sourced from registry.internal.example.com.
# custom:
# name: no-internal-registry
# result_type: generic
# weight: 5
# not_compliant_title: Packages from internal registry found
# details_order:
# - purl
# - reason
package docker.scout
import rego.v1
default pass := false
pass if {
count(violation) == 0
}
violation contains v if {
att := oci.referrer("https://scout.docker.com/sbom/v0.1")
some pkg in att.statement.predicate.artifacts
contains(pkg.purl, "registry.internal.example.com")
v := {
"message": sprintf("Package %s sourced from internal registry", [pkg.purl]),
"detail": {
"purl": pkg.purl,
"reason": "matches registry.internal.example.com",
},
}
}
# Single file
$ docker scout policy myorg/app:latest --policy-file ./no-internal-registry.rego
# Directory of policies
$ docker scout policy myorg/app:latest --policy-dir ./rego
# Custom policies combined with a config file
$ docker scout policy myorg/app:latest \
--policy-dir ./rego \
--policy-config ./policies.json
Both --policy-file and --policy-dir are repeatable. When either is
provided, the built-in defaults are not loaded automatically. To run both
built-in and custom policies together, publish the built-in set to a registry
with docker scout policy publish, then pass both bundles using
--policy-bundle:
$ docker scout policy myorg/app:latest \
--policy-bundle registry.example.com/default-policies:latest \
--policy-bundle registry.example.com/dhi-policies:latest \
--policy-file ./custom.rego
--policy-bundle is repeatable, so you can combine as many bundles as needed
alongside local policy files.
Metadata annotations#
The CLI reads OPA metadata annotations
to render results. Place them in a # METADATA block immediately above the
package declaration.
| Annotation | Purpose |
|---|---|
title |
Human-readable policy name shown in the report |
description |
Longer explanation |
custom.name |
Stable ID used to match --policy-config entries. Defaults to the package path if omitted |
custom.result_type |
How violations are rendered: vulnerability, license, boolean, or generic (default) |
custom.weight |
Scoring weight and display sort order. Higher weights sort first in the report and contribute more to the health score. Defaults to 10 when omitted; a weight of 0 excludes the policy from the score |
custom.not_compliant_title |
Status label shown when the policy fails |
custom.details_order |
Ordered list of detail keys to display as columns |
Output contract#
pass:truewhen the policy is met. The standard form ispass if { count(violation) == 0 }.violation: a set of objects. Each object should have amessagestring and adetailobject whose keys matchcustom.details_order. An optionalremediationstring is shown as remediation guidance.
Input and built-in functions#
The evaluation input is the enriched SBOM. Key entry points:
input.source.image: image metadata, includinginput.source.image.config.config.User,input.source.image.name, andinput.source.image.digest.data.config: the per-policyconfigobject from the policy-config file.
The following built-in functions are available in policy Rego:
| Function | Description |
|---|---|
oci.referrer(predicateType) |
Retrieve an attestation by predicate type |
oci.canonical_name(ref) |
Normalize an image reference, for example "node:25" to "docker.io/library/node" |
oci.image_digest(ref) |
Resolve the current digest of a tag from its registry |
oci.index(ref, digest) |
Get the OCI image index for an image |
oci.referrer_index(ref, digest) |
Get the OCI referrer index for an image |
oci.referrer_by_digest(ref, digest) |
Get a referrer for an image by digest |
scout.parse_purl(purl) |
Parse a PURL into its components |
scout.package_provenance(purl) |
Provenance of a package from the SBOM |
scout.vulnerabilities(purls) |
Vulnerabilities for the given PURLs |
scout.package_recommendation(purl) |
Recommended (fixed) version for a package |
scout.base_image() |
Base image matches recorded in the SBOM |
cosign.verify_dsse(envelope, opts) |
Verify an in-toto DSSE envelope's cosign signature |
cosign.verify_image(ref, opts) |
Verify an image's cosign signature |
gpg.verify_commit(...) |
Verify a detached commit signature |
Common predicate types for oci.referrer:
| Predicate type | Content |
|---|---|
https://scout.docker.com/vulnerabilities/v0.1 |
CVEs per package |
https://scout.docker.com/sbom/v0.1 |
SBOM artifacts, with purl and licenses |
https://scout.docker.com/provenance/v0.1 |
Build provenance, including base_image |
https://openvex.dev/ns/v0.2.0 |
VEX statements |
Debug policies#
Add print() statements to your Rego and enable debug output by setting
"debug": true in the policy-config file:
violation contains v if {
att := oci.referrer("https://scout.docker.com/sbom/v0.1")
some pkg in att.statement.predicate.artifacts
print("checking", pkg.purl)
contains(pkg.purl, blocked)
# ...
}
{
"debug": true,
"policies": [
{ "name": "no-internal-registry" }
]
}
Output is prefixed with the policy name and source line:
no-internal-registry#28: checking pkg:deb/debian/curl@7.88.1
[!NOTE]
The
debugfield in the policy-config controlsprint()output from your Rego. The global--debugflag is separate: it enables CLI-level debug logging for bundle loading, registry resolution, and similar internals.
Inspect raw evaluation results#
Use --result-file to write the full evaluation result for every policy to a
JSON file. This is useful when iterating on a custom policy to inspect
intermediate values.
$ docker scout policy myorg/app:latest \
--policy-file ./no-internal-registry.rego \
--result-file result.json
Each entry in the output contains:
pass: the policy's boolean outcome.violations: thedetailobject of each reported violation.bindings: the rawdata.docker.scoutdocument, including theviolationset and any other complete rules, for inspecting intermediate values.metrics: OPA evaluation metrics (timers and counters).
{
"no-internal-registry": {
"pass": false,
"violations": [
{ "purl": "pkg:deb/debian/curl@7.88.1", "reason": "matches \"registry.internal.example.com\"" }
],
"bindings": {
"blocked": "registry.internal.example.com",
"violation": [
{
"message": "Package pkg:deb/debian/curl@7.88.1 sourced from internal registry",
"detail": {
"purl": "pkg:deb/debian/curl@7.88.1",
"reason": "matches \"registry.internal.example.com\""
}
}
]
},
"metrics": {
"timer_rego_query_eval_ns": 1234567
}
}
}
Share policies as OCI bundles#
Package .rego files as an OCI artifact and distribute them through any
registry.
Publish a bundle#
# Publish a directory of policies
$ docker scout policy publish \
--policy-dir ./rego \
registry.example.com/my-policies:latest
# Publish specific files
$ docker scout policy publish \
--policy-file fixable.rego \
--policy-file licenses.rego \
registry.example.com/my-policies:latest
# Publish the built-in default set
$ docker scout policy publish registry.example.com/my-policies:latest
Each module's metadata is validated before publishing. The command prints the resulting digest and the list of bundled policies.
Use a bundle#
$ docker scout policy myorg/app:latest \
--policy-bundle registry.example.com/my-policies:latest
# Combine a bundle with local files and a config
$ docker scout policy myorg/app:latest \
--policy-bundle registry.example.com/my-policies:latest \
--policy-file ./extra.rego \
--policy-config ./policies.json
--policy-bundle is repeatable. Authentication uses your existing Docker
registry credentials. Bundles are cached by digest, so re-running against the
same bundle does not re-download it. A new digest (for example, after
re-publishing :latest) is fetched automatically.