Skip to content

Add daily firewall container security scan - #47407

Merged
pelikhan merged 5 commits into
mainfrom
copilot/add-daily-agentic-workflow
Jul 22, 2026
Merged

Add daily firewall container security scan#47407
pelikhan merged 5 commits into
mainfrom
copilot/add-daily-agentic-workflow

Conversation

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Adds daily supply-chain scanning for the immutable multi-architecture gh-aw-firewall/squid image. Findings create a deduplicated issue labeled cookie and security.

Changes

  • Container scanning

    • Scans linux/amd64 and linux/arm64 image digests with Syft and Grype.
    • Reports all vulnerabilities and available fixes.
    • Detects drift between pinned and published image digests.
  • License enforcement

    • Adds .grant.yaml using the license policy from CONTRIBUTING.md.
    • Rejects missing, unknown, and unapproved licenses.
  • Supply-chain controls

    • Downloads pinned Syft, Grype, and Grant releases.
    • Verifies each archive using a committed SHA-256 checksum.
    • Scans platform-specific immutable digests rather than mutable tags.
  • Reporting and gates

    • Creates one deduplicated issue per image digest.
    • Preserves reporting when scanning or artifact publication fails.
    • Fails after reporting for critical vulnerabilities, rejected licenses, or scanner errors.
syft --platform "$PLATFORM" "$IMMUTABLE_IMAGE" \
  -o "syft-json=$SYFT_SBOM" \
  -o "spdx-json=$SPDX_SBOM"

grype "sbom:$SYFT_SBOM" --fail-on critical
grant check --config .grant.yaml --output json "$SPDX_SBOM"

Copilot AI and others added 2 commits July 22, 2026 21:23
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot scan all know container images used in compiled lock files

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

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot scan all know container images used in compiled lock files

Implemented in ed5ff99. The daily scan now reads every compiled lock file’s gh-aw-manifest, deduplicates all immutable container pins, and scans each Linux platform with per-image findings and issue deduplication.

@pelikhan
pelikhan marked this pull request as ready for review July 22, 2026 22:31
Copilot AI review requested due to automatic review settings July 22, 2026 22:31
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (0 additions detected in default business logic directories: src/, lib/, pkg/, internal/, app/, core/, domain/, services/, api/).

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

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 a daily container supply-chain scan with vulnerability, license, digest-drift, and issue reporting gates.

Changes:

  • Installs checksum-verified Syft, Grype, and Grant.
  • Scans immutable image digests and reports findings.
  • Adds strict license enforcement.
Show a summary per file
File Description
.grant.yaml Defines the license allowlist.
.github/workflows/daily-squid-image-scan.md Defines scanning, reporting, and enforcement.
.github/workflows/daily-squid-image-scan.lock.yml Compiled GitHub Actions workflow.

Review details

Tip

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

  • Files reviewed: 2/3 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment on lines +131 to +135
jq -s '
[.[].containers[]?]
| unique_by(.pinned_image)
| sort_by(.image)
' > "$output/images.json"; then
Comment thread .github/workflows/daily-squid-image-scan.md

jq -r '
.manifests[]?
| select(.platform.os == "linux")

@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 daily firewall container security scan

Good overall design — pinned immutable digests, SHA-256 verified tool downloads, fallback reporting when artifacts are absent, and a single deduplicated issue per image digest.

Three non-blocking but reliability-affecting issues flagged inline:

  1. Tool versions duplicated as literal strings (lines 52-56, 370-374) — the fallback and scan summary hardcode the same version strings as the Install pinned security tools env vars. A future version bump will leave stale metadata in both places.

  2. Grype exit-code guard is wrong (line 285) — the condition exit_code != 0 && critical_count == 0 swallows unexpected Grype errors (exit code > 1, e.g. DB failure) when criticals are present. Should distinguish Grype's exit 1 (threshold exceeded) from other non-zero exits.

  3. Tool install hardcoded to linux_amd64 (line 94) — the assert x86_64 guard makes the install step fail entirely on arm64 runners; the scan infrastructure already iterates arm64 platform digests but would have no scanner available.

