Skip to content

feat(security): add Docker Scout scan composite and integrate into pr-security-scan workflow#142

Merged
bedatty merged 2 commits intodevelopfrom
feat/docker-scout-scan
Mar 13, 2026
Merged

feat(security): add Docker Scout scan composite and integrate into pr-security-scan workflow#142
bedatty merged 2 commits intodevelopfrom
feat/docker-scout-scan

Conversation

@bedatty
Copy link
Contributor

@bedatty bedatty commented Mar 13, 2026

Lerian

GitHub Actions Shared Workflows


Description

Add a new src/security/docker-scout composite action that runs Docker Scout analysis (quickview, CVEs, recommendations) on locally built Docker images. Integrate it into the pr-security-scan reusable workflow as an opt-in feature via the new enable_docker_scout input (default false).

Additionally:

  • Fix uses: ./src/... refs in self-workflows (branch-cleanup, labels-sync) to use external refs for correct resolution by external callers
  • Standardize section comments across existing composite actions
  • Update AI assistant rules (.claude/commands/, .cursor/rules/)

Type of Change

  • feat: New workflow or new input/output/step in an existing workflow
  • fix: Bug fix in a workflow (incorrect behavior, broken step, wrong condition)
  • refactor: Internal restructuring with no behavior change

Breaking Changes

None. The enable_docker_scout input defaults to false, so existing callers are unaffected.

Testing

  • YAML syntax validated locally
  • Triggered a real workflow run on a caller repository using @develop or the beta tag
  • Verified all existing inputs still work with default values
  • Confirmed no secrets or tokens are printed in logs
  • Checked that unrelated workflows are not affected

Caller repo / workflow run: pending — will test via workflow_dispatch after merge to develop

Related Issues

Closes #

Summary by CodeRabbit

Release Notes

  • New Features

    • Docker Scout analysis now available in PR security scanning workflow (opt-in via enable_docker_scout flag)
    • New PR security reporter aggregates vulnerability scan results into formatted PR comments
  • Documentation

    • Updated documentation for composite actions and reusable workflows covering configurability, step grouping, and reserved input naming conventions
    • Enhanced guidance for Docker Scout integration and security scanning workflows

@bedatty bedatty requested a review from a team as a code owner March 13, 2026 14:11
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Walkthrough

This PR adds comprehensive documentation for GitHub Actions composites and reusable workflows covering configurability, reserved input/secret names, and step organization conventions. It introduces two new security actions (Docker Scout and PR Security Reporter), updates existing notification and workflow actions with enhanced logic and section comments, and migrates local action references to remote sources.

Changes

Cohort / File(s) Summary
Documentation: Configurability & Reserved Names
.claude/commands/composite.md, .claude/commands/gha.md, .claude/commands/workflow.md, .cursor/rules/composite-actions.mdc, .cursor/rules/reusable-workflows.mdc
Adds guidance on defaults-first configurability, three-layer input propagation (caller → reusable workflow → composite), step section titles with formatting rules, and prohibited GitHub reserved prefixes (GITHUB_\, ACTIONS_\, RUNNER_\*) for custom inputs/secrets.
Workflow Reference Migration
.github/workflows/branch-cleanup.yml, .github/workflows/labels-sync.yml
Replaces local action paths (./src/config/{branch-cleanup,labels-sync}) with remote references to LerianStudio/github-actions-shared-workflows@develop.
PR Security Scan Workflow Refactoring
.github/workflows/pr-security-scan.yml, docs/pr-security-scan-workflow.md
Introduces enable_docker_scout and enable_docker_scout_recommendations inputs; restructures scanning pipeline to use new pr-security-reporter action; replaces inline reporting script with dedicated action-based flow.
Docker Scout Action
src/security/docker-scout/action.yml, src/security/docker-scout/README.md
New composite action for Docker Scout image scanning; accepts image reference, DockerHub credentials, severity filters; outputs quickview, CVEs, vulnerability flag, and optional recommendations.
PR Security Reporter Action
src/security/pr-security-reporter/action.yml, src/security/pr-security-reporter/README.md
New composite action aggregating Trivy filesystem/image scans and Docker Scout findings into formatted PR comments with idempotent update-or-create logic; handles multi-source parsing, Markdown escaping, and conditional section rendering.
Notification Actions
src/notify/discord-release/action.yml, src/notify/slack-release/action.yml
Adds pre-flight beta-detection step, dry-run summary output, and conditional gating to skip notifications on beta releases when configured.
Build & Config Actions
src/build/docker-build-ts/action.yml, src/config/branch-cleanup/action.yml
Introduces section header comments for step organization (Setup, Image Configuration, Build & Push) and mode delineation (Merged Branch Mode, Stale Branch Mode); no functional changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive No related issue number provided in the description ('Closes #' left blank), which is acceptable for feature/refactor PRs but should be documented if an issue exists. If this PR addresses a specific issue, update the description with the issue number. If no issue exists, this is acceptable but consider creating tracking issues for future reference.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding Docker Scout scan composite and integrating it into the pr-security-scan workflow.
Description check ✅ Passed The description addresses all critical template sections: clear summary of changes, type of change marked correctly, no breaking changes noted, testing approach documented, and structure follows the template.
Out of Scope Changes check ✅ Passed Changes are focused on Docker Scout security integration and documentation updates. Scope includes rule updates (.claude/commands/, .cursor/rules/) which support the primary feature and are within acceptable scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/docker-scout-scan
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@bedatty bedatty self-assigned this Mar 13, 2026
coderabbitai[bot]
coderabbitai bot previously requested changes Mar 13, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/pr-security-scan.yml (1)

