Skip to content

Writing a justification

A justification is a written explanation of why a particular finding does not need to be fixed before this build ships. You write it in cdso_config.yml in your repository, keyed to the specific finding, and the pipeline carries it into the scan record the reviewer reads. The reviewer sees your text exactly as you wrote it, so write it for a person.

What can and cannot be justified

Finding Can it be justified? Where it goes
Critical CVE No. It has to be fixed.
High CVE with a fix available No. The reviewer will refuse it.
High CVE with no fixed release Yes, but expect the reviewer to look closely. mitigations
Medium or Low CVE Yes. mitigations
Static analysis finding Yes, by rule id. semgrep.exclusions
Unverified credential pattern Yes, by file. trufflehog.excluded_files
Verified live credential No. Revoke it, rotate it, remove it.
Malware signature match No.
Dockerfile lint error Yes, by rule code. hadolint.ignores
Root user at PRODUCTION Yes, with a reason a reviewer can check. container_spec.root_user
Dynamic scan alert Yes. zap.skip_reason

What a reviewer is looking for

A reviewer reading a justification wants answers to three questions, in this order.

  1. Why is this image not affected? For example, the vulnerable function is never called, the package is present but the feature is disabled, or the port is not exposed.
  2. What is the upgrade path? When is a fix expected, or why is none coming?
  3. What limits the risk in the meantime? Network policy, a non root user, a read only filesystem, and so on.

Two or three sentences that answer those questions are better than a long paragraph that does not.

Examples

A vulnerability with no fixed release:

my_service:
  mitigations:
    - CVE_2025_12345: >-
        The affected function is in the XML parser, which this service
        never loads; requests are JSON only and the parser module is not
        imported. No fixed release exists upstream as of this build. The
        container runs as a non root user with no outbound network.

A static analysis rule that does not apply:

my_service:
  semgrep:
    exclusions:
      python.lang.security.audit.subprocess-shell-true: >-
        The single match is in scripts/local_dev.py, which is not copied
        into the image; the Dockerfile copies src/ only.

An unverified credential pattern in a test fixture:

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

Things the pipeline checks

  1. CVE keys use the CVE id with dashes replaced by underscores (CVE_2025_12345). GHSA ids are not accepted.
  2. The container key uses underscores where the container name has dashes.
  3. Exclusions use the map form shown above so the reason is carried into the record. A plain list carries no reason.
  4. A mitigations entry that is empty or reads "Mitigation pending" is treated as not ready, and the submission waits until it is written.

How Explainable AI helps

The finding page drafts a starting point from the evidence: what the scanner found, where, and what it would take to clear it. Use it to save time, but the words that go into the file are yours and the reviewer will hold you to them.

What the reviewer sees

Each justification appears beside the finding it covers, word for word, along with the CVE table from the scan record. Excluding a finding hides it from the gate, not from the reviewer. They see every exclusion and the reason you gave.