sarif-dockerfile-anchor

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: Apache-2.0

README

sarif-dockerfile-anchor

Anchor Microsoft Defender Vulnerability Management (MDVM) container-image SARIF findings to the Dockerfile lines that introduced the vulnerable packages, so GitHub code scanning renders them as pull-request inline annotations and diff gates — the same experience CodeQL gives on source code.

Defender for Cloud's container scan reports every finding at the image reference (for example myregistry.azurecr.io/app line 1). GitHub code scanning can only annotate a pull request on a changed line of a file in the repository, so those findings never appear inline on the PR diff. This tool rewrites each OS-package finding's location to the Dockerfile instruction that introduced the package.

Generalized, reusable port of an internal remap script. Single tool, no extra services; reads the SARIF + a CycloneDX SBOM + your Dockerfile.

How it classifies findings

Finding kind Decided by Anchored to
Injected OS package name appears in the Dockerfile as a <name>_…deb filename or <name>= apt pin the install/download line (any severity)
Transitive OS package not named in the Dockerfile, but in the SBOM dependency closure of an injected package the nearest such install line (any severity; --link-transitive, default on)
Base-image OS package OS package not present in the Dockerfile (and no injected ancestor) the final-stage FROM line (kept only for --base-severity)
Application / language package SBOM purl type is not an OS/distro type (e.g. pkg:maven/…, pkg:npm/…) left at the image reference (Dependabot / CodeQL territory)
  • OS vs application is decided from the CycloneDX SBOM purl type: the distro/system types deb, rpm, apk, alpm, qpkg, yocto (purl spec) are treated as OS packages; everything else (maven, npm, pypi, golang, nuget, conda, conan, generic, …) is application/language.
  • Base-image findings anchor to the Dockerfile's final-stage FROM (the last FROM), since the scanned image is always built from that stage — no base image needs to be supplied.
  • Transitive anchoring (--link-transitive, default on) walks the CycloneDX dependencies graph: an OS finding not named in the Dockerfile is anchored to the install line of the nearest package that pulled it in, instead of the FROM line. A package in an installed package's dependency closure is thus attributed to that install line — including shared base libraries it depends on. Pass --link-transitive=false to keep the base-FROM behaviour.
  • A stable partialFingerprints (sha1(ruleId + package)) keeps alerts from churning across re-runs.

The original SARIF is never modified: the enriched document is written to stdout (or --output) and a one-line summary goes to stderr.

Install

Download a prebuilt static binary from the releases page, or build from source:

go install github.com/fuj1g0n/sarif-dockerfile-anchor/cmd/sarif-dockerfile-anchor@latest

CLI usage

The input files come from the Defender for Cloud CLI (see Inputs from the Defender CLI):

sarif-dockerfile-anchor \
  --sarif        image.sarif \
  --sbom         sbom.cyclonedx.json \
  --dockerfile   Dockerfile \
  --output       image.enriched.sarif
# summary (injected / transitive / base / left-at-image counts) is printed to stderr
Flag Required Default Description
--sarif yes Defender CLI image-scan SARIF
--sbom yes CycloneDX SBOM JSON (OS/app classification via purl)
--dockerfile yes Dockerfile to anchor findings to
--base-severity no high,critical severities of base-image OS findings kept inline
--link-transitive no true anchor a transitive OS finding to the install line of the nearest package (per the SBOM dependency graph) that pulled it in; false keeps the base-FROM behaviour
--dockerfile-uri no value of --dockerfile repo-relative URI written into the SARIF; override only when the file read differs from its committed path (e.g. an absolute --dockerfile, or a generated/rendered Dockerfile)
--output no stdout where to write the enriched SARIF
Inputs from the Defender CLI

The Defender for Cloud CLI produces these files (verified with CLI v2.0.3334.114):

# Export the locally-built image to a docker-archive tar first. The Defender
# CLI resolver only accepts a local artifact (OCI layout dir / tar) or a
# pullable registry reference; a bare local-daemon tag logs
# "pkg/resolver : No suitable resolver found". A tar resolves cleanly.
docker save "$IMAGE" -o image.tar

# Image scan (--scanner mdvm): vulnerability findings from Microsoft Defender
# Vulnerability Management. --defender-output names its scan SARIF (default:
# defender.sarif in the working directory).
defender scan image image.tar --scanner mdvm     --defender-output image.sarif

