diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml new file mode 100644 index 0000000..4b7d92a --- /dev/null +++ b/.github/workflows/scan.yaml @@ -0,0 +1,90 @@ +--- +name: Scan + +on: + workflow_dispatch: + schedule: + # A CVE disclosed after an image is published only surfaces on a re-scan, + # so sweep every published image daily. + - cron: "17 6 * * *" + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + scan: ${{ steps.matrix.outputs.scan }} + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Generate matrix + id: matrix + run: | + set -euo pipefail + + scan="[]" + for metadata in images/*/metadata.yaml; do + image="$(basename "$(dirname "${metadata}")")" + version="$(yq '.version' "${metadata}")" + while read -r platform; do + case "${platform}" in + linux/amd64) runner="ubuntu-latest" ;; + linux/arm64) runner="ubuntu-24.04-arm" ;; + *) echo "unsupported platform: ${platform}" >&2; exit 1 ;; + esac + scan="$(jq -c \ + --arg image "${image}" --arg version "${version}" \ + --arg slug "${platform//\//-}" --arg runner "${runner}" \ + '. + [{image: $image, version: $version, slug: $slug, runner: $runner}]' \ + <<<"${scan}")" + done < <(yq '.platforms[]' "${metadata}") + done + + echo "scan=${scan}" >>"${GITHUB_OUTPUT}" + + scan: + name: scan (${{ matrix.image }}, ${{ matrix.slug }}) + needs: prepare + if: ${{ needs.prepare.outputs.scan != '[]' }} + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: read + security-events: write + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.prepare.outputs.scan) }} + steps: + - name: Login to GHCR + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Trivy resolves the manifest list to the runner's own architecture, so + # each platform is scanned by the native runner that built it. + - name: Scan published image + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ matrix.version }} + scanners: vuln + severity: HIGH,CRITICAL + limit-severities-for-sarif: true + format: sarif + output: trivy.sarif + hide-progress: true + + - name: Upload SARIF + uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + with: + sarif_file: trivy.sarif + category: trivy-${{ matrix.image }}-${{ matrix.slug }} diff --git a/AGENTS.md b/AGENTS.md index 13a198a..8186b73 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,3 +22,5 @@ container build -t :local -f images//Dockerfile images/ ``` CI (`.github/workflows/build.yaml`) builds changed images on native amd64/arm64 runners, merges digests into a manifest list, and attaches SBOM + provenance attestations. Third-party actions are pinned to commit SHAs — keep it that way when editing workflows. + +`.github/workflows/scan.yaml` re-scans every published image daily with Trivy and reports HIGH/CRITICAL CVEs to GitHub code scanning. It reports and does not gate: most findings are in vendored upstream artefacts this repo cannot patch. It reads the `version` from each `metadata.yaml`, so that field must always name a tag that is actually published. diff --git a/README.md b/README.md index 03f97e1..2af6413 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ when this repo rebuilds the same upstream version; the rolling `X.Y`, `X`, and - No docker CLI, except CI-runner images that build against an injected dind sidecar - Base images pinned by digest, tool versions pinned and updated by Renovate - SBOM and SLSA provenance attestations attached to every image +- Every published image re-scanned daily for HIGH/CRITICAL CVEs, reported to GitHub code scanning ### claude-code @@ -97,6 +98,10 @@ CI builds and tags whatever version the metadata declares — bump it in the sam Trigger a manual build of any (or every) image via *Actions → Build → Run workflow*. +`.github/workflows/scan.yaml` runs daily and scans every published image, one job per image per platform on its native runner, uploading Trivy results to GitHub code scanning. A CVE disclosed after an image ships only surfaces on a re-scan, so this — not the build — is what catches them. + +The scan reports rather than gates. Almost everything it finds lives in vendored upstream artefacts (Go binaries, the runner's .NET runtime, npm's own bundled dependencies) where a patched upstream module exists but this repo only consumes a release build, so it cannot act on the fix until upstream rebuilds. Gating merges on that would block PRs on work the repo cannot do. Treat the alerts as a queue: the ones worth acting on are the OS packages a base-image bump fixes, and language deps this repo installs directly. + ## Local development Build with [Apple container](https://github.com/apple/container) (or any BuildKit-compatible builder): diff --git a/images/infisical-mcp/Dockerfile b/images/infisical-mcp/Dockerfile index 7a2f459..0a1309f 100644 --- a/images/infisical-mcp/Dockerfile +++ b/images/infisical-mcp/Dockerfile @@ -2,8 +2,13 @@ FROM node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432 # renovate: datasource=npm depName=@infisical/mcp ARG INFISICAL_MCP_VERSION=0.0.23 +# The node base image ships an older bundled npm whose own vendored deps carry +# known CVEs; upgrading npm itself is the only way to move them. +# renovate: datasource=npm depName=npm +ARG NPM_VERSION=12.0.1 -RUN npm install -g "@infisical/mcp@${INFISICAL_MCP_VERSION}" \ +RUN npm install -g "npm@${NPM_VERSION}" \ + && npm install -g "@infisical/mcp@${INFISICAL_MCP_VERSION}" \ && npm cache clean --force \ && node --check /usr/local/lib/node_modules/@infisical/mcp/dist/index.js diff --git a/images/infisical-mcp/metadata.yaml b/images/infisical-mcp/metadata.yaml index 1c417f4..139b553 100644 --- a/images/infisical-mcp/metadata.yaml +++ b/images/infisical-mcp/metadata.yaml @@ -1,8 +1,12 @@ --- name: infisical-mcp description: Infisical MCP server (secrets management over MCP) -# renovate: datasource=npm depName=@infisical/mcp -version: 0.0.23 +# -. Bump the revision whenever this image's +# contents change without INFISICAL_MCP_VERSION moving. Renovate tracks upstream +# on INFISICAL_MCP_VERSION in the Dockerfile — deliberately not annotated here, +# since a -N suffix is a semver pre-release and would be "upgraded" straight +# back to the bare release. +version: 0.0.23-1 platforms: - linux/amd64 - linux/arm64 diff --git a/images/sandbox-agent/Dockerfile b/images/sandbox-agent/Dockerfile index 00aa5b6..19206b1 100644 --- a/images/sandbox-agent/Dockerfile +++ b/images/sandbox-agent/Dockerfile @@ -2,6 +2,10 @@ FROM ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebff # renovate: datasource=node-version depName=node ARG NODE_VERSION=24.18.0 +# Node ships an older bundled npm whose own vendored deps carry known CVEs; +# upgrading npm itself is the only way to move them. +# renovate: datasource=npm depName=npm +ARG NPM_VERSION=12.0.1 # renovate: datasource=github-releases depName=cli/cli ARG GH_VERSION=2.96.0 # renovate: datasource=github-releases depName=astral-sh/uv @@ -36,7 +40,10 @@ RUN apt-get update \ RUN NODE_ARCH="$(case "${TARGETARCH}" in amd64) echo x64 ;; *) echo "${TARGETARCH}" ;; esac)" \ && curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" \ | tar -xJ -C /usr/local --strip-components=1 --no-same-owner \ - && node --version + && npm install -g "npm@${NPM_VERSION}" \ + && npm cache clean --force \ + && node --version \ + && npm --version RUN curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${TARGETARCH}.tar.gz" \ | tar -xz -C /usr/local/bin --strip-components=2 --no-same-owner "gh_${GH_VERSION}_linux_${TARGETARCH}/bin/gh" \ diff --git a/images/sandbox-agent/metadata.yaml b/images/sandbox-agent/metadata.yaml index a002e58..04afb45 100644 --- a/images/sandbox-agent/metadata.yaml +++ b/images/sandbox-agent/metadata.yaml @@ -1,7 +1,7 @@ --- name: sandbox-agent description: Rootless base image for sandboxed coding agents -version: 1.0.0 +version: 1.0.1 platforms: - linux/amd64 - linux/arm64