10-56: ⚠️ Potential issue | 🟠 Major

Missing workflow_dispatch trigger and dry_run input.

Per repo convention, reusable workflows must expose both workflow_call and workflow_dispatch triggers, plus a dry_run input (default: false) for manual testing and preview runs.

Proposed fix
 on:
   workflow_call:
     inputs:
+      dry_run:
+        description: 'Run in dry-run mode (preview only, no PR comments posted)'
+        type: boolean
+        default: false
       runner_type:
         description: 'GitHub runner type to use'
         type: string
         default: 'blacksmith-4vcpu-ubuntu-2404'
       # ... rest of inputs ...
+
+  workflow_dispatch:
+    inputs:
+      dry_run:
+        description: 'Run in dry-run mode (preview only, no PR comments posted)'
+        type: boolean
+        default: false
+      runner_type:
+        description: 'GitHub runner type to use'
+        type: string
+        default: 'blacksmith-4vcpu-ubuntu-2404'
+      # Mirror remaining workflow_call inputs here for manual dispatch

Based on learnings: "Enforce that every reusable GitHub Actions workflow in LerianStudio/github-actions-shared-workflows exposes both workflow_call and workflow_dispatch triggers, and includes a dry_run input (default: false)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr-security-scan.yml around lines 10 - 56, Add a manual
trigger and dry-run input to this reusable workflow: add a workflow_dispatch
trigger alongside the existing workflow_call, and add an inputs.dry_run boolean
(default: false) to the workflow_call inputs so consumers and manual runs can
perform preview/testing; modify the top-level triggers to include
workflow_dispatch and add the dry_run input entry (name it dry_run, type
boolean, default false) to the inputs block that currently contains runner_type,
filter_paths, etc., referencing the existing workflow_call and inputs sections.
src/notify/discord-release/action.yml (1)

43-63: ⚠️ Potential issue | 🟠 Major

Harden both Bash blocks: direct interpolation is injection-prone, and content is logged in plain text.

Move values to env variables, quote $GITHUB_OUTPUT, use printf for output, and redact content in dry-run logs. Per coding guidelines, never print potentially sensitive data via echo or step output.

Suggested hardening patch
     - name: Detect beta release
       id: beta
       shell: bash
+      env:
+        RELEASE_TAG: ${{ inputs.release-tag }}
       run: |
-        if [[ "${{ inputs.release-tag }}" == *"-beta."* ]]; then
-          echo "is_beta=true" >> $GITHUB_OUTPUT
+        if [[ "$RELEASE_TAG" == *"-beta."* ]]; then
+          echo "is_beta=true" >> "$GITHUB_OUTPUT"
         else
-          echo "is_beta=false" >> $GITHUB_OUTPUT
+          echo "is_beta=false" >> "$GITHUB_OUTPUT"
         fi
@@
     - name: Dry run summary
       if: inputs.dry-run == 'true'
       shell: bash
+      env:
+        RELEASE_TAG: ${{ inputs.release-tag }}
+        COLOR: ${{ inputs.color }}
+        USERNAME: ${{ inputs.username }}
+        SKIP_BETA: ${{ inputs.skip-beta }}
+        IS_BETA: ${{ steps.beta.outputs.is_beta }}
       run: |
         echo "::notice::DRY RUN — Discord notification will not be sent"
         echo "  webhook      : (configured)"
-        echo "  release-tag  : ${{ inputs.release-tag }}"
-        echo "  color        : ${{ inputs.color }}"
-        echo "  username     : ${{ inputs.username }}"
-        echo "  content      : ${{ inputs.content }}"
-        echo "  skip-beta    : ${{ inputs.skip-beta }}"
-        echo "  is_beta      : ${{ steps.beta.outputs.is_beta }}"
+        printf '  release-tag  : %s\n' "$RELEASE_TAG"
+        printf '  color        : %s\n' "$COLOR"
+        printf '  username     : %s\n' "$USERNAME"
+        echo "  content      : (redacted)"
+        printf '  skip-beta    : %s\n' "$SKIP_BETA"
+        printf '  is_beta      : %s\n' "$IS_BETA"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notify/discord-release/action.yml` around lines 43 - 63, The Bash blocks
are injection-prone and print potentially sensitive content; update the "if"
block and the "Dry run summary" step to set inputs (like inputs.release-tag,
inputs.color, inputs.username, inputs.skip-beta, inputs.content) into
environment variables instead of direct interpolation, write to GITHUB_OUTPUT
using a quoted redirect ("$GITHUB_OUTPUT") from a safe printf call in the beta
check (e.g., produce is_beta via printf), and change the Dry run summary to use
printf with the environment vars and redact or omit the actual inputs.content
value (replace with "[REDACTED]" or similar) so no sensitive text is echoed or
emitted via step outputs (refer to steps.beta.outputs.is_beta and the Dry run
summary step name when locating the run blocks to modify).
src/notify/slack-release/action.yml (1)

37-43: ⚠️ Potential issue | 🟠 Major

Harden Bash input interpolation against command injection at lines 39–43.

Direct ${{ inputs.* }} interpolation within the bash run script at lines 39–43 is vulnerable to command injection. These user-provided inputs expand as literal shell code before execution; a caller providing channel: "test'; rm -rf /" would inject commands.

Move inputs to env variables and use printf %s instead of echo:

Hardening patch
     - name: Dry run summary
       if: inputs.dry-run == 'true'
       shell: bash
+      env:
+        CHANNEL: ${{ inputs.channel }}
+        PRODUCT_NAME: ${{ inputs.product-name }}
+        RELEASE_TAG: ${{ inputs.release-tag }}
+        COLOR: ${{ inputs.color }}
+        ICON_EMOJI: ${{ inputs.icon-emoji }}
       run: |
         echo "::notice::DRY RUN — Slack notification will not be sent"
-        echo "  channel      : ${{ inputs.channel }}"
-        echo "  product-name : ${{ inputs.product-name }}"
-        echo "  release-tag  : ${{ inputs.release-tag }}"
-        echo "  color        : ${{ inputs.color }}"
-        echo "  icon-emoji   : ${{ inputs.icon-emoji }}"
+        printf '  channel      : %s\n' "$CHANNEL"
+        printf '  product-name : %s\n' "$PRODUCT_NAME"
+        printf '  release-tag  : %s\n' "$RELEASE_TAG"
+        printf '  color        : %s\n' "$COLOR"
+        printf '  icon-emoji   : %s\n' "$ICON_EMOJI"

Note: Lines 50–55 using env are safe—GitHub Actions sets environment variables directly without shell evaluation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notify/slack-release/action.yml` around lines 37 - 43, The DRY RUN bash
block interpolates GitHub Action inputs directly into the shell (channel,
product-name, release-tag, color, icon-emoji) which allows command injection;
change the step to export those inputs into environment variables (e.g.,
CHANNEL, PRODUCT_NAME, RELEASE_TAG, COLOR, ICON_EMOJI) via the step's env
mapping and then replace the echo lines with safe printing using printf '%s\n'
"$CHANNEL" etc. Ensure all references use the env var names (not `${{ inputs.*
}}`) and quote the variable expansions to prevent word-splitting and globbing
while keeping the same message format including the initial "::notice::DRY RUN —
Slack notification will not be sent".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/commands/composite.md:
- Around line 100-106: The fenced code block containing the three-layer flow
diagram is missing a language specifier; update the triple-backtick fence that
wraps the diagram to include a language tag (e.g., change ``` to ```text or
```plaintext) so linters accept it—look for the fenced block showing "Caller
repo  Reusable workflow  Composite" and add the language identifier immediately
after the opening backticks.

In @.claude/commands/gha.md:
- Around line 326-332: The fenced code block showing the ASCII table (starting
with "Caller repo              Reusable workflow           Composite" and ending
with "recommendations }}") is missing a language specifier; update the
triple-backtick fence around that block to include "text" (i.e., change ``` to
```text) so the block renders correctly and preserves spacing in the Markdown
(look for the exact fenced block containing "enable_docker_scout" /
"enable-recommendations" to modify).

In @.cursor/rules/composite-actions.mdc:
- Around line 140-146: The fenced code block showing the table lacks a language
specifier; update the block that begins with the triple backticks and the table
("Caller repo ... Composite") to use a plaintext/text fence (e.g., ```text) so
syntax highlighters render it consistently with other docs; locate the fenced
block in composite-actions.mdc and change the opening fence to include "text"
(or "plaintext") and keep the rest unchanged.

In @.cursor/rules/reusable-workflows.mdc:
- Around line 238-245: The fenced code block containing the three-column flow
diagram (the block starting with "Caller repo                   Reusable
workflow              Composite" and the ASCII table) is missing a language
specifier; update that fenced block to include a language identifier (use "text"
as in the proposed fix) so it becomes ```text ... ``` to ensure proper
rendering, mirroring the same change made in composite.md; locate the diagram
block in the reusable-workflows.mdc file and add the "text" specifier to the
opening fence.

In `@docs/pr-security-scan-workflow.md`:
- Around line 180-181: The inputs documentation table is missing the
enable_docker_scout_recommendations input; add a new row for
enable_docker_scout_recommendations with type boolean, default true, and
description "Enable Docker Scout recommendations to surface Dockerfile issues
(non-root user, missing attestations, base image gaps)" so the table matches the
workflow's defined input (enable_docker_scout_recommendations) referenced
earlier in the file.

In `@src/security/docker-scout/action.yml`:
- Around line 111-119: The run step currently assigns QUICKVIEW_OUTPUT and
CVES_OUTPUT by interpolating '${{ steps.quickview.outputs.quickview }}' and '${{
steps.cves.outputs.cves }}' into single-quoted shell strings which can break on
quotes/newlines; instead export these outputs as environment variables or use a
here-doc to safely capture multi-line content before grepping: set
QUICKVIEW_OUTPUT and CVES_OUTPUT via the GitHub Actions env mechanism or use
printf '%s' with a here-doc so the grep line that checks for
'(critical|high|medium|low)\s+[1-9]' reads from those safe variables; update
references to QUICKVIEW_OUTPUT, CVES_OUTPUT, and HAS_VULNS accordingly so no
unquoted shell interpolation occurs.

---

Outside diff comments:
In @.github/workflows/pr-security-scan.yml:
- Around line 10-56: Add a manual trigger and dry-run input to this reusable
workflow: add a workflow_dispatch trigger alongside the existing workflow_call,
and add an inputs.dry_run boolean (default: false) to the workflow_call inputs
so consumers and manual runs can perform preview/testing; modify the top-level
triggers to include workflow_dispatch and add the dry_run input entry (name it
dry_run, type boolean, default false) to the inputs block that currently
contains runner_type, filter_paths, etc., referencing the existing workflow_call
and inputs sections.

In `@src/notify/discord-release/action.yml`:
- Around line 43-63: The Bash blocks are injection-prone and print potentially
sensitive content; update the "if" block and the "Dry run summary" step to set
inputs (like inputs.release-tag, inputs.color, inputs.username,
inputs.skip-beta, inputs.content) into environment variables instead of direct
interpolation, write to GITHUB_OUTPUT using a quoted redirect ("$GITHUB_OUTPUT")
from a safe printf call in the beta check (e.g., produce is_beta via printf),
and change the Dry run summary to use printf with the environment vars and
redact or omit the actual inputs.content value (replace with "[REDACTED]" or
similar) so no sensitive text is echoed or emitted via step outputs (refer to
steps.beta.outputs.is_beta and the Dry run summary step name when locating the
run blocks to modify).

In `@src/notify/slack-release/action.yml`:
- Around line 37-43: The DRY RUN bash block interpolates GitHub Action inputs
directly into the shell (channel, product-name, release-tag, color, icon-emoji)
which allows command injection; change the step to export those inputs into
environment variables (e.g., CHANNEL, PRODUCT_NAME, RELEASE_TAG, COLOR,
ICON_EMOJI) via the step's env mapping and then replace the echo lines with safe
printing using printf '%s\n' "$CHANNEL" etc. Ensure all references use the env
var names (not `${{ inputs.* }}`) and quote the variable expansions to prevent
word-splitting and globbing while keeping the same message format including the
initial "::notice::DRY RUN — Slack notification will not be sent".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 87b0d7f6-7400-4cfe-b897-d1752874e2d4

📥 Commits

Reviewing files that changed from the base of the PR and between ae466e0 and 749fccb.

📒 Files selected for processing (16)
  • .claude/commands/composite.md
  • .claude/commands/gha.md
  • .claude/commands/workflow.md
  • .cursor/rules/composite-actions.mdc
  • .cursor/rules/reusable-workflows.mdc
  • .github/workflows/branch-cleanup.yml
  • .github/workflows/labels-sync.yml
  • .github/workflows/pr-security-scan.yml
  • docs/pr-security-scan-workflow.md
  • src/build/docker-build-ts/action.yml
  • src/config/branch-cleanup/action.yml
  • src/config/labels-sync/action.yml
  • src/notify/discord-release/action.yml
  • src/notify/slack-release/action.yml
  • src/security/docker-scout/README.md
  • src/security/docker-scout/action.yml

@bedatty bedatty dismissed coderabbitai[bot]’s stale review March 13, 2026 14:23

All comments addressed — valid suggestions applied in d988c56, out-of-scope suggestions (workflow_dispatch, discord/slack hardening) dismissed as not applicable to this PR.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 13, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/security/pr-security-reporter/action.yml`:
- Around line 188-217: The Docker Scout reporting is currently gated by
dockerScanEnabled so Scout data (scoutQuickview, scoutCves, scoutHasVulns) and
flags (hasFindings, hasScanErrors) are never processed when only
dockerScoutEnabled is true; change the conditional to run the Scout analysis
block when dockerScoutEnabled is true (ignore dockerScanEnabled for rendering
Scout output), ensure scoutHasVulns sets hasFindings regardless of
dockerScanEnabled, set hasScanErrors only when Scout output is missing
(scoutQuickview && scoutCves both empty) and dockerScoutEnabled is true, and
only render scoutRecommendations when scoutRecommendations is present (or when
the Scout analysis block was emitted) so recommendations don't appear without
the analysis; update references to dockerScoutEnabled, dockerScanEnabled,
scoutQuickview, scoutCves, scoutHasVulns, scoutRecommendations, hasFindings,
hasScanErrors and body accordingly.

In `@src/security/pr-security-reporter/README.md`:
- Around line 1-8: Add a real top-level Markdown H1 at the start of README.md to
satisfy MD041 by inserting "# pr-security-reporter" as the first line (or
replace the embedded <h1> tag in the HTML table with the same Markdown heading);
ensure the Markdown H1 appears before any HTML so the linter recognizes it as
the file's primary heading.
- Around line 12-20: The README inputs table is missing the
scout-recommendations input declared in action.yml; update the table to add a
row for `scout-recommendations` (match the exact name, default value `""`, and
required flag as in action.yml) so the README's inputs contract exactly mirrors
action.yml and callers can wire the recommendations section; ensure the column
values (Description, Required, Default) match the semantics from
`src/security/pr-security-reporter/action.yml`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2bcb0d7c-bf30-4183-9fd9-caef4408d5fe

📥 Commits

Reviewing files that changed from the base of the PR and between 749fccb and 2553274.

📒 Files selected for processing (2)
  • src/security/pr-security-reporter/README.md
  • src/security/pr-security-reporter/action.yml

@bedatty bedatty dismissed coderabbitai[bot]’s stale review March 13, 2026 14:26

All comments addressed.

@bedatty bedatty merged commit a36d49d into develop Mar 13, 2026
1 check passed
@github-actions github-actions bot deleted the feat/docker-scout-scan branch March 13, 2026 14:30
@coderabbitai coderabbitai bot mentioned this pull request Mar 20, 2026
14 tasks
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.

1 participant