Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1a3f651
Add Docker Compose, Helm, and Kubernetes component detectors
jpinz Apr 2, 2026
077d1d0
Add tests for Docker Compose, Dockerfile, Helm, and Kubernetes compon…
jpinz Apr 2, 2026
fd74cb5
Add `#nullable disable` to the new files
jpinz Apr 2, 2026
d5c91ed
Fix helm file patterns, and update tests
jpinz Apr 2, 2026
a111856
Add tests for Docker, Helm, and Kubernetes images with tag and digest
jpinz Apr 3, 2026
70776f8
Add support for additional Docker Compose file patterns and new tests…
jpinz Apr 3, 2026
a099d56
Refactor DockerCompose and Kubernetes component detectors to address …
jpinz Apr 3, 2026
0bbc3db
Refine HelmComponentDetector to process only relevant values files an…
jpinz Apr 3, 2026
5c4e487
Add default to switch cases
jpinz Apr 3, 2026
b5d3fbf
Enhance KubernetesComponentDetector to skip non-manifest files and im…
jpinz Apr 3, 2026
5158b75
Move DockerCompose ordering in DetectorClass enum
jpinz Apr 3, 2026
a26f434
Merge branch 'main' into jupinzer/add_container_detectors
jpinz Apr 3, 2026
0873316
Fix build issues
jpinz Apr 3, 2026
ea3b0a4
Skip image references with unresolved variables in DockerCompose, Hel…
Copilot Apr 3, 2026
5984b1c
Remove accidentally committed .nuget/nuget.exe and add to .gitignore
Copilot Apr 3, 2026
9d56f38
Fix failing tests
jpinz Apr 3, 2026
8bce22b
Merge branch 'main' into jupinzer/add_container_detectors
jpinz Apr 3, 2026
ad592b2
Add docs for the new detectors
jpinz Apr 3, 2026
9ae1dd4
Add a new Containers DetectorCategory that enabled Dockerfile, Docker…
jpinz Apr 3, 2026
70762f1
Use shared HasUnresolvedVariables in Dockerfile detector, add Helm co…
Copilot Apr 3, 2026
d220257
Fix AllowedDetectorIds+Categories intersection bug in DetectorRestric…
Copilot Apr 3, 2026
dff7d5f
Rename test variables for clarity in DetectorRestrictionServiceTests
Copilot Apr 3, 2026
569babe
Refactor DockerfileComponentDetector to use DockerReferenceUtility fo…
jpinz Apr 3, 2026
f651b13
Revert Containers DetectorCategory support
jpinz Apr 3, 2026
7f08f43
Fix Helm detector file ordering via two-pass OnPrepareDetectionAsync
Copilot Apr 3, 2026
b77c789
Refactor image reference handling in component detectors to use TryPa…
jpinz Apr 6, 2026
70ada53
Remove duplicate search patterns for Helm charts in HelmComponentDete…
jpinz Apr 6, 2026
e5ffbb5
Add "PodTemplate" to KubernetesKinds in KubernetesComponentDetector
jpinz Apr 6, 2026
c0f586c
Remove `#nullable enable` from files added in this PR
jpinz Apr 6, 2026
5d270f5
Add test for skipping values.yaml in different directory from Chart.yaml
jpinz Apr 6, 2026
df6c196
enable nullable on the new files and fix issues with it
jpinz Apr 6, 2026
ce8e98b
Clarify that values files won't be processed in the helm detector if …
jpinz Apr 6, 2026
5b148b7
Refactor Categories property to use nameof for DetectorClass in multi…
jpinz Apr 6, 2026
84b4383
Add `PodTemplate` to the docs for kubernetes
jpinz Apr 6, 2026
6478e2e
Optimize the KubernetesComponentDetector.OnFileFoundAsync method
jpinz Apr 6, 2026
a3fa9b8
Add verification tests for DockerCompose, Helm, and Kubernetes.
jpinz Apr 7, 2026
146759c
Merge branch 'main' into jupinzer/add_container_detectors
jpinz Apr 7, 2026
7fc72c1
Add test for Kubernetes Pod with image tag and digest
jpinz Apr 7, 2026
9b8e1b7
Merge branch 'main' into jupinzer/add_container_detectors
jpinz Apr 8, 2026
bac8dc8
Address copilot PR comments
jpinz Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/snapshot-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
run:
dotnet run scan --Verbosity Verbose --SourceDirectory ${{ github.workspace }}/test/Microsoft.ComponentDetection.VerificationTests/resources --Output ${{ github.workspace }}/output
--DockerImagesToScan "docker.io/library/debian@sha256:9b0e3056b8cd8630271825665a0613cc27829d6a24906dc0122b3b4834312f7d,mcr.microsoft.com/cbl-mariner/base/core@sha256:c1bc83a3d385eccbb2f7f7da43a726c697e22a996f693a407c35ac7b4387cd59,docker.io/library/alpine@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870"
--DetectorArgs DockerReference=EnableIfDefaultOff,SPDX22SBOM=EnableIfDefaultOff --DirectoryExclusionList "**/pip/parallel/**;**/pip/roots/**;**/pip/pre-generated/**;**/pip/fallback/**;**/pip/index-removal/**;**/pip/simple-extras/**;**/pip/pytestresultpkg/**"
--DetectorArgs DockerReference=EnableIfDefaultOff,SPDX22SBOM=EnableIfDefaultOff,DockerCompose=EnableIfDefaultOff,Helm=EnableIfDefaultOff,Kubernetes=EnableIfDefaultOff --DirectoryExclusionList "**/pip/parallel/**;**/pip/roots/**;**/pip/pre-generated/**;**/pip/fallback/**;**/pip/index-removal/**;**/pip/simple-extras/**;**/pip/pytestresultpkg/**"

- name: Upload output folder
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/snapshot-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
run:
dotnet run scan --Verbosity Verbose --SourceDirectory ${{ github.workspace }}/test/Microsoft.ComponentDetection.VerificationTests/resources --Output ${{ github.workspace }}/output
--DockerImagesToScan "docker.io/library/debian@sha256:9b0e3056b8cd8630271825665a0613cc27829d6a24906dc0122b3b4834312f7d,mcr.microsoft.com/cbl-mariner/base/core@sha256:c1bc83a3d385eccbb2f7f7da43a726c697e22a996f693a407c35ac7b4387cd59,docker.io/library/alpine@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870"
--DetectorArgs DockerReference=EnableIfDefaultOff,SPDX22SBOM=EnableIfDefaultOff --MaxDetectionThreads 5 --DirectoryExclusionList "**/pip/parallel/**;**/pip/roots/**;**/pip/pre-generated/**;**/pip/fallback/**;**/pip/index-removal/**;**/pip/simple-extras/**;**/pip/pytestresultpkg/**"
--DetectorArgs DockerReference=EnableIfDefaultOff,SPDX22SBOM=EnableIfDefaultOff,DockerCompose=EnableIfDefaultOff,Helm=EnableIfDefaultOff,Kubernetes=EnableIfDefaultOff --MaxDetectionThreads 5 --DirectoryExclusionList "**/pip/parallel/**;**/pip/roots/**;**/pip/pre-generated/**;**/pip/fallback/**;**/pip/index-removal/**;**/pip/simple-extras/**;**/pip/pytestresultpkg/**"

- name: Run Verification Tests
working-directory: test/Microsoft.ComponentDetection.VerificationTests
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ launchSettings.json

# Dev nupkgs
dev-source
.nuget/
18 changes: 18 additions & 0 deletions docs/detectors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
| -------------------------- | ---------- |
| CondaLockComponentDetector | DefaultOff |

- [Docker Compose](dockercompose.md)

| Detector | Status |
| ------------------------------- | ---------- |
| DockerComposeComponentDetector | DefaultOff |

- [Dockerfile](dockerfile.md)

| Detector | Status |
Expand All @@ -42,12 +48,24 @@
| ----------------------- | ------ |
| GradleComponentDetector | Stable |

- [Helm](helm.md)

| Detector | Status |
| ---------------------- | ---------- |
| HelmComponentDetector | DefaultOff |

- [Ivy](ivy.md)

| Detector | Status |
| ----------- | ------------ |
| IvyDetector | Experimental |

- [Kubernetes](kubernetes.md)

| Detector | Status |
| ------------------------------ | ---------- |
| KubernetesComponentDetector | DefaultOff |

- [Linux](linux.md)

| Detector | Status |
Expand Down
48 changes: 48 additions & 0 deletions docs/detectors/dockercompose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Docker Compose Detection

## Requirements

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`

Comment on lines +5 to +8
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documented patterns include docker-compose.*.yml / compose.*.yaml, but the repo’s file discovery matcher only supports * as a leading or trailing wildcard. These mid-string wildcard patterns won’t match in production, so the docs currently promise support that won’t work. Please update the documented patterns to the supported form (or update the detector + matcher and keep docs in sync).

Copilot uses AI. Check for mistakes.
The `DockerComposeComponentDetector` is a **DefaultOff** detector and must be explicitly enabled via the `--DetectorArgs` parameter.

## Detection strategy

The Docker Compose detector parses YAML compose files to extract Docker image references from service definitions.

### Service Image Detection

The detector looks for the `services` section and extracts the `image` field from each service:

```yaml
services:
web:
image: nginx:1.21
db:
image: postgres:14
```

Services that only define a `build` directive without an `image` field are skipped, as they do not reference external Docker images.

### Full Registry References

The detector supports full registry image references:

```yaml
services:
app:
image: ghcr.io/myorg/myapp:v2.0
```

### Variable Resolution

Images containing unresolved variables (e.g., `${TAG}` or `{{ .Values.tag }}`) are skipped to avoid reporting incomplete or incorrect references. The detector checks for `$`, `{`, or `}` characters in image references.

## Known limitations

- **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs DockerCompose=EnableIfDefaultOff`
- **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
51 changes: 51 additions & 0 deletions docs/detectors/helm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Helm Detection

## Requirements

Helm detection depends on the following to successfully run:

- One or more Helm values files matching the patterns: `*values*.yaml`, `*values*.yml`
- Chart metadata files (`Chart.yaml`, `Chart.yml`, `chart.yaml`, `chart.yml`) are matched for file discovery but only values files are parsed for image references

Comment on lines +5 to +9
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requirements list *values*.yaml / *values*.yml patterns, but the repo’s file-pattern matcher only supports leading or trailing * (prefix/suffix wildcards). As written, those “contains” patterns won’t match real-world values files like values.production.yaml or myapp-values-dev.yml in production scans, so the docs will be misleading unless the matcher or patterns are updated.

Copilot uses AI. Check for mistakes.
The `HelmComponentDetector` is a **DefaultOff** detector and must be explicitly enabled via the `--DetectorArgs` parameter.

## Detection strategy

The Helm detector parses Helm values YAML files to extract Docker image references. It recursively walks the YAML tree looking for `image` keys.

### Direct Image Strings

The detector recognizes image references specified as simple strings:

```yaml
image: nginx:1.21
```

### Structured Image Objects

The detector also supports the common Helm chart pattern of structured image definitions:

```yaml
image:
registry: ghcr.io
repository: org/myimage
tag: v1.0
```

The `registry` and `tag` fields are optional. When present, the detector reconstructs the full image reference. The `digest` field is also supported.

### Recursive Search

The detector recursively traverses all nested mappings and sequences in the values file, detecting image references at any depth in the YAML structure.

### Variable Resolution

Images containing unresolved variables (e.g., `{{ .Values.tag }}`) are skipped to avoid reporting incomplete or incorrect references. The detector checks for `$`, `{`, or `}` characters in image references.

## Known limitations

- **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs Helm=EnableIfDefaultOff`
- **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
- **No Dependency Graph**: All detected images are registered as independent components without parent-child relationships
59 changes: 59 additions & 0 deletions docs/detectors/kubernetes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Kubernetes Detection

## Requirements

Kubernetes detection depends on the following to successfully run:

- One or more Kubernetes manifest files matching the patterns: `*.yaml`, `*.yml`
- Manifests must contain both `apiVersion` and `kind` fields to be recognized as Kubernetes resources

The `KubernetesComponentDetector` is a **DefaultOff** detector and must be explicitly enabled via the `--DetectorArgs` parameter.

## Detection strategy

The Kubernetes detector parses Kubernetes manifest YAML files to extract Docker image references from container specifications.

### Supported Resource Kinds

The detector recognizes the following Kubernetes resource kinds:

- `Pod`
- `PodTemplate`
- `Deployment`
- `StatefulSet`
- `DaemonSet`
- `ReplicaSet`
- `Job`
- `CronJob`
- `ReplicationController`

Comment on lines +16 to +29
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc’s “Supported Resource Kinds” list doesn’t include PodTemplate, but the implementation’s KubernetesKinds set does. Please update the documentation to reflect the actual supported kinds (or remove PodTemplate support if it’s not intended).

Copilot uses AI. Check for mistakes.
Files with an unrecognized `kind` or missing `apiVersion`/`kind` fields are skipped.

### Container Image Detection

The detector extracts image references from all container types within pod specifications:

- **containers**: Main application containers
- **initContainers**: Initialization containers that run before app containers
- **ephemeralContainers**: Ephemeral debugging containers

### Pod Spec Locations

The detector handles different pod spec locations depending on the resource kind:

- **Pod**: `spec.containers`
- **Deployment, StatefulSet, DaemonSet, ReplicaSet, ReplicationController**: `spec.template.spec.containers`
- **Job**: `spec.template.spec.containers`
- **CronJob**: `spec.jobTemplate.spec.template.spec.containers`

### Variable Resolution

Images containing unresolved variables (e.g., `${TAG}`) are skipped to avoid reporting incomplete or incorrect references. The detector checks for `$`, `{`, or `}` characters in image references.

## Known limitations

- **DefaultOff Status**: This detector must be explicitly enabled using `--DetectorArgs Kubernetes=EnableIfDefaultOff`
- **Broad File Matching**: The `*.yaml` and `*.yml` search patterns match all YAML files, so the detector relies on content-based filtering (`apiVersion` and `kind` fields) to identify Kubernetes manifests
- **Variable Resolution**: Image references containing unresolved template variables are not reported
- **Limited Resource Kinds**: Only the eight resource kinds listed above are supported. Custom resources (CRDs) or other workload types are not detected
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The limitations section says “Only the eight resource kinds listed above are supported”, but the document lists nine kinds (Pod, PodTemplate, Deployment, StatefulSet, DaemonSet, ReplicaSet, Job, CronJob, ReplicationController). Please update the count to avoid confusion.

Suggested change
- **Limited Resource Kinds**: Only the eight resource kinds listed above are supported. Custom resources (CRDs) or other workload types are not detected
- **Limited Resource Kinds**: Only the nine resource kinds listed above are supported. Custom resources (CRDs) or other workload types are not detected

Copilot uses AI. Check for mistakes.
- **No Dependency Graph**: All detected images are registered as independent components without parent-child relationships
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,53 @@ public static class DockerReferenceUtility
private const string LEGACYDEFAULTDOMAIN = "index.docker.io";
private const string OFFICIALREPOSITORYNAME = "library";

/// <summary>
/// Returns true if the reference contains unresolved variable placeholders (e.g., ${VAR}, {{ .Values.tag }}).
/// Such references should be skipped before calling <see cref="ParseFamiliarName"/> or <see cref="ParseQualifiedName"/>.
/// </summary>
/// <param name="reference">The image reference string to check.</param>
Comment on lines +41 to +45
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says unresolved-variable skipping logic was consolidated across all Docker-related detectors, but DockerfileComponentDetector still has its own regex-based HasUnresolvedVariables helper and does not use this new shared method. Either update the Dockerfile detector to use DockerReferenceUtility.HasUnresolvedVariables or adjust the PR description to match the implemented scope.

This issue also appears on line 47 of the same file.

Copilot uses AI. Check for mistakes.
/// <returns><c>true</c> if the reference contains variable placeholder characters; otherwise <c>false</c>.</returns>
public static bool HasUnresolvedVariables(string reference) =>
reference.IndexOfAny(['$', '{', '}']) >= 0;
Comment on lines +41 to +48
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new helper duplicates the Dockerfile detector’s existing HasUnresolvedVariables implementation (Regex("[${}]")). To actually consolidate the logic across Docker-related detectors (as described in the PR), consider switching the Dockerfile detector to call this shared helper and removing the duplicate method so the criteria can’t drift over time.

Copilot uses AI. Check for mistakes.

/// <summary>
/// Attempts to parse an image reference string into a <see cref="DockerReference"/>.
/// Returns <c>null</c> if the reference contains unresolved variables or cannot be parsed.
/// </summary>
/// <param name="imageReference">The image reference string to parse.</param>
/// <returns>A <see cref="DockerReference"/> if parsing succeeds; otherwise <c>null</c>.</returns>
public static DockerReference? TryParseImageReference(string imageReference)
{
if (HasUnresolvedVariables(imageReference))
{
return null;
}

try
{
return ParseFamiliarName(imageReference);
}
catch
{
return null;
}
}

/// <summary>
/// Parses an image reference and registers it with the recorder if valid.
/// Silently skips references with unresolved variables or that cannot be parsed.
/// </summary>
/// <param name="imageReference">The image reference string to parse.</param>
/// <param name="recorder">The component recorder to register the image with.</param>
public static void TryRegisterImageReference(string imageReference, ISingleFileComponentRecorder recorder)
{
var dockerRef = TryParseImageReference(imageReference);
if (dockerRef != null)
{
recorder.RegisterUsage(new DetectedComponent(dockerRef.ToTypedDockerReferenceComponent()));
}
}

public static DockerReference ParseQualifiedName(string qualifiedName)
{
var regexp = DockerRegex.ReferenceRegexp;
Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,13 @@ public enum DetectorClass

/// <summary> Indicates a detector applies to Swift packages.</summary>
Swift,

/// <summary>Indicates a detector applies to Docker Compose image references.</summary>
DockerCompose,

/// <summary>Indicates a detector applies to Helm chart image references.</summary>
Helm,

/// <summary>Indicates a detector applies to Kubernetes manifest image references.</summary>
Kubernetes,
}
Loading
Loading