Skip to content

Add --syft container scan support to gh aw compile#47515

Merged
pelikhan merged 10 commits into
mainfrom
copilot/add-syft-container-vulnerability-scan-tool
Jul 23, 2026
Merged

Add --syft container scan support to gh aw compile#47515
pelikhan merged 10 commits into
mainfrom
copilot/add-syft-container-vulnerability-scan-tool

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

This change adds Syft as a first-class compile-time scanner, aligned with existing optional scanners (actionlint, poutine, zizmor, runner-guard, grype). The compile command and MCP compile interface can now request SBOM scanning for container images referenced by compiled workflows.

  • CLI and compile config surface

    • Added --syft to gh aw compile.
    • Wired the flag through CompileConfig and command construction paths.
  • Compile pipeline integration

    • Added Syft collection/execution in the batch scanner stage, alongside existing post-compile tooling.
    • Reused lock-file manifest image extraction flow so Syft and Grype operate on the same container reference set.
  • Syft scanner implementation

    • Added pkg/cli/syft.go with Docker-based Syft execution against referenced images.
    • Emits concise scan output and handles strict/non-strict behavior consistently with other scanners.
  • Docker image preparation + MCP compile tool

    • Added SyftImage to Docker scanner image constants.
    • Extended Docker preflight/image preparation and MCP compile args/schema handling to include syft.
  • Targeted test updates

    • Updated scanner-related argument validation and Docker-image preparation tests to include the new Syft path.
# CLI
gh aw compile --syft

# MCP compile tool payload
{
  "workflows": [".github/workflows/example.md"],
  "syft": true
}

Generated by 👨‍🍳 PR Sous Chef · gpt54 6.02 AIC · ⌖ 9.67 AIC · ⊞ 7K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 15 AIC · ⌖ 8.9 AIC · ⊞ 7K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Add syft scanner support to compile command Add --syft container scan support to gh aw compile Jul 23, 2026
Copilot AI requested a review from pelikhan July 23, 2026 07:14
@pelikhan
pelikhan marked this pull request as ready for review July 23, 2026 07:16
Copilot AI review requested due to automatic review settings July 23, 2026 07:16
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot update static analysis agentic workflow

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Syft-based container SBOM scanning to the compile pipeline and MCP interface.

Changes:

  • Adds --syft configuration and command wiring.
  • Runs Syft through Docker against manifest container images.
  • Extends Docker preparation and scanner argument tests.
Show a summary per file
File Description
cmd/gh-aw/main.go Defines and wires --syft.
pkg/cli/compile_config.go Adds Syft compile configuration.
pkg/cli/compile_external_tools.go Exposes the Syft batch runner.
pkg/cli/compile_pipeline.go Executes Syft after compilation.
pkg/cli/docker_images.go Adds Syft image preparation.
pkg/cli/docker_images_test.go Updates Docker image tests.
pkg/cli/mcp_argument_validation_test.go Adds Syft to MCP parameter tests.
pkg/cli/mcp_tools_readonly.go Adds MCP Syft support.
pkg/cli/syft.go Implements Docker-based Syft scanning.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/9 changed files
  • Comments generated: 4
  • Review effort level: Medium

Comment thread pkg/cli/syft.go Outdated
imageRef = img.Image
}

if _, err := runSyftOnImage(imageRef, verbose); err != nil {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 8af308f and verified in latest commit. SBOM files are now persisted to /tmp/gh-aw-syft-sboms/sbom-{image}.json with paths returned in SyftScanResult.SBOMPath. The compile result includes SBOM paths in verbose output (lines 84-88 of syft.go).

Comment thread pkg/cli/syft.go
Comment on lines +95 to +101
if err := cmd.Run(); err != nil {
stderrStr := strings.TrimSpace(stderr.String())
if stderrStr != "" {
syftLog.Printf("syft stderr for %s: %s", imageRef, stderrStr)
}
return nil, fmt.Errorf("syft failed on %s: %w", imageRef, err)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 8af308f. Non-empty stderr is now captured and included in the returned error message (lines 127-133 of syft.go): "syft failed on %s: %w\nstderr: %s" format provides registry authentication and image resolution failures.

Comment thread pkg/cli/syft.go Outdated
Comment on lines +77 to +84
cmd := exec.Command(
"docker",
"run",
"--rm",
SyftImage,
imageRef,
"-o", "syft-json",
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 8af308f. Context is now threaded through runSyftOnImage signature and exec.CommandContext is used (line 108 of syft.go), allowing cancellation during long registry operations.

Comment thread pkg/cli/syft.go Outdated
return nil
}

func runSyftOnImage(imageRef string, verbose bool) (*syftOutput, error) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 8af308f. Added comprehensive test suite in syft_test.go with 8 test functions covering: context cancellation, JSON parsing, SBOM persistence, safe filename generation, strict/non-strict modes, and empty lock file handling.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill on this PR, address the latest maintainer request in the discussion, and rerun the relevant checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · gpt54 6.02 AIC · ⌖ 9.67 AIC · ⊞ 7K ·
Comment /souschef to run again

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /tdd and /codebase-design — requesting changes on correctness and test coverage gaps.

📋 Key Themes & Highlights

Key Themes

  • No unit tests for syft.go: grype.go has extensive coverage in grype_test.go; the analogous syft.go has none. Missing tests for image selection, JSON parsing, command failure, and strict/non-strict paths.
  • Context not propagated to Docker subprocess: cmd.Run() is not bound to the compile context — cancellation during a slow registry pull is silently ignored.
  • Stderr swallowed on failure: only the exit-status string is surfaced; actionable Docker/registry errors remain hidden from the user.
  • SBOM discarded: runSyftOnImage returns a parsed *syftOutput that is never used — the only observable output is a package count, making --syft less useful than --grype in non-verbose mode.

Positive Highlights

  • ✅ Clean structural parity with grype.go — the batch-runner abstraction is well reused.
  • ✅ Consistent strict/non-strict handling and pinned-vs-floating image selection.
  • ✅ MCP wiring (args struct, validation list, Docker preflight, log line) is complete and correct.
  • #nosec G204 annotation is present and justified.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 25 AIC · ⌖ 7.39 AIC · ⊞ 6.7K
Comment /matt to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Add --syft container scan support

The implementation is well-structured and follows the existing scanner patterns. Four blocking issues were identified by a prior review pass; I am adding one more:

Blocking issues (5 total):

  1. Unpinned anchore/syft:latest image (docker_images.go:36) — floating tag breaks reproducibility and is a supply-chain risk. Pin to a versioned tag like v1.x.y to match rhysd/actionlint:1.7.12.

  2. SBOM output is discarded (syft.go:54) — runSyftOnImage returns *syftOutput but the caller ignores it. Users get only a package count; no SBOM data is surfaced or stored.

  3. Scan errors hide actionable stderr (syft.go:101) — on failure, only exit status N is returned to the caller; Syft/Docker stderr (e.g. auth errors, unknown image) is sent only to the debug logger.

  4. No context cancellation propagation (syft.go:84) — cmd.Run() is not bound to the compile context, so long registry operations cannot be cancelled.

  5. No tests for syft.go — analogous grype.go has comprehensive test coverage; this new file has none.

Non-blocking notes
  • The --syft flag help text could mention that an SBOM is not currently persisted.
  • GrypeImage has the same :latest floating-tag issue; consider addressing both together.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 27 AIC · ⌖ 4.66 AIC · ⊞ 5K

Comment thread pkg/cli/docker_images.go Outdated
PoutineImage = "ghcr.io/boostsecurityio/poutine:latest"
ActionlintImage = "rhysd/actionlint:1.7.12"
RunnerGuardImage = "ghcr.io/vigilant-llc/runner-guard:latest"
SyftImage = "anchore/syft:latest"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unpinned image taganchore/syft:latest uses a floating tag. All other scanners with their own tag use a pinned version (e.g. rhysd/actionlint:1.7.12). A floating :latest tag makes scans non-reproducible and creates a supply-chain risk if the upstream image is compromised.

Pin to a specific release tag or digest, e.g.:

SyftImage = "anchore/syft:v1.x.y"

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in latest commit. Syft image is now pinned to v1.48.0 instead of latest tag in docker_images.go:36.

@github-actions

Copy link
Copy Markdown
Contributor

Test Quality Sentinel Report

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 62.9 AIC · ⌖ 7.2 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Test Quality Sentinel: 62/100 (Acceptable). No guideline violations. Key concern: syft.go adds 110 lines of new production logic with zero test coverage — no syft_test.go exists. Consider adding behavioral tests for the syft pipeline step before merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (185 new lines in pkg/ — exceeds the 100-line threshold) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/47515-add-syft-sbom-scanning-to-compile-pipeline.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-47515: Add Syft SBOM Scanning to Compile Pipeline

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 47515-add-syft-sbom-scanning-to-compile-pipeline.md for PR #47515).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 72.5 AIC · ⌖ 11.6 AIC · ⊞ 8.5K ·
Comment /review to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

REQUEST_CHANGESsyft.go has several correctness and performance issues that must be fixed before merge.

Blocking issues (5)

1. SBOM output is discarded (high)

The *syftOutput returned by runSyftOnImage is ignored on every call. --syft currently produces only a count message; no SBOM data is stored, returned, or written to disk. The flag is not useful until this is resolved.

2. Context not propagated — Docker ignores cancellation (high)

exec.Command is used instead of exec.CommandContext. A cancelled compile context cannot stop a running Docker pull or catalog operation. The full call chain (runSyftOnImagerunSyftOnLockFilesRunSyftOnLockFiles) must accept and forward ctx.

3. Unconditional json.Unmarshal on potentially empty stdout (medium)

If syft exits 0 but writes nothing, the unmarshal returns a misleading unexpected end of JSON input. Add a stdout.Len() == 0 guard.

4. No scan-result cache (medium)

grype.go deduplicates per-image runs with a mutex-protected map. syft.go has none — N lock files referencing the same image trigger N Docker pulls.

5. Missing scan subcommand in Docker invocation (medium)

Syft deprecated the implicit default command in favor of syft scan <source> (v0.60+). The current invocation (syft <image> -o syft-json) may silently break as anchore/syft:latest advances.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 70.8 AIC · ⌖ 5.13 AIC · ⊞ 5.7K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/syft.go:437

runSyftOnImage result discarded — SBOM is silently dropped: the parsed package list is never stored, returned, or written to disk, so --syft currently produces only a count message with no persistent artifact.

<details>
<summary>💡 Suggested fix</summary>

Either write the SBOM to a sidecar file (e.g. &lt;lockfile&gt;.syft.json) or at minimum log the full result. As written, the scanner produces no actionable output — the *syftOutput return value is discarded:

// current:
if _, er</details>

<details><summary>pkg/cli/syft.go:460</summary>

**`exec.Command` used instead of `exec.CommandContext`Docker process ignores context cancellation**: if the compile context is cancelled (timeout or interrupt) while syft is pulling or cataloging, `cmd.Run()` blocks indefinitely.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Thread `ctx` through the full call chain and use `exec.CommandContext`:

```go
// runSyftOnImage(ctx context.Context, imageRef string, verbose bool) (*syftOutput, error)
cmd := exec.CommandContext(ctx, &quot;docker&quot;, &quot;run&quot;,…

</details>

<details><summary>pkg/cli/syft.go:487</summary>

**`json.Unmarshal` called unconditionally — empty stdout causes a misleading parse error**: if syft succeeds but writes no JSON (empty image, startup warning on stdout), this returns `unexpected end of JSON input` instead of a useful diagnostic.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Guard the unmarshal, matching the defensive pattern in `grype.go`:

```go
if stdout.Len() == 0 {
    return nil, fmt.Errorf(&quot;syft produced no output for %s&quot;, imageRef)
}
if err := json.Unmarshal(stdout.Byt…

</details>

<details><summary>pkg/cli/syft.go:1</summary>

**No scan-result cache — same image scanned N times**: `grype.go` deduplicates runs with a `grypeCache` (`sync.Mutex` + `map[string]*grypeOutput`). `syft.go` has no equivalent, so N lock files referencing the same image trigger N sequential Docker pulls and catalog runs.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Add a package-level cache matching `grypeCache`:

```go
var (
    syftCacheMu sync.Mutex
    syftCache   = map[string]*syftOutput{}
)

Then check and populate in `runSyftOnIma…

pkg/cli/syft.go:461

docker run invocation missing scan subcommand — may break on current syft versions: syft deprecated the implicit default command in favor of syft scan &lt;source&gt; (stable since v0.60+). Running without the subcommand may silently fall back to legacy behavior or fail as anchore/syft:latest advances.

<details>
<summary>💡 Suggested fix</summary>

cmd := exec.CommandContext(ctx, &quot;docker&quot;, &quot;run&quot;, &quot;--rm&quot;,
    SyftImage,
    &quot;scan&quot;, imageRef,
    &quot;-o&quot;, &quot;syft-json&quot;,
)

Pin `SyftIma…

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot update static analysis agentic workflow

Addressed in 53f2df5. Updated the static analysis workflow and regenerated lock file so it now pulls/verifies Syft, runs gh aw compile --syft, and includes Syft in the workflow prompt/reporting guidance.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 4 commits July 23, 2026 07:47
…, and tests

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill on this PR, address the latest maintainer request in the discussion, and rerun the relevant checks once the branch is up to date.

...

Completed pr-finisher task. Addressed all review feedback in commits 8af308f, e68aa07, dca8f46, and fb4d3ed:

  1. Added context.Context support with exec.CommandContext for proper cancellation
  2. Enhanced error messages to include stderr output
  3. Persisted SBOM files to disk with paths returned in SyftScanResult
  4. Created comprehensive test suite (8 test functions)
  5. Fixed permission constants and string replacement patterns
  6. Fixed context cancellation test for deterministic behavior

All tests passing, lint clean, workflows validated.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address these unresolved review comments, and rerun the relevant checks once the branch is up to date:

Generated by 👨‍🍳 PR Sous Chef · gpt54 15 AIC · ⌖ 8.9 AIC · ⊞ 7K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
@pelikhan
pelikhan merged commit 8127be0 into main Jul 23, 2026
20 checks passed
@pelikhan
pelikhan deleted the copilot/add-syft-container-vulnerability-scan-tool branch July 23, 2026 09:42
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants