Skip to content

cdso_config.yml

One file at the root of the application repository. The pipeline reads it on every run; the reviewer sees what it carries. It is the only file a developer edits to configure scanning and to record every justification.

Top level

deployment_level: "PRODUCTION"    # DEVELOPMENT, STAGING, PRODUCTION
security_group: "acme"            # the reviewing security group
organization: "acme"
software_name: "my-service"       # filing directory in the security group's scans project
Field Meaning
deployment_level Where the container will run. Changes the container spec gate: a root user passes at DEVELOPMENT and fails at PRODUCTION.
security_group The security group whose reviewers assess this application. Scan output publishes to that group's security repository.
organization The organization the application belongs to. With security_group and the container key, this is how Attest knows who owns the evidence.
software_name The directory the scan record files under in the security repository.

The container key

One block per container, keyed by the container name. It must match the name input on the review component in .gitlab-ci.yml. Where the name contains dashes, the YAML key uses underscores.

my_service:
  project_type: "container"          # container, tool, base
  dockerfile_folder: "."
  container_lifespan: "PERSISTENT"   # PERSISTENT or EPHEMERAL
  connection_context: "EXTERNAL"     # EXTERNAL (internet facing) or INTERNAL
Field Meaning
project_type container for an application, tool for a build or scan tool, base for an image others build on.
dockerfile_folder Where the Dockerfile is, relative to the repository root.
container_lifespan PERSISTENT for a long running service, EPHEMERAL for a job.
connection_context EXTERNAL for anything internet facing, INTERNAL otherwise. Reviewers weigh reachability against it.

Per scan blocks

Added under the container key once the first scan has reported. Each one is read by the pipeline and carried into the scan record verbatim.

Vulnerability mitigations

my_service:
  mitigations:
    - CVE_2025_12345: >-
        Why this image is not affected, the upgrade path, and what
        limits it in the meantime.

Key by the CVE id with dashes as underscores, never a GHSA id. The text is read verbatim by the reviewer. A Critical is never listed here; a High only when no fixed release exists. An entry that reads "Mitigation pending" or is empty is not ready for review.

Static analysis exclusions

my_service:
  semgrep:
    exclusions:
      <rule.id>: >-
        Why this rule does not apply to this image.

Use the map form, not a plain list, so the reason travels into the scan record.

Credential pattern exclusions

my_service:
  trufflehog:
    excluded_files:
      - tests/fixtures/sample_jwt.txt

Only for unverified matches. A verified live credential is never excluded; it is revoked, rotated, and removed.

Dockerfile ignores

my_service:
  hadolint:
    ignores:
      - DL3008  # why this line is deliberate

An ignore silences the rule for the whole Dockerfile, not one line.

Root user

my_service:
  container_spec:
    root_user: >-
      What needs root, what is done to limit it, and what would remove
      the need.

Accepted only at a level where root fails and only with a reason a reviewer can check.

Dynamic scan

my_service:
  zap:
    skip_reason: >-
      Why the dynamic scan does not apply, or the mitigation for each alert.

Rules the pipeline enforces

  1. The schema is validated on every run. A missing required field fails the run with the field named.
  2. Ownership is self declared: organization, security_group, and the container key say who owns the evidence, and Attest shows unassigned rather than guess when a field is missing.
  3. Access to a container's evidence in Attest is controlled by matching the signed in person's group claims against the declared organization and application.

Where Attest helps

The finding page for any justifiable finding shows the exact lines to add and which section they go in, and says whether the section already exists in the file. The text itself is the developer's to write.