These are COMMENT-level — the core scanning logic, supply-chain controls, and issue deduplication are sound.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 50.4 AIC · ⌖ 4.9 AIC · ⊞ 5K

tools: {
syft: "1.49.0",
grype: "0.116.0",
grant: "0.6.8"

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.

Tool versions hardcoded as literal strings in fallback step

Lines 52-56 hardcode the tool versions (syft 1.49.0, grype 0.116.0, grant 0.6.8) in the fallback summary.json. The same versions appear again at lines 370-374. Both locations duplicate the authoritative versions declared in the Install pinned security tools step env vars (SYFT_VERSION, GRYPE_VERSION, GRANT_VERSION).

If those env vars are bumped in the future, the fallback / scan steps will silently emit stale version metadata, making scanner output look like it came from a different tool version than what actually ran.

Suggested fix: Write the versions to a shared file (e.g. $output/tool-versions.env) during installation, then source that file in both places. Or pass the env vars through to the step that builds the fallback JSON.

@copilot please address this.

image_fixable=$((image_fixable + platform_fixable))

GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \
--fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1

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.

Grype --fail-on critical exit code ignored; error only if critical count is zero

Lines 282-287:

GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \
  --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1
grype_exit=$?
if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then
  record_image_error "Grype table scan failed unexpectedly..."
fi

When platform_critical > 0, a non-zero exit is silently ignored, so a Grype crash or unexpected error during the table pass is swallowed. The exit code should also be recorded as an operational error when it's non-zero for reasons other than detected criticals (e.g. DB read failure, network issue on a partial run).

Consider:

if [ "$grype_exit" -ne 0 ] && [ "$grype_exit" -ne 1 ]; then
  # exit 1 = vulnerabilities found above threshold; anything else is unexpected
  record_image_error ...
fi

(Grype exits 1 when vulnerabilities exceed the threshold and 0 otherwise.)

@copilot please address this.

GRANT_SHA256: "6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919"
run: |
set -euo pipefail
test "$(uname -m)" = "x86_64"

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.

Tool install hardcoded to linux_amd64 only — fails silently on arm64 runners

Line 94 asserts uname -m == x86_64 which guards the rest, but the scan step at line 114 uses both amd64 and arm64 platforms. The install script downloads only the linux_amd64 tarball (line 104), so if this job ever runs on an arm64 runner the entire install step fails, tools_ready becomes false, and all per-platform scan data is absent from the summary with only an operational error.

Since the scan already iterates multi-arch platforms, consider installing the appropriate binary for the runner architecture, or document the amd64-only constraint explicitly in the workflow description.

@copilot please address this.

@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, /codebase-design, and /grill-with-docs — requesting changes on correctness and reliability issues.

📋 Key Themes & Highlights

Key Themes

  • Silent error swallowing: Missing -e flag in the scan step and an under-guarded grype_exit check can let errors pass undetected, producing a false-clean summary.
  • Fragile manifest integrity check: Using sha256sum on the raw manifest bytes returned by docker buildx imagetools inspect --raw may not be byte-stable — consider comparing the OCI digest field directly.
  • License policy likely too narrow: .grant.yaml omits common Debian/Ubuntu OS-level licenses (GPL-2.x, LGPL, MPL-2.0); the first real run will likely generate false-positive license failures.
  • Hardcoded tool versions in two places: The fallback summary.json duplicates version strings from the env block; a version bump will hit one but miss the other.
  • Hard-coded x86_64 assertion: Produces an unhelpful failure if the runner architecture changes.
  • Runner image inconsistency: ubuntu-latest on the scan job vs ubuntu-slim elsewhere — likely intentional (needs docker buildx), but should be documented.

Positive Highlights

  • ✅ Excellent supply-chain hygiene: all tools downloaded with pinned SHA-256 checksums.
  • ✅ Immutable-digest scanning is exactly the right approach for reproducible results.
  • ✅ Comprehensive error propagation via record_error / record_image_error throughout the scan loop.
  • ✅ Deduplicated issue creation via deduplicate-by-title prevents issue spam on repeated runs.
  • ✅ Clean separation between the scan job and the agentic reporting step.

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

Comments that could not be inline-anchored

.github/workflows/daily-squid-image-scan.md:2112

[/tdd] The outer scan step uses set -uo pipefail (missing -e), while the install step uses set -euo pipefail. An unhandled error mid-loop won't abort the step and can silently produce a false-clean summary.

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

Change the scan step preamble:

set -euo pipefail

If the intent is to allow the loop to continue after per-image errors (which is handled via record_error), add a comment explaining the deliberate omission of -e so futur…

.github/workflows/daily-squid-image-scan.md:2087

[/codebase-design] test &quot;$(uname -m)&quot; = &quot;x86_64&quot; hard-codes the architecture and silently blocks arm64 runners. If CI ever switches to an arm runner, the install step fails with no useful message.

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

Replace with an explicit, informative error:

arch=&quot;$(uname -m)&quot;
if [ &quot;$arch&quot; != &#39;x86_64&#39; ]; then
  echo &quot;Unsupported architecture: $arch. Only x86_64 is supported.&quot; &gt;&amp;2
  exit 1
fi

Or, better yet, parameterise the download URL so arm64 i…

.github/workflows/daily-squid-image-scan.md:2276

[/tdd] Grype is run twice for each platform: once to get JSON counts and once to get a human-readable table. The second run (line 2275) discards its exit code into grype_exit but it is only checked on the condition platform_critical -eq 0. If grype exits non-zero for a reason other than critical CVEs (e.g. DB corruption), the error is silently swallowed.

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

Check grype_exit unconditionally:

if [ &quot;$grype_exit&quot; -ne 0 ]; then
  record_im…

</details>

<details><summary>.github/workflows/daily-squid-image-scan.md:2194</summary>

**[/tdd]** The manifest integrity check (`sha256sum -c`) is performed against the raw bytes of the downloaded JSON file, not the OCI digest value. OCI content digests are computed from the serialised manifest bytes, but `docker buildx imagetools inspect --raw` may normalise whitespace or add a trailing newline, making the check fragile.

&lt;details&gt;
&lt;summary&gt;💡 Why this matters&lt;/summary&gt;

If the manifest bytes differ even by a single byte from what was originally digested (e.g. pretty-printed vs …

</details>

<details><summary>.github/workflows/daily-squid-image-scan.md:2046</summary>

**[/grill-with-docs]** The fallback `summary.json` hardcodes tool versions (`syft: &quot;1.49.0&quot;`, etc.) inline at two places — here in the fallback and at line 2363 in the real summary. If the versions are ever bumped, one location is likely to be missed.

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

Extract the versions into environment variables at the top of the step (where they already appear as `SYFT_VERSION`, etc.) and reference them from both the fallback and the summary generation via `--…

</details>

<details><summary>.grant.yaml:2425</summary>

**[/grill-with-docs]** The allowlist does not include `GPL-2.0-only`, `LGPL-2.1-or-later`, or `MPL-2.0`, which appear in many system packages inside Debian/Ubuntu base images (e.g. `bash`, `libc`). Grant will reject these and trigger false-positive license failures on every scan run until the list is extended.

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

Review what licenses the current squid image actually carries (the first scan will reveal this), then extend the allowlist. Typical additio…

</details>

<details><summary>.github/workflows/daily-squid-image-scan.md:2065</summary>

**[/codebase-design]** `runs-on: ubuntu-latest` is used for the scan job, but other jobs in this repo (e.g. the `activation` job above) use `ubuntu-slim`. Using `ubuntu-latest` is slightly heavier and less consistent with the rest of the workflow.

Consider switching to `ubuntu-slim` for alignment, or document why the full image is required (e.g., `docker buildx` availability).

@copilot please address this.

</details>

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@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_CHANGES — 3 high-severity blocking issues found

The workflow has three correctness issues that can suppress security alerts entirely, and two medium issues that should be fixed before this runs daily in production.

🔴 Blocking issues (high severity)
  1. Fail-open in safe_outputs (line 1557): When the threat-detection LLM fails, the entire safe_outputs job is skipped. Real vulnerability findings collected by the agent are never filed as issues — the workflow silently succeeds with no output.

  2. Drift detection returns image_updated: false when registry is unreachable (lines ~1787–1798): A registry outage causes every image to be reported as up-to-date, masking unknown drift state. The operational_errors array captures the fetch failure, but the image_updated field contradicts it.

  3. Scan step missing -e in set -uo pipefail (line 1721): Without -e, silent intermediate failures (e.g. a failed jq write) allow the script to continue, accumulate zero counts, and produce a passing summary with critical_vulnerabilities: 0.

🟡 Medium issues (should fix)
  1. Agent burns AI credits when scan_image fails (line 380): The agent condition allows the LLM to run to completion against a placeholder summary, wasting quota and potentially creating duplicate operational-failure issues, before the post-step gate kills the job.

  2. Issue deduplication relies on the LLM reproducing an exact title string (prompt line ~2390): Any capitalization or digest-length variation breaks deduplication, creating duplicate issues or silently merging new findings into old closed ones.

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

Comments that could not be inline-anchored

.github/workflows/daily-squid-image-scan.lock.yml:1557

Fail-open: safe_outputs is silently skipped when detection fails, causing all vulnerability findings to be dropped with no GitHub issues created.

<details>
<summary>💡 Details and suggested fix</summary>

The condition on line 1557:

if: (!cancelled()) &amp;&amp; needs.agent.result != &#39;skipped&#39; &amp;&amp; needs.detection.result == &#39;success&#39;

When threat-detection times out, hits an API error, or exceeds its invocation cap, needs.detection.result == &#39;success&#39; is false and safe_outputs is…

.github/workflows/daily-squid-image-scan.lock.yml:1797

Drift detection silently reports image_updated=false when the registry is unreachable, masking unknown drift state as "no change".

<details>
<summary>💡 Details and suggested fix</summary>

When docker buildx imagetools inspect &quot;$image_tag&quot; fails (registry down, auth error, firewall block), the outer else branch sets current_digest=&quot;unknown&quot; but leaves image_updated=false. The summary written to image-results.jsonl therefore reports the image as up-to-date with `image_updated: …

.github/workflows/daily-squid-image-scan.lock.yml:1721

Scan step uses set -uo pipefail but omits -e, allowing silent failures that produce zero-finding summaries without aborting the script.

<details>
<summary>💡 Details and suggested fix</summary>

Line 1721 (and its mirror on line 2112 in the .md compiled output):

set -uo pipefail

Without -e, commands that fail but are not wrapped in if ! (e.g. intermediate jq file-writes, grype db status) continue silently. This means an empty or absent output file propagates down…

.github/workflows/daily-squid-image-scan.lock.yml:380

Agent job runs and burns AI credits when scan_image fails, then a post-step gate kills it — the LLM executes to completion first, consuming quota with no actionable output.

<details>
<summary>💡 Details and suggested fix</summary>

Line 380:

if: (always() &amp;&amp; needs.scan_image.result != &#39;skipped&#39;) &amp;&amp; (needs.activation.outputs.daily_ai_credits_exceeded != &#39;true&#39;)

When scan_image has result failure, this condition is satisfied. The "Ensure image scan summary exists" step syn…

.github/workflows/daily-squid-image-scan.md:2390

Issue deduplication depends entirely on the LLM producing an exact title format — any variation creates a duplicate open issue or silently merges a new finding into an old closed one.

<details>
<summary>💡 Details and suggested fix</summary>

The prompt at line 2390 instructs the agent:
> Use the title Container findings for &lt;first 12 characters of index_digest&gt;

The deduplicate_by_title: true safe-output handler matches on the full, prefix-adjusted title. The deduplication logic is th…

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

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

Copy link
Copy Markdown
Collaborator

@copilot rename agentic workflow file to match intent

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

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Done. I ran the pr-finisher pass and pushed fixes in d8d5123 (noop issue suppression, explicit linux/amd64+linux/arm64 platform filtering, runner-arch-aware pinned tool installs, and corrected Grype exit-code handling).

@pelikhan
pelikhan merged commit ab20814 into main Jul 22, 2026
@pelikhan
pelikhan deleted the copilot/add-daily-agentic-workflow branch July 22, 2026 22:59
Copilot stopped work on behalf of pelikhan due to an error July 22, 2026 22:59
@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.

3 participants