# SBOM scan (--scanner mdvmsbom): --defender-output names its scan SARIF, which
# reports malicious packages (same default defender.sarif). --output names the
# CycloneDX SBOM file with OS/app purls used for classification (default
# sbom-finding-<timestamp>.json; --sbom-format default cyclonedx1.6-json).
defender scan sbom  image.tar --scanner mdvmsbom --defender-output sbom.sarif --output sbom.cyclonedx.json

[!IMPORTANT] defender scan image and defender scan sbom BOTH default their scan SARIF to defender.sarif in the working directory (the --defender-output default). Run in the same directory without distinct names, they overwrite each other — the SBOM scan (which reports malicious packages, usually none) would clobber the image scan's vulnerabilities with an empty SARIF. Give each scan a distinct --defender-output (image.sarif / sbom.sarif) and pin the scanner explicitly — --scanner mdvm for the image scan (vulnerabilities) and --scanner mdvmsbom for the SBOM scan — so the SARIF content stays deterministic regardless of CLI default changes or invocation order.

[!NOTE] The exported image SARIF holds Critical/High/Medium findings; Low-severity findings are excluded by default in the tested CLI version. All findings are located at the image reference (line 1) — which is exactly what this tool remaps.

GitHub Actions usage

- name: Defender for Cloud image scan + SBOM
  run: |
    # Scan a docker-archive tar (docker save), not the bare local-daemon tag:
    # the resolver only accepts a local artifact (tar / OCI dir) or a pullable
    # registry ref, so a bare tag logs "No suitable resolver found".
    # Pin the scanner explicitly and give each scan a distinct --defender-output;
    # both scan SARIFs default to defender.sarif and would otherwise overwrite
    # each other regardless of order.
    docker save "$IMAGE" -o image.tar
    ./defender scan image image.tar --scanner mdvm     --defender-output image.sarif
    ./defender scan sbom  image.tar --scanner mdvmsbom --defender-output sbom.sarif --output sbom.cyclonedx.json

- name: Anchor MDVM SARIF to Dockerfile
  id: anchor
  uses: fuj1g0n/sarif-dockerfile-anchor@v1
  with:
    sarif: image.sarif
    sbom: sbom.cyclonedx.json
    dockerfile: Dockerfile
    output: image.enriched.sarif
    # link-transitive: true  # (default) anchor transitive OS deps to their
    #                        # introducing install line; false keeps base-FROM

- name: Upload to code scanning
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: ${{ steps.anchor.outputs.sarif }}
    category: defender-mdvm

When pinned to a release tag (@vX.Y.Z) the composite action downloads the matching prebuilt binary for the runner's OS/architecture. When pinned to any other ref (@main, a branch, or a SHA) it builds the binary from the action source at that exact commit, using the same devbox-pinned Go toolchain as CI, so behaviour is tied to the pinned commit. No Python or other runtime is required.

Development

This repo uses devbox for a reproducible Go toolchain:

devbox run -- go test ./...
devbox run -- go build ./...

License

Apache-2.0

Directories

Path Synopsis
cmd
sarif-dockerfile-anchor command
Command sarif-dockerfile-anchor rewrites Microsoft Defender (MDVM) container-image SARIF so that OS-package findings are anchored to the Dockerfile lines that introduced the packages, enabling GitHub code scanning pull-request inline annotations and diff gates.
Command sarif-dockerfile-anchor rewrites Microsoft Defender (MDVM) container-image SARIF so that OS-package findings are anchored to the Dockerfile lines that introduced the packages, enabling GitHub code scanning pull-request inline annotations and diff gates.
internal
anchor
Package anchor rewrites Microsoft Defender (MDVM) container-scan SARIF result locations so that OS-package findings point at the Dockerfile line that introduced the package, instead of the opaque image reference.
Package anchor rewrites Microsoft Defender (MDVM) container-scan SARIF result locations so that OS-package findings point at the Dockerfile line that introduced the package, instead of the opaque image reference.
cyclonedx
Package cyclonedx provides a minimal reader for CycloneDX SBOM JSON that indexes component names by the ecosystem type encoded in their package URL (purl).
Package cyclonedx provides a minimal reader for CycloneDX SBOM JSON that indexes component names by the ecosystem type encoded in their package URL (purl).
dockerfile
Package dockerfile parses a Dockerfile into lines and answers the two location questions the anchoring logic needs:
Package dockerfile parses a Dockerfile into lines and answers the two location questions the anchoring logic needs:

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL