Add ability to use syft binary instead of container for Linux detector#1776
Add ability to use syft binary instead of container for Linux detector#1776jasonpaulos wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an alternate execution path for the Linux detector to run Syft via a user-specified local binary (Linux.SyftBinaryPath) instead of always invoking Syft through a Docker container, improving compatibility for environments where the Syft image/container approach isn’t desirable.
Changes:
- Introduces
ISyftRunnerplus Docker-based and binary-based Syft runner implementations, with DI registration for both. - Refactors
LinuxScanner/LinuxContainerDetectorto scan using anImageReferencemodel and a selectedISyftRunner. - Updates and adds unit tests to cover runner behavior and the new invocation flow.
Show a summary per file
| File | Description |
|---|---|
| test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/LinuxApplicationLayerExperimentTests.cs | Updates detector constructors in experiment tests to match new DI signatures. |
| test/Microsoft.ComponentDetection.Detectors.Tests/LinuxScannerTests.cs | Refactors scanner tests to mock ISyftRunner and use ImageReference. |
| test/Microsoft.ComponentDetection.Detectors.Tests/LinuxContainerDetectorTests.cs | Updates detector tests for new runner selection and new scanner signatures. |
| test/Microsoft.ComponentDetection.Detectors.Tests/DockerSyftRunnerTests.cs | Adds tests validating Docker runner source/bind construction for each image kind. |
| test/Microsoft.ComponentDetection.Detectors.Tests/BinarySyftRunnerTests.cs | Adds tests validating binary runner version checks and command-line construction. |
| src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs | Registers new runner services/factory and ensures logging services are available. |
| src/Microsoft.ComponentDetection.Detectors/linux/LinuxScanner.cs | Moves Syft execution behind ISyftRunner; updates telemetry fields and call sites. |
| src/Microsoft.ComponentDetection.Detectors/linux/LinuxContainerDetector.cs | Selects runner based on Linux.SyftBinaryPath; passes runner through scan pipeline; refactors local image scanning to use ImageReference. |
| src/Microsoft.ComponentDetection.Detectors/linux/LinuxApplicationLayerDetector.cs | Wires new runner dependencies through the experimental detector subclass. |
| src/Microsoft.ComponentDetection.Detectors/linux/ISyftRunner.cs | Introduces the runner abstraction for executing Syft. |
| src/Microsoft.ComponentDetection.Detectors/linux/ImageReference.cs | Makes ImageReference/ImageReferenceKind public and keeps parsing/normalization logic here. |
| src/Microsoft.ComponentDetection.Detectors/linux/ILinuxScanner.cs | Updates scanner API to accept ImageReference and ISyftRunner. |
| src/Microsoft.ComponentDetection.Detectors/linux/IDockerSyftRunner.cs | Adds marker interface for Docker-backed runner. |
| src/Microsoft.ComponentDetection.Detectors/linux/IBinarySyftRunnerFactory.cs | Adds factory interface for creating binary-backed runners by path. |
| src/Microsoft.ComponentDetection.Detectors/linux/DockerSyftRunner.cs | Implements Docker container execution of Syft with per-kind source/bind mapping. |
| src/Microsoft.ComponentDetection.Detectors/linux/BinarySyftRunnerFactory.cs | Implements binary runner factory using ICommandLineInvocationService and ILoggerFactory. |
| src/Microsoft.ComponentDetection.Detectors/linux/BinarySyftRunner.cs | Implements local-binary execution of Syft, including --version availability check. |
Copilot's findings
- Files reviewed: 17/17 changed files
- Comments generated: 4
| var result = await this.commandLineInvocationService.ExecuteCommandAsync( | ||
| this.syftBinaryPath, | ||
| null, | ||
| null, | ||
| cancellationToken, | ||
| "--version"); | ||
|
|
||
| if (result.ExitCode != 0) | ||
| { | ||
| this.logger.LogInformation( | ||
| "Syft binary at {SyftBinaryPath} failed version check with exit code {ExitCode}. Stderr: {StdErr}", | ||
| this.syftBinaryPath, | ||
| result.ExitCode, | ||
| result.StdErr); | ||
| return false; | ||
| } | ||
|
|
||
| this.logger.LogInformation( | ||
| "Using Syft binary at {SyftBinaryPath}: {SyftVersion}", | ||
| this.syftBinaryPath, | ||
| result.StdOut?.Trim()); | ||
| return true; |
There was a problem hiding this comment.
CanRunAsync calls ICommandLineInvocationService.ExecuteCommandAsync, which throws (e.g., InvalidOperationException) when the command path cannot be located. If Linux.SyftBinaryPath is missing/invalid, this will bubble up and fail the Linux detector instead of treating the runner as unavailable. Consider catching command-location exceptions (or calling CanCommandBeLocatedAsync first) and returning false so the detector can skip cleanly (or fall back to the Docker runner).
| var result = await this.commandLineInvocationService.ExecuteCommandAsync( | |
| this.syftBinaryPath, | |
| null, | |
| null, | |
| cancellationToken, | |
| "--version"); | |
| if (result.ExitCode != 0) | |
| { | |
| this.logger.LogInformation( | |
| "Syft binary at {SyftBinaryPath} failed version check with exit code {ExitCode}. Stderr: {StdErr}", | |
| this.syftBinaryPath, | |
| result.ExitCode, | |
| result.StdErr); | |
| return false; | |
| } | |
| this.logger.LogInformation( | |
| "Using Syft binary at {SyftBinaryPath}: {SyftVersion}", | |
| this.syftBinaryPath, | |
| result.StdOut?.Trim()); | |
| return true; | |
| if (string.IsNullOrWhiteSpace(this.syftBinaryPath)) | |
| { | |
| this.logger.LogInformation("Syft binary path is not configured."); | |
| return false; | |
| } | |
| try | |
| { | |
| var result = await this.commandLineInvocationService.ExecuteCommandAsync( | |
| this.syftBinaryPath, | |
| null, | |
| null, | |
| cancellationToken, | |
| "--version"); | |
| if (result.ExitCode != 0) | |
| { | |
| this.logger.LogInformation( | |
| "Syft binary at {SyftBinaryPath} failed version check with exit code {ExitCode}. Stderr: {StdErr}", | |
| this.syftBinaryPath, | |
| result.ExitCode, | |
| result.StdErr); | |
| return false; | |
| } | |
| this.logger.LogInformation( | |
| "Using Syft binary at {SyftBinaryPath}: {SyftVersion}", | |
| this.syftBinaryPath, | |
| result.StdOut?.Trim()); | |
| return true; | |
| } | |
| catch (InvalidOperationException ex) | |
| { | |
| this.logger.LogInformation( | |
| ex, | |
| "Syft binary at {SyftBinaryPath} could not be located or invoked.", | |
| this.syftBinaryPath); | |
| return false; | |
| } |
| /// <summary> | ||
| /// Specifies the type of image reference. | ||
| /// </summary> | ||
| internal enum ImageReferenceKind | ||
| public enum ImageReferenceKind | ||
| { |
There was a problem hiding this comment.
ImageReferenceKind/ImageReference were changed from internal to public, expanding the public API surface for what looks like an internal Linux-detector detail. Since Microsoft.ComponentDetection.Detectors already grants internals access to Microsoft.ComponentDetection.Orchestrator and test assemblies via InternalsVisibleTo, consider keeping these types (and related Syft runner abstractions) internal unless you explicitly want to support them as public API.
| using var record = new LinuxScannerTelemetryRecord | ||
| { | ||
| ImageToScan = imageHash, | ||
| ScannerVersion = ScannerImage, | ||
| ImageToScan = imageReference.Reference, | ||
| ScannerVersion = DockerSyftRunner.ScannerImage, | ||
| }; |
There was a problem hiding this comment.
LinuxScannerTelemetryRecord uses ImageToScan = imageReference.Reference and ScannerVersion = DockerSyftRunner.ScannerImage. For local images, Reference is a full normalized host path (can leak machine/user path details into telemetry), and when using the binary runner the scanner version will be incorrect. Consider using a sanitized/non-host identifier (e.g., OriginalInput) and capturing runner identity/version based on the ISyftRunner used.
See below for a potential fix:
var scannerVersion = syftRunner is DockerSyftRunner
? DockerSyftRunner.ScannerImage
: syftRunner.GetType().Name;
using var record = new LinuxScannerTelemetryRecord
{
ImageToScan = imageReference.OriginalInput,
ScannerVersion = scannerVersion,
| var arguments = CmdParameters | ||
| .Concat(scopeParameters) | ||
| .ToList(); | ||
| (stdout, stderr) = await syftRunner.RunSyftAsync( | ||
| imageReference, |
There was a problem hiding this comment.
LinuxScannerTelemetryRecord still has a SemaphoreFailure field, but semaphore acquisition now happens inside the Syft runners and the scanner no longer sets this telemetry signal. Consider propagating a clear “semaphore not acquired” result/exception from ISyftRunner.RunSyftAsync so the scanner can set record.SemaphoreFailure (or otherwise preserve this telemetry).
By passing the detector argument
Linux.SyftBinaryPath, the Linux detector will invoke the given syft binary instead of using the syft docker image to scan images.