From 820345814728afad83457b155f96d8dd9350177e Mon Sep 17 00:00:00 2001 From: Julian Pinzer Date: Fri, 1 May 2026 19:00:35 +0000 Subject: [PATCH 1/4] Promote the Dockerfile, Docker Compose, and Helm Detectors to Experimental status Co-authored-by: Copilot --- docs/detectors/README.md | 6 +++--- docs/detectors/dockercompose.md | 4 ++-- docs/detectors/dockerfile.md | 4 ++-- docs/detectors/helm.md | 4 ++-- .../dockercompose/DockerComposeComponentDetector.cs | 2 +- .../dockerfile/DockerfileComponentDetector.cs | 2 +- .../helm/HelmComponentDetector.cs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/detectors/README.md b/docs/detectors/README.md index ba789f298..313886577 100644 --- a/docs/detectors/README.md +++ b/docs/detectors/README.md @@ -22,13 +22,13 @@ | Detector | Status | | ------------------------------ | ---------- | -| DockerComposeComponentDetector | DefaultOff | +| DockerComposeComponentDetector | Experimental | - [Dockerfile](dockerfile.md) | Detector | Status | | --------------------------- | ---------- | -| DockerfileComponentDetector | DefaultOff | +| DockerfileComponentDetector | Experimental | - [DotNet](dotnet.md) @@ -52,7 +52,7 @@ | Detector | Status | | ---------------------- | ---------- | -| HelmComponentDetector | DefaultOff | +| HelmComponentDetector | Experimental | - [Ivy](ivy.md) diff --git a/docs/detectors/dockercompose.md b/docs/detectors/dockercompose.md index f05e026ad..03387c00b 100644 --- a/docs/detectors/dockercompose.md +++ b/docs/detectors/dockercompose.md @@ -6,7 +6,7 @@ Docker Compose detection depends on the following to successfully run: - One or more Docker Compose files matching the patterns: `docker-compose.yml`, `docker-compose.yaml`, `docker-compose.*.yml`, `docker-compose.*.yaml`, `compose.yml`, `compose.yaml`, `compose.*.yml`, `compose.*.yaml` -The `DockerComposeComponentDetector` is a **DefaultOff** detector and must be explicitly enabled via the `--DetectorArgs` parameter. +The `DockerComposeComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. ## Detection strategy @@ -42,7 +42,7 @@ Images containing unresolved variables (e.g., `${TAG}` or `${REGISTRY:-docker.io ## Known limitations -- **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs DockerCompose=EnableIfDefaultOff` +- **Experimental Status**: This detector runs automatically but its output is not included in scan results - **Variable Resolution**: Image references containing unresolved environment variables or template expressions are not reported, which may lead to under-reporting in compose files that heavily use variable substitution - **Build-Only Services**: Services that only specify a `build` directive without an `image` field are not reported - **No Dependency Graph**: All detected images are registered as independent components without parent-child relationships \ No newline at end of file diff --git a/docs/detectors/dockerfile.md b/docs/detectors/dockerfile.md index 8143c031a..ef382dda0 100644 --- a/docs/detectors/dockerfile.md +++ b/docs/detectors/dockerfile.md @@ -6,7 +6,7 @@ Dockerfile detection depends on the following to successfully run: - One or more Dockerfile files matching the patterns: `dockerfile`, `dockerfile.*`, or `*.dockerfile` -The `DockerfileComponentDetector` is a **DefaultOff** detector and must be explicitly enabled via the `--DetectorArgs` parameter. +The `DockerfileComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. ## Detection strategy @@ -26,7 +26,7 @@ The detector attempts to resolve Dockerfile variables using the `ResolveVariable ## Known limitations -- **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs DockerReference=EnableIfDefaultOff` +- **Experimental Status**: This detector runs automatically but its output is not included in scan results - **Variable Resolution**: Image references containing unresolved Dockerfile `ARG` or `ENV` variables are not reported, which may lead to under-reporting in Dockerfiles that heavily use build-time variables - **No Version Pinning Validation**: The detector does not warn about unpinned image versions (e.g., `latest` tags), which are generally discouraged in production Dockerfiles - **No Digest Support**: While Docker supports content-addressable image references using SHA256 digests (e.g., `ubuntu@sha256:abc...`), the parsing and reporting of these references depends on the underlying `DockerReferenceUtility.ParseFamiliarName()` implementation diff --git a/docs/detectors/helm.md b/docs/detectors/helm.md index 9866be7a2..cba35d558 100644 --- a/docs/detectors/helm.md +++ b/docs/detectors/helm.md @@ -8,7 +8,7 @@ Helm detection depends on the following to successfully run: - A chart metadata file named `Chart.yaml` or `Chart.yml` must exist in the same directory for file discovery/co-location checks; only values files are parsed for image references - Lowercase `chart.yaml` and `chart.yml` do not satisfy this requirement; the detector requires an uppercase `Chart.*` file name. -The `HelmComponentDetector` is a **DefaultOff** detector and must be explicitly enabled via the `--DetectorArgs` parameter. +The `HelmComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. ## Detection strategy @@ -45,7 +45,7 @@ Images containing unresolved variables (e.g., `{{ .Values.tag }}`) are skipped t ## Known limitations -- **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs Helm=EnableIfDefaultOff` +- **Experimental Status**: This detector runs automatically but its output is not included in scan results - **Values Files Only**: Only files with `values` in the name are parsed for image references. Chart.yaml files are matched but not processed - **Same-Directory Co-location**: Values files are only processed when a `Chart.yaml` (or `Chart.yml`) exists in the **same directory**. Values files in subdirectories of a chart root (e.g., `mychart/subdir/values.yaml`) will not be detected, even if a `Chart.yaml` exists in the parent directory - **Variable Resolution**: Image references containing unresolved Helm template expressions are not reported diff --git a/src/Microsoft.ComponentDetection.Detectors/dockercompose/DockerComposeComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/dockercompose/DockerComposeComponentDetector.cs index a582728a1..8f2c857d1 100644 --- a/src/Microsoft.ComponentDetection.Detectors/dockercompose/DockerComposeComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/dockercompose/DockerComposeComponentDetector.cs @@ -12,7 +12,7 @@ namespace Microsoft.ComponentDetection.Detectors.DockerCompose; using Microsoft.Extensions.Logging; using YamlDotNet.RepresentationModel; -public class DockerComposeComponentDetector : FileComponentDetector, IDefaultOffComponentDetector +public class DockerComposeComponentDetector : FileComponentDetector, IExperimentalDetector { public DockerComposeComponentDetector( IComponentStreamEnumerableFactory componentStreamEnumerableFactory, diff --git a/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs index e314fb82d..031358105 100644 --- a/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/dockerfile/DockerfileComponentDetector.cs @@ -12,7 +12,7 @@ namespace Microsoft.ComponentDetection.Detectors.Dockerfile; using Microsoft.Extensions.Logging; using Valleysoft.DockerfileModel; -public class DockerfileComponentDetector : FileComponentDetector, IDefaultOffComponentDetector +public class DockerfileComponentDetector : FileComponentDetector, IExperimentalDetector { private readonly ICommandLineInvocationService commandLineInvocationService; private readonly IEnvironmentVariableService envVarService; diff --git a/src/Microsoft.ComponentDetection.Detectors/helm/HelmComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/helm/HelmComponentDetector.cs index be9f0f01c..9da078303 100644 --- a/src/Microsoft.ComponentDetection.Detectors/helm/HelmComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/helm/HelmComponentDetector.cs @@ -15,7 +15,7 @@ namespace Microsoft.ComponentDetection.Detectors.Helm; using Microsoft.Extensions.Logging; using YamlDotNet.RepresentationModel; -public class HelmComponentDetector : FileComponentDetector, IDefaultOffComponentDetector +public class HelmComponentDetector : FileComponentDetector, IExperimentalDetector { public HelmComponentDetector( IComponentStreamEnumerableFactory componentStreamEnumerableFactory, From e880b90fdf296bcee3ac67edf3e3ead19ba1386a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 19:29:35 +0000 Subject: [PATCH 2/4] docs: document opt-in mechanism for experimental detectors in dockerfile, dockercompose, and helm docs Agent-Logs-Url: https://github.com/microsoft/component-detection/sessions/c1b52ac3-7a11-4689-b324-b61c26014208 Co-authored-by: jpinz <8357054+jpinz@users.noreply.github.com> --- docs/detectors/dockercompose.md | 4 ++-- docs/detectors/dockerfile.md | 4 ++-- docs/detectors/helm.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/detectors/dockercompose.md b/docs/detectors/dockercompose.md index 03387c00b..499ac671c 100644 --- a/docs/detectors/dockercompose.md +++ b/docs/detectors/dockercompose.md @@ -6,7 +6,7 @@ Docker Compose detection depends on the following to successfully run: - One or more Docker Compose files matching the patterns: `docker-compose.yml`, `docker-compose.yaml`, `docker-compose.*.yml`, `docker-compose.*.yaml`, `compose.yml`, `compose.yaml`, `compose.*.yml`, `compose.*.yaml` -The `DockerComposeComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. +The `DockerComposeComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. To include its output, pass `--DetectorArgs DockerCompose=Enable` (the key is the detector Id `DockerCompose`, not the class name). ## Detection strategy @@ -42,7 +42,7 @@ Images containing unresolved variables (e.g., `${TAG}` or `${REGISTRY:-docker.io ## Known limitations -- **Experimental Status**: This detector runs automatically but its output is not included in scan results +- **Experimental Status**: This detector runs automatically but its output is not included in scan results by default. To opt in, pass `--DetectorArgs DockerCompose=Enable` - **Variable Resolution**: Image references containing unresolved environment variables or template expressions are not reported, which may lead to under-reporting in compose files that heavily use variable substitution - **Build-Only Services**: Services that only specify a `build` directive without an `image` field are not reported - **No Dependency Graph**: All detected images are registered as independent components without parent-child relationships \ No newline at end of file diff --git a/docs/detectors/dockerfile.md b/docs/detectors/dockerfile.md index ef382dda0..773f8266a 100644 --- a/docs/detectors/dockerfile.md +++ b/docs/detectors/dockerfile.md @@ -6,7 +6,7 @@ Dockerfile detection depends on the following to successfully run: - One or more Dockerfile files matching the patterns: `dockerfile`, `dockerfile.*`, or `*.dockerfile` -The `DockerfileComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. +The `DockerfileComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. To include its output, pass `--DetectorArgs DockerReference=Enable` (the key is the detector Id `DockerReference`, not the class name). ## Detection strategy @@ -26,7 +26,7 @@ The detector attempts to resolve Dockerfile variables using the `ResolveVariable ## Known limitations -- **Experimental Status**: This detector runs automatically but its output is not included in scan results +- **Experimental Status**: This detector runs automatically but its output is not included in scan results by default. To opt in, pass `--DetectorArgs DockerReference=Enable` - **Variable Resolution**: Image references containing unresolved Dockerfile `ARG` or `ENV` variables are not reported, which may lead to under-reporting in Dockerfiles that heavily use build-time variables - **No Version Pinning Validation**: The detector does not warn about unpinned image versions (e.g., `latest` tags), which are generally discouraged in production Dockerfiles - **No Digest Support**: While Docker supports content-addressable image references using SHA256 digests (e.g., `ubuntu@sha256:abc...`), the parsing and reporting of these references depends on the underlying `DockerReferenceUtility.ParseFamiliarName()` implementation diff --git a/docs/detectors/helm.md b/docs/detectors/helm.md index cba35d558..c9f7bdb55 100644 --- a/docs/detectors/helm.md +++ b/docs/detectors/helm.md @@ -8,7 +8,7 @@ Helm detection depends on the following to successfully run: - A chart metadata file named `Chart.yaml` or `Chart.yml` must exist in the same directory for file discovery/co-location checks; only values files are parsed for image references - Lowercase `chart.yaml` and `chart.yml` do not satisfy this requirement; the detector requires an uppercase `Chart.*` file name. -The `HelmComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. +The `HelmComponentDetector` is an **Experimental** detector. It runs automatically during scans, but its output is not included in the final scan results. To include its output, pass `--DetectorArgs Helm=Enable` (the key is the detector Id `Helm`, not the class name). ## Detection strategy @@ -45,7 +45,7 @@ Images containing unresolved variables (e.g., `{{ .Values.tag }}`) are skipped t ## Known limitations -- **Experimental Status**: This detector runs automatically but its output is not included in scan results +- **Experimental Status**: This detector runs automatically but its output is not included in scan results by default. To opt in, pass `--DetectorArgs Helm=Enable` - **Values Files Only**: Only files with `values` in the name are parsed for image references. Chart.yaml files are matched but not processed - **Same-Directory Co-location**: Values files are only processed when a `Chart.yaml` (or `Chart.yml`) exists in the **same directory**. Values files in subdirectories of a chart root (e.g., `mychart/subdir/values.yaml`) will not be detected, even if a `Chart.yaml` exists in the parent directory - **Variable Resolution**: Image references containing unresolved Helm template expressions are not reported From 5fa9cb1edc990c04d4815cc6bb6d3f64f8c49e0e Mon Sep 17 00:00:00 2001 From: Julian Pinzer Date: Mon, 4 May 2026 16:54:51 +0000 Subject: [PATCH 3/4] Add DockerfileComponentDetectorTests Co-authored-by: Copilot --- .../DockerfileComponentDetectorTests.cs | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 test/Microsoft.ComponentDetection.Detectors.Tests/DockerfileComponentDetectorTests.cs diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/DockerfileComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/DockerfileComponentDetectorTests.cs new file mode 100644 index 000000000..62bf38b24 --- /dev/null +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/DockerfileComponentDetectorTests.cs @@ -0,0 +1,260 @@ +#nullable enable +namespace Microsoft.ComponentDetection.Detectors.Tests; + +using System.Linq; +using System.Threading.Tasks; +using AwesomeAssertions; +using Microsoft.ComponentDetection.Contracts; +using Microsoft.ComponentDetection.Contracts.TypedComponent; +using Microsoft.ComponentDetection.Detectors.Dockerfile; +using Microsoft.ComponentDetection.TestsUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +[TestClass] +[TestCategory("Governance/All")] +[TestCategory("Governance/ComponentDetection")] +public class DockerfileComponentDetectorTests : BaseDetectorTest +{ + public DockerfileComponentDetectorTests() => + this.DetectorTestUtility + .AddServiceMock(new Mock()) + .AddServiceMock(new Mock()); + + [TestMethod] + public async Task TestDockerfile_SingleFromInstructionAsync() + { + var dockerfile = @" +FROM nginx:1.21 +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + var components = componentRecorder.GetDetectedComponents(); + components.Should().ContainSingle(); + + var dockerRef = components.First().Component as DockerReferenceComponent; + dockerRef.Should().NotBeNull(); + dockerRef!.Repository.Should().Be("library/nginx"); + dockerRef.Tag.Should().Be("1.21"); + dockerRef.Digest.Should().BeNull(); + } + + [TestMethod] + public async Task TestDockerfile_FromWithRegistryAsync() + { + var dockerfile = @" +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + var components = componentRecorder.GetDetectedComponents(); + components.Should().ContainSingle(); + + var dockerRef = components.First().Component as DockerReferenceComponent; + dockerRef.Should().NotBeNull(); + dockerRef!.Domain.Should().Be("mcr.microsoft.com"); + dockerRef.Repository.Should().Be("dotnet/sdk"); + dockerRef.Tag.Should().Be("8.0"); + dockerRef.Digest.Should().BeNull(); + } + + [TestMethod] + public async Task TestDockerfile_FromWithDigestAsync() + { + var dockerfile = @" +FROM nginx@sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abc1 +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + var components = componentRecorder.GetDetectedComponents(); + components.Should().ContainSingle(); + + var dockerRef = components.First().Component as DockerReferenceComponent; + dockerRef.Should().NotBeNull(); + dockerRef!.Digest.Should().Be("sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abc1"); + dockerRef.Tag.Should().BeNull(); + } + + [TestMethod] + public async Task TestDockerfile_MultiStageBuildAsync() + { + var dockerfile = @" +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app +COPY . . +RUN dotnet publish -c Release -o out + +FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS runtime +WORKDIR /app +COPY --from=build /app/out ./ +ENTRYPOINT [""/app/MyApp""] +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + var components = componentRecorder.GetDetectedComponents(); + components.Should().HaveCount(2); + + var repos = components + .Select(c => c.Component as DockerReferenceComponent) + .Where(c => c != null) + .Select(c => c!.Repository) + .ToList(); + repos.Should().Contain("dotnet/sdk"); + repos.Should().Contain("dotnet/runtime-deps"); + } + + [TestMethod] + public async Task TestDockerfile_CopyFromStageNameDoesNotCreateExtraComponentAsync() + { + // COPY --from= references a previous build stage and should not yield a separate image component. + var dockerfile = @" +FROM nginx:1.21 AS build +FROM alpine:3.18 AS runtime +COPY --from=build /etc/nginx/nginx.conf /etc/nginx/nginx.conf +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + var components = componentRecorder.GetDetectedComponents().ToList(); + + // Two FROM instructions => two images. The COPY --from=build should resolve back to nginx:1.21, + // which is already registered, so no new component is added. + components.Should().HaveCount(2); + var repos = components + .Select(c => (c.Component as DockerReferenceComponent)!.Repository) + .ToList(); + repos.Should().Contain("library/nginx"); + repos.Should().Contain("library/alpine"); + } + + [TestMethod] + public async Task TestDockerfile_CopyFromExternalImageAsync() + { + // COPY --from= references an image directly and should produce a component. + var dockerfile = @" +FROM alpine:3.18 +COPY --from=busybox:1.36 /bin/busybox /usr/local/bin/busybox +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + var components = componentRecorder.GetDetectedComponents().ToList(); + components.Should().HaveCount(2); + + var repos = components + .Select(c => (c.Component as DockerReferenceComponent)!.Repository) + .ToList(); + repos.Should().Contain("library/alpine"); + repos.Should().Contain("library/busybox"); + } + + [TestMethod] + public async Task TestDockerfile_LowercaseFilenameAsync() + { + var dockerfile = @"FROM redis:7-alpine"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + componentRecorder.GetDetectedComponents().Should().ContainSingle(); + } + + [TestMethod] + public async Task TestDockerfile_ExtensionFilenameAsync() + { + var dockerfile = @"FROM redis:7-alpine"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("app.dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + componentRecorder.GetDetectedComponents().Should().ContainSingle(); + } + + [TestMethod] + public async Task TestDockerfile_PrefixedFilenameAsync() + { + var dockerfile = @"FROM redis:7-alpine"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile.prod", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + componentRecorder.GetDetectedComponents().Should().ContainSingle(); + } + + [TestMethod] + public async Task TestDockerfile_NoFromInstructionsAsync() + { + var dockerfile = @" +# This Dockerfile has no FROM instructions +ARG BUILD_VERSION=1.0 +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + componentRecorder.GetDetectedComponents().Should().BeEmpty(); + } + + [TestMethod] + public async Task TestDockerfile_MalformedContentAsync() + { + // Garbage content should not crash the detector. + var dockerfile = "this is not a dockerfile at all { ] : >"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + componentRecorder.GetDetectedComponents().Should().BeEmpty(); + } + + [TestMethod] + public async Task TestDockerfile_FromWithUnresolvedArgVariableIsSkippedAsync() + { + // References containing unresolved variable placeholders (e.g. ${BASE_TAG}) cannot be parsed + // into a concrete image identity and are skipped by DockerReferenceUtility. + var dockerfile = @" +ARG BASE_TAG=1.21 +FROM nginx:${BASE_TAG} +"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Dockerfile", dockerfile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + componentRecorder.GetDetectedComponents().Should().BeEmpty(); + } +} From 32f5081134c2f3bd3df0647ed2fde29fccee68e3 Mon Sep 17 00:00:00 2001 From: Julian Pinzer Date: Mon, 4 May 2026 17:33:18 +0000 Subject: [PATCH 4/4] Update the dockerfile.md docs Co-authored-by: Copilot --- docs/detectors/dockerfile.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/detectors/dockerfile.md b/docs/detectors/dockerfile.md index 773f8266a..2da4fa3a4 100644 --- a/docs/detectors/dockerfile.md +++ b/docs/detectors/dockerfile.md @@ -24,9 +24,15 @@ The detector extracts image references from `COPY --from=` instructions t ### Variable Resolution The detector attempts to resolve Dockerfile variables using the `ResolveVariables()` method from the parser library. Images with unresolved variables (containing `$`, `{`, or `}` characters) are skipped to avoid reporting incomplete or incorrect references. +### Tag and Digest Support +The detector supports the full Docker reference grammar via `DockerReferenceUtility.ParseFamiliarName()`. Image references are parsed and reported with their tag, digest, or both: +- Tagged references (e.g., `FROM nginx:1.21`) populate the `Tag` field +- Canonical references with a SHA256 digest (e.g., `FROM nginx@sha256:abc...`) populate the `Digest` field +- Dual references with both a tag and a digest (e.g., `FROM nginx:1.21@sha256:abc...`) populate both fields + ## Known limitations - **Experimental Status**: This detector runs automatically but its output is not included in scan results by default. To opt in, pass `--DetectorArgs DockerReference=Enable` - **Variable Resolution**: Image references containing unresolved Dockerfile `ARG` or `ENV` variables are not reported, which may lead to under-reporting in Dockerfiles that heavily use build-time variables - **No Version Pinning Validation**: The detector does not warn about unpinned image versions (e.g., `latest` tags), which are generally discouraged in production Dockerfiles -- **No Digest Support**: While Docker supports content-addressable image references using SHA256 digests (e.g., `ubuntu@sha256:abc...`), the parsing and reporting of these references depends on the underlying `DockerReferenceUtility.ParseFamiliarName()` implementation +- **Untagged Images Skipped**: Image references with neither a tag nor a digest (e.g. `FROM nginx`) are skipped because they cannot be uniquely identified