Skip to content

Container Image Security Scanner

The Container Image Security Scanner (scanner-container) is a first-class VirtualSutra agent that scans container images for CVEs, OS compliance violations, and generates Software Bills of Materials (SBOMs). It is dispatched directly by the orchestrator - one task per ContainerImageConfig in the inspection scope - and runs in parallel with cloud and codebase scanner tasks.

Availability: Pro and Enterprise · Tenant-scoped

See the Tier & Scope Matrix for how this is enforced today.

Coordinator image: ghcr.io/virtual-sutra/scanner-container
Redis stream: vsutra:tasks:container_scanner
AgentType: CONTAINER_SCANNER

In Kubernetes, the coordinator spawns a multi-container Job using dedicated sub-images - each tool runs in its own container, sharing the pulled image layer via an emptyDir volume:

Sub-image Tool Capability
ghcr.io/virtual-sutra/scanner-pull Skopeo Image pull (init container)
ghcr.io/virtual-sutra/scanner-trivy Trivy CVE scanning (N1)
ghcr.io/virtual-sutra/scanner-syft Syft SBOM generation (N3)
ghcr.io/virtual-sutra/scanner-openscap OpenSCAP + SSG OS compliance (N2, opt-in)

Capabilities

Track Capability Tools Status
N1 CVE scanning Trivy Implemented
N2 OS compliance OpenSCAP + SCAP Security Guide + umoci Implemented (opt-in)
N3 SBOM generation Syft (CycloneDX) Implemented

All image pulls use Skopeo for daemon-free operation - no Docker socket is required.


Prerequisites

The following tools must be present in the scanner container image. They are included in the official ghcr.io/virtual-sutra/scanner-container image.

N1 - CVE Scanning (required)

  • Skopeo >= 1.13 - daemon-free image pull to OCI layout
  • Trivy >= 0.50 - CVE and misconfiguration scanning

N2 - OS Compliance (opt-in, enable_openscap: true)

  • OpenSCAP >= 1.3 (openscap-scanner package)
  • SCAP Security Guide (SSG) - scap-security-guide package
  • umoci >= 0.4 - extracts OCI image filesystem for oscap-chroot evaluation

N2 is disabled by default (enable_openscap: false) because image extraction is slow. Enable it per image via the ContainerImageConfig. See OS compliance findings for the operating systems it assesses and how to read its results.

N3 - SBOM Generation (required)

  • Syft >= 1.0 - generates CycloneDX JSON SBOM

Configuration

Container images to scan are declared in InspectionConfig.scope.container_images as a list of ContainerImageConfig objects.

{
  "scope": {
    "container_images": [
      {
        "registry": "ghcr.io",
        "repository": "my-org/my-app",
        "tag": "v1.2.3",
        "platforms": ["linux/amd64", "linux/arm64"],
        "enable_trivy": true,
        "enable_openscap": false,
        "enable_sbom": true,
        "license_blocklist": ["GPL-3.0", "AGPL-3.0"],
        "credentials_ref": "env://REGISTRY_PASSWORD",
        "username_ref": "env://REGISTRY_USERNAME"
      }
    ]
  }
}

ContainerImageConfig fields

