README
ΒΆ
β grype_me
An easy to use GitHub Action to scan the supply chain of your project for known vulnerabilities using Anchore Grype and generate badges with detailed reports.
Quick Start
- uses: actions/checkout@v4
with: { fetch-depth: 0, fetch-tags: true }
- uses: TomTonic/grype_me@v1
with:
scan: 'latest_release'
fail-build: false
gist-token: ${{ secrets.GIST_TOKEN }}
gist-id: ${{ vars.GRYPE_BADGE_GIST_ID }}
This scans your latest release, uploads a shields.io badge JSON and a detailed Markdown report to a GitHub Gist, and makes the badge URL available as a step output. Click a badge above to see a live example report.
For a full example see how this project runs a daily scan to update the two badges in this README.
Note: The default scan mode is
latest_release, which scans your highest semver tag. If your repo has no tags yet, usescan: 'head'instead.
Note: Due to automated daily updates of this action, pinning its version may yield unexpected behavior. See Daily tag updates.
Features
- π Uses the latest Grype version with a daily-updated vulnerability database (bundled in the action image)
- β‘ ~2Γ faster than installing Grype during a workflow run (no ~200 MB DB download)
- π¦ Multiple scan targets: repositories, container images, directories, or SBOMs
- π― Latest release scanning: Ideal for nightly scans of your published releases
- π Detailed vulnerability counts by severity (Critical, High, Medium, Low)
- π¨ Fail builds on vulnerabilities at or above a configurable threshold
- π§ Option to show only vulnerabilities with available fixes
- π·οΈ Dynamic badge generation with linked Markdown reportsβno extra action needed
How It Works
This action runs Grype with a pre-downloaded vulnerability database inside a Docker container. It supports two modes:
| Mode | Input | Description |
|---|---|---|
| Repository | scan |
Scans source code via dependency manifests (go.mod, package.json, requirements.txt, etc.) |
| Artifact | image / path / sbom |
Scans container images, directories, or SBOM files |
Repository mode
Grype reads dependency manifests directly from the repoβno build required. This works especially well for Go projects.
- β Detects source-declared dependencies without compiling
- β Great for nightly scans of tagged releases
- β Runtime-only or dynamically downloaded dependencies require artifact mode
Scan modes:
latest_releaseβ Scans your highest stable semver tag (default)headβ Scans the current working directory<tag/branch>β Scans a specific ref
Artifact mode
Use image, path, or sbom to scan build artifacts. These inputs are mutually exclusive with scan.
Usage
Nightly Release Scan with Badge
See .github/workflows/security-badge.yml for the workflow that generates the badges shown in this README. Here's the essential pattern:
name: Security Badge
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
update-badge:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0, fetch-tags: true }
- uses: TomTonic/grype_me@v1
with:
scan: 'latest_release'
fail-build: false
gist-token: ${{ secrets.GIST_TOKEN }}
gist-id: ${{ vars.GRYPE_BADGE_GIST_ID }}
gist-filename: 'my-project'
This writes three files to the gist:
my-project.jsonβ shields.io endpoint badge JSONmy-project.mdβ detailed Markdown report with CVE tablemy-project-grype.jsonβ raw Grype scan output
Container Image Scan
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- uses: TomTonic/grype_me@v1
with:
image: 'myapp:${{ github.sha }}'
fail-build: true
severity-cutoff: 'high'
Using the Badge in Your README
After the first workflow run, add the badge to your README:
[](https://gist.githubusercontent.com/YOUR_USER/YOUR_GIST_ID/raw/my-project.md)
The badge links to the detailed Markdown report. Clicking it shows the full CVE breakdown.
Setup
- Create a GitHub Gist at gist.github.com with any initial file (e.g.,
init.txtwith content{}). Copy the Gist ID from the URL. - Create a Personal Access Token at GitHub Settings β Developer settings β Personal access tokens with
gistscope. - Add secrets/variables to your repository:
- Secret
GIST_TOKENβ the PAT from step 2 - Variable
GRYPE_BADGE_GIST_IDβ the gist ID from step 1
- Secret
Inputs
Scan Target (mutually exclusive)
| Input | Description | Default |
|---|---|---|
scan |
Repository scan: latest_release, head, or a tag/branch |
latest_release |
image |
Container image to scan (e.g., alpine:latest) |
β |
path |
Directory or file to scan | β |
sbom |
SBOM file (Syft, CycloneDX, SPDX) | β |
Options
| Input | Description | Default |
|---|---|---|
fail-build |
Fail if vulnerabilities β₯ severity-cutoff |
false |
severity-cutoff |
Threshold: negligible, low, medium, high, critical |
medium |
output-file |
Save results to JSON file | β |
only-fixed |
Only report vulnerabilities with fixes available | false |
db-update |
Update DB before scanning (see Performance) | false |
Gist Integration
| Input | Description | Default |
|---|---|---|
gist-token |
GitHub PAT with gist scope (store as secret) |
β |
gist-id |
ID of the gist to update | β |
gist-filename |
Base filename for gist files (e.g., my-project) |
auto from scan mode |
Advanced inputs
| Input | Description | Default |
|---|---|---|
debug |
Print environment variables (may expose secrets) | false |
Outputs
| Output | Description |
|---|---|
cve-count |
Total vulnerabilities found |
critical / high / medium / low |
Count per severity |
grype-version |
Grype version used |
db-version |
Vulnerability database version |
json-output |
Path to output file (if output-file set) |
badge-url |
shields.io badge URL (dynamic endpoint when gist configured, static otherwise) |
report-url |
URL to the detailed Markdown report in the gist |
Performance
The action image is rebuilt daily with the latest Grype and vulnerability database. This eliminates the ~200 MB database download, making scans roughly 2Γ faster than running Grype manually in a GitHub Actions workflow.
| Scenario | Recommendation |
|---|---|
| Nightly scans | Use pre-baked DB (default) β fast and fresh enough |
| Security gates before release | Consider db-update: true for absolute freshness |
- uses: TomTonic/grype_me@v1
with:
scan: 'latest_release'
db-update: true # Download latest DB before scanning
Daily tag updates
The published container image is rebuilt daily to always contain the newest Grype release and the latest vulnerability database. As a result, moving tags are shifted to the new image every day: latest, v1, v1.2, and v1.2.3. By design, the patch level only refers to the patch level of this action, not including the vulnerability database.
Only the following tags remain immutable and stable:
v1.2.3-releasev1.2.3_grype-0.xyz.0_db-YYYY-MM-DDThh-mm-ssZ
This behavior is intentional but can be surprising if you try to pin to a patch level tag in CI or other automation. If you require an unchanging image, pin to one of the immutable tags (for example the db-specific ..._grype-..._db-... tag or the -release tag).
Badge
The action generates a dynamic shields.io badge that shows vulnerability counts with color-coding:
| Color | Meaning |
|---|---|
| No vulnerabilities | |
| Low severity only | |
| Medium severity | |
| High severity | |
| Critical severity |
When gist integration is configured, the badge is a shields.io endpoint badge that updates automatically. Clicking the badge opens the detailed Markdown report showing every CVE with package, version, fix status, and description.
Without gist integration, the badge-url output contains a static shields.io URL that can be displayed in workflow summaries:
- uses: TomTonic/grype_me@v1
id: grype
with: { scan: 'latest_release' }
- run: |
echo "" >> $GITHUB_STEP_SUMMARY
Alerting Examples
Create GitHub Issue
- uses: TomTonic/grype_me@v1
id: grype
with: { scan: 'latest_release' }
- if: steps.grype.outputs.critical > 0
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'π¨ Critical vulnerabilities detected',
body: `Found ${{ steps.grype.outputs.critical }} critical CVEs.\n\n[View report](${{ steps.grype.outputs.report-url }})`,
labels: ['security', 'critical']
});
Slack Notification
- uses: TomTonic/grype_me@v1
id: grype
with: { scan: 'latest_release' }
- if: steps.grype.outputs.cve-count > 0
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "π Scan: ${{ steps.grype.outputs.critical }} critical, ${{ steps.grype.outputs.high }} high CVEs"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
License
BSD 3-Clause License β see LICENSE.