Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion .github/actions/security-container-scan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ steps:
| `grype-image` | Grype container image to use for scanning. Override to pin to a specific digest for supply-chain hardening. | No | `anchore/grype:latest` |
| `report-json` | Filename for the JSON report. | No | `grype-results.json` |
| `report-sarif` | Filename for the SARIF report. | No | `grype-results.sarif` |
| `report-table` | Filename for the human-readable table report (always generated). | No | `grype-results.txt` |
| `upload-artifact` | Upload reports as a workflow artifact. | No | `true` |
| `artifact-name` | Artifact name for uploaded reports. | No | `grype-container-scan` |
| `generate-sbom` | Generate and upload an SBOM via `anchore/sbom-action`. | No | `true` |
Expand All @@ -131,10 +132,12 @@ steps:
| `detail` | Free-form detail string corresponding to `status`. |
| `report_json` | Path to the JSON report (if generated). |
| `report_sarif` | Path to the SARIF report (if generated). |
| `report_table` | Path to the table report (always generated). |

## Notes

- **Step summary is count-only**: the `$GITHUB_STEP_SUMMARY` output shows total matches and Critical/High/Medium/Low counts, but does **not** list individual CVE IDs or affected packages. On public repositories, run summaries are world-readable, and publishing a list of unresolved CVEs + package versions amounts to handing attackers a roadmap. Per-CVE detail is available in the JSON/SARIF artifact (collaborators only) or, when `upload-sarif: true`, in the Security tab.
- **Step summary is count-only**: the `$GITHUB_STEP_SUMMARY` output shows total matches and Critical/High/Medium/Low counts, but does **not** list individual CVE IDs or affected packages. On public repositories, run summaries are world-readable, and publishing a list of unresolved CVEs + package versions amounts to handing attackers a roadmap. Per-CVE detail is available in the JSON/SARIF/table artifact (collaborators only) or, when `upload-sarif: true`, in the Security tab.
- **Three artifact formats**: each scan produces JSON (Grype-native, used by tooling and `jq` drill-down), SARIF (GitHub code scanning / IDE viewers), and a plain-text table (drop-in readable for reviewers who don't want to touch `jq`). All three are bundled into the same workflow artifact.
- **Supply chain**: `grype-image` defaults to `anchore/grype:latest` for ease of adoption and DB freshness. For hardened pipelines, override it to a specific digest (`anchore/grype@sha256:...`) and refresh periodically.
- **SBOM generation**: enabled by default; set `generate-sbom: "false"` to skip when you only need the vulnerability scan.
- **Multi-arch**: Grype scans the image variant that is loaded into the local Docker daemon. When the runner is `linux/amd64` and you need to scan `linux/arm64`, pull with `--platform linux/arm64` first.
Expand Down
22 changes: 21 additions & 1 deletion .github/actions/security-container-scan/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ inputs:
description: 'Filename for SARIF report'
required: false
default: grype-results.sarif
report-table:
description: 'Filename for the human-readable table report (always produced).'
required: false
default: grype-results.txt
upload-artifact:
description: 'Upload reports as a workflow artifact'
required: false
Expand Down Expand Up @@ -86,6 +90,9 @@ outputs:
report_sarif:
description: 'Path to SARIF report (if generated)'
value: ${{ steps.final.outputs.report_sarif }}
report_table:
description: 'Path to the table report (always generated).'
value: ${{ steps.final.outputs.report_table }}

runs:
using: composite
Expand Down Expand Up @@ -116,14 +123,15 @@ runs:
format: ${{ inputs.sbom-format }}
artifact-name: ${{ inputs.sbom-artifact-name }}

- name: Run Grype scan (JSON + SARIF)
- name: Run Grype scan (JSON + SARIF + table)
id: grype
if: ${{ steps.precheck.outputs.image_exists == 'true' }}
shell: bash
env:
IMAGE: ${{ inputs.image }}
REPORT_JSON: ${{ inputs.report-json }}
REPORT_SARIF: ${{ inputs.report-sarif }}
REPORT_TABLE: ${{ inputs.report-table }}
FAIL_ON: ${{ inputs.fail-on }}
GRYPE_IMAGE: ${{ inputs.grype-image }}
run: |
Expand Down Expand Up @@ -152,6 +160,12 @@ runs:
"${GRYPE_IMAGE}" \
"${IMAGE}" -o sarif > "${REPORT_SARIF}" || true

docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME/.cache/grype:/root/.cache/grype" \
"${GRYPE_IMAGE}" \
"${IMAGE}" -o table > "${REPORT_TABLE}" || true

echo "exit_code=${scan_rc}" >> "$GITHUB_OUTPUT"
if [ $scan_rc -eq 0 ]; then
echo "status=ok" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -191,9 +205,13 @@ runs:
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
# if-no-files-found: ignore defensively covers the case where Grype
# fails mid-run and produces only a subset of the three formats.
path: |
${{ inputs.report-json }}
${{ inputs.report-sarif }}
${{ inputs.report-table }}
if-no-files-found: ignore

- name: Write scan summary
if: ${{ inputs.write-summary == 'true' }}
Expand Down Expand Up @@ -229,6 +247,7 @@ runs:
INPUT_FAIL_ON: ${{ inputs.fail-on }}
INPUT_REPORT_JSON: ${{ inputs.report-json }}
INPUT_REPORT_SARIF: ${{ inputs.report-sarif }}
INPUT_REPORT_TABLE: ${{ inputs.report-table }}
INPUT_FAIL_BUILD: ${{ inputs.fail-build }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -260,6 +279,7 @@ runs:
echo "detail=${detail}" >> "$GITHUB_OUTPUT"
echo "report_json=${INPUT_REPORT_JSON}" >> "$GITHUB_OUTPUT"
echo "report_sarif=${INPUT_REPORT_SARIF}" >> "$GITHUB_OUTPUT"
echo "report_table=${INPUT_REPORT_TABLE}" >> "$GITHUB_OUTPUT"

if [ "${INPUT_FAIL_BUILD}" = "true" ] && [ "${status}" != "ok" ]; then
echo "Failing build due to status=${status}: ${detail}" 1>&2
Expand Down
Loading