Field Type Default Description
registry string required Registry hostname (e.g. ghcr.io, docker.io, 123456789.dkr.ecr.us-east-1.amazonaws.com)
repository string required Repository path (e.g. my-org/my-app)
tag string "latest" Image tag or digest
platforms list[string] ["linux/amd64"] Target platforms to scan
enable_trivy bool true Run Trivy CVE scan
enable_openscap bool false Run OpenSCAP OS compliance scan (slow - opt-in)
openscap_profile string cis Benchmark profile: cis or stig (STIG on Ubuntu and RHEL only)
enable_sbom bool true Generate CycloneDX SBOM with Syft
license_blocklist list[string] [] SPDX license identifiers that produce a finding when found in the SBOM
credentials_ref string null Secret ref for registry password (e.g. env://REGISTRY_PASSWORD, aws_sm://my-secret)
username_ref string null Secret ref for registry username

Registry Authentication

Public registries

No credentials needed. Leave credentials_ref and username_ref unset.

Private registries (generic)

Set REGISTRY_USERNAME and REGISTRY_PASSWORD environment variables on the scanner container, or use per-image credentials_ref / username_ref secret references.

REGISTRY_USERNAME=myuser
REGISTRY_PASSWORD=mypassword

AWS ECR

Use standard AWS IAM credentials. Authenticate with the ECR auth helper or pre-authenticate:

aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com

Or assign the AmazonEC2ContainerRegistryReadOnly IAM policy to the scanner's execution role.

Azure ACR

Create a service principal with the AcrPull role:

az ad sp create-for-rbac --name vsutra-scanner \
  --role AcrPull \
  --scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/<acr>

Store the password in a secret and reference it via credentials_ref.

GCP Artifact Registry

Create a service account with roles/artifactregistry.reader and provide the JSON key:

gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:vsutra-scanner@PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.reader"

Store the JSON key content in a secret and reference it via credentials_ref.


Findings

CVE findings (N1 - Trivy)

Field Value
resource_type container:image
tags.cve_id CVE identifier (e.g. CVE-2024-12345)
tags.package Affected package name and version
tags.fixed_version Version that fixes the CVE (if available)
tags.scanner "trivy"
Compliance mappings CIS Docker Benchmark, NIST SP 800-190

OS compliance findings (N2 - OpenSCAP)

The benchmark is chosen from the image's own /etc/os-release:

Image OS cis profile stig profile
Debian 12, Debian 13 CIS Level 1 Server not supported
Ubuntu 22.04, Ubuntu 24.04 CIS Level 1 Server STIG
RHEL / UBI 8, 9, 10 CIS Level 1 Server STIG

RHEL / UBI benchmarks read the image's RPM database. OpenSCAP does that only by chroot(), which the scanner (non-root, no capabilities) is not permitted. Such an image is therefore not evaluated in this release: it gets CKV_VSUTRA_IMG_OSCAP_2, and the OpenSCAP sub-scan fails as a coverage gap. Support for RHEL / UBI images is planned. Every other identified OS is not supported for OS compliance: Alpine, Debian 11, Ubuntu 20.04, Rocky, AlmaLinux, CentOS Stream, Amazon Linux, Oracle Linux, SLES, Fedora, and STIG on Debian. An image whose OS cannot be identified (no os-release, such as scratch or static images) fails the sub-scan.

An assessed image gets CKV_VSUTRA_IMG_OSCAP_1, plus CKV_VSUTRA_IMG_OSCAP_2 when some of its rules were not evaluated; an unsupported one gets CKV_VSUTRA_IMG_OSCAP_0; an image whose OS cannot be identified gets none. All are informational:

check_id When Tags
CKV_VSUTRA_IMG_OSCAP_1 The benchmark ran. Always present for an assessed image openscap_status: assessed; rule counts (rules_evaluated, rules_passed, rules_failed, rules_undecided, rules_set_aside and one rules_set_aside_<class> per set-aside class below, rules_not_evaluated, rules_not_run_by_oscap, rules_evaluated_by_oscap); profile_id, benchmark, benchmark_version
CKV_VSUTRA_IMG_OSCAP_0 The OS is not supported openscap_status: not_supported, os_id, os_version
CKV_VSUTRA_IMG_OSCAP_2 Rules were not evaluated because their checks need chroot(): the whole benchmark (RHEL / UBI) or single rules. The sub-scan fails openscap_status: not_evaluated, not_evaluated_reason (rpm_database_needs_chroot or probe_needs_chroot), rules_selected, rules_evaluated, rules_not_evaluated

Every rule that failed and could be judged on an image is one finding:

Field Value
resource_type container:os
check_id The XCCDF rule id, verbatim, e.g. xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile. It is the same on every supported OS
title The benchmark's rule title, prefixed OS compliance:
severity The rule's XCCDF severity (info → informational, unknown → low)
compliance_controls The controls the rule's own references cite (CIS section, NIST 800-53, PCI DSS, and others)
remediation_steps The benchmark's shell remediation, when it has one. It is written for a running host: apply it where the image is built
tags rule_id, result, xccdf_severity, profile (cis / stig), profile_id, benchmark, benchmark_version, platform, scanner: "openscap"

A rule OpenSCAP could not decide is a finding titled "OS compliance check could not be evaluated: …", at medium severity for error and informational for unknown. It is not a failure of the rule.

These are results for an image, not for a host. The image is unpacked without root and nothing is running in it. Four classes of rule are therefore set aside: they are neither passes nor failures, are counted on the summary, and their controls are recorded as not evaluated:

  • file_ownership: every unpacked file belongs to the scanner's own user.
  • host_state: kernel parameters, services, mounts, the bootloader and similar.
  • script_check: rules answered by a script that would run in the scanner, not in the image.
  • not_checked: the benchmark ships no check for the rule.

0 failed on the summary never means "compliant": the set-aside rules have to be assessed on the host or cluster the image runs on.

OpenSCAP never downloads remote content, on any deployment profile. A rule whose check needs remote content is set aside as not checked, and its controls are recorded as not evaluated. In the shipped content (SSG 0.1.82), the only such rule is Debian's security-patch check, and no profile the scanner runs selects it.

License violation findings (N3 - Syft)

Field Value
resource_type package:{type} (e.g. package:npm, package:pip)
severity medium
tags.license SPDX license identifier
tags.package Package name and version
tags.scanner "syft"

SBOM Artifacts

CycloneDX JSON SBOMs are written to SBOM_OUTPUT_DIR/{run_id}/{image_slug}.cdx.json (default: /tmp/vsutra-sboms/).

SBOMs are accessible via the API:

  • GET /runs/{run_id}/sbom - list available SBOM files for a run
  • GET /runs/{run_id}/sbom/{filename} - download a specific CycloneDX SBOM file

Environment Variables

Variable Default Description
REDIS_URL required Redis connection URL
REGISTRY_USERNAME - Default registry username (overridden per image by username_ref)
REGISTRY_PASSWORD - Default registry password (overridden per image by credentials_ref)
SBOM_OUTPUT_DIR /tmp/vsutra-sboms Directory for SBOM artifact files
SCAP_CONTENT_PATH /usr/share/xml/scap/ssg/content Directory holding the ssg-<product>-ds.xml datastreams; the one for the image's OS is chosen per image. A value that is not a directory (the old single-file form) fails the sub-scan
SCANNER_SPAWNER_BACKEND kubernetes Spawner backend for sub-scanner Jobs
SPAWNER_JOB_TTL_SECONDS 600 TTL for completed scan Jobs
SPAWNER_K8S_NAMESPACE release namespace Namespace for spawned Jobs
SPAWNER_SCANNER_CONTAINER_PULL_IMAGE ghcr.io/virtual-sutra/scanner-pull:latest Override pull sub-image
SPAWNER_SCANNER_CONTAINER_TRIVY_IMAGE ghcr.io/virtual-sutra/scanner-trivy:latest Override Trivy sub-image
SPAWNER_SCANNER_CONTAINER_SYFT_IMAGE ghcr.io/virtual-sutra/scanner-syft:latest Override Syft sub-image
SPAWNER_SCANNER_CONTAINER_OPENSCAP_IMAGE ghcr.io/virtual-sutra/scanner-openscap:latest Override OpenSCAP sub-image
LOG_LEVEL INFO Logging level

Resource Types Summary

resource_type Source
container:image Trivy CVE findings
container:os OpenSCAP: one finding per failing rule, plus the status records CKV_VSUTRA_IMG_OSCAP_0, _1 and _2 described above
package:npm Syft license violation (npm package)
package:pip Syft license violation (Python package)
package:go Syft license violation (Go module)
package:java Syft license violation (Java/Maven package)