From 5e0a92afcdbbaf7b590015cf5f6cdb17a10c2f25 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:47:55 -0600 Subject: [PATCH] Attach an SBOM to the Docker image, retag stable, and document the image Release images now carry an SPDX SBOM attestation next to the provenance attestation build-push-action already adds. Marking a release stable retags the image that release published instead of rebuilding it. The rebuild paired an older wheel with the default branch's Dockerfile, and gave users who had already pulled the release new layers to download. imagetools create carries the attestation manifests across. docs/ci-cd.md gains a Docker image section: what the image contains, how to persist the reachability engine cache between container runs, keeping pulls cheap in CI, and how to inspect the attestations. --- .github/workflows/docker-stable.yml | 41 ++++++++-------- .github/workflows/release.yml | 3 ++ CHANGELOG.md | 19 ++++++++ docs/ci-cd.md | 74 +++++++++++++++++++++++++++++ pyproject.toml | 2 +- socketsecurity/__init__.py | 2 +- uv.lock | 2 +- 7 files changed, 118 insertions(+), 25 deletions(-) diff --git a/.github/workflows/docker-stable.yml b/.github/workflows/docker-stable.yml index de1fe16..7f85d03 100644 --- a/.github/workflows/docker-stable.yml +++ b/.github/workflows/docker-stable.yml @@ -17,31 +17,28 @@ jobs: with: persist-credentials: false - - name: Check if version exists in PyPI - id: version_check - env: - INPUT_VERSION: ${{ inputs.version }} - run: | - if ! curl -s -f "https://pypi.org/pypi/socketsecurity/${INPUT_VERSION}/json" > /dev/null; then - echo "Error: Version ${INPUT_VERSION} not found on PyPI" - exit 1 - fi - echo "Version ${INPUT_VERSION} found on PyPI - proceeding with release" - - name: Set up Docker publishing uses: $/.github/actions/setup-docker with: + # Retagging copies manifests between tags; nothing is built. + enable-qemu: "false" dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build & Push Stable Docker - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 - with: - push: true - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max - tags: socketdev/cli:stable - build-args: | - CLI_VERSION=${{ inputs.version }} - + # Point stable at the image the release already published instead of + # rebuilding it. A rebuild would pair that release's wheel with today's + # Dockerfile, and would give users who already pulled the release new + # layers to download. The retag keeps the release's attestations. + - name: Tag the release image as stable + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + if ! printf '%s' "$INPUT_VERSION" | grep -Eq '^[0-9]+(\.[0-9A-Za-z-]+)+$'; then + echo "Error: '${INPUT_VERSION}' is not a release version such as 1.2.3" + exit 1 + fi + if ! docker buildx imagetools inspect "socketdev/cli:${INPUT_VERSION}" > /dev/null; then + echo "Error: socketdev/cli:${INPUT_VERSION} is not on Docker Hub" + exit 1 + fi + docker buildx imagetools create --tag socketdev/cli:stable "socketdev/cli:${INPUT_VERSION}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef33417..354ac5f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,6 +119,9 @@ jobs: platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max + # Attach an SPDX SBOM generated from the built image, next to the + # provenance attestation the action already adds. + sbom: true tags: | socketdev/cli:latest socketdev/cli:${{ env.VERSION }} diff --git a/CHANGELOG.md b/CHANGELOG.md index bfe78cc..38b11ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 2.11.12 + +### Added: SBOM attestation on the Docker image + +- Release images on Docker Hub now carry an SPDX SBOM attestation alongside the + existing provenance attestation. See + [Verify the image](docs/ci-cd.md#verify-the-image). + +### Changed: `stable` points at the published release image + +- Marking a release stable now retags the image that release published instead of + rebuilding it, so `socketdev/cli:stable` is the same image as + `socketdev/cli:`, attestations included. + +### Added: Docker image guide + +- New [Docker image](docs/ci-cd.md#docker-image) section covering how to cache the + reachability engine between container runs and keep image pulls cheap in CI. + ## 2.11.11 ### Changed: use lowercase Python CLI User-Agent diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 253c27c..d0863aa 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -522,6 +522,80 @@ Dashboard metadata unless `--integration` was explicitly supplied. PR comments remain limited to the existing GitHub and GitLab SCM adapters; Azure receives console output and Dashboard association but does not post a PR comment. +## Docker image + +`socketdev/cli` on Docker Hub packages the CLI with the toolchains reachability +analysis runs on: Node.js, Go, Java, .NET, Rust, Ruby, pyenv, and PyPy on +`linux/amd64`. The toolchains are most of the image's size. Tags are published +per release (`socketdev/cli:`), plus `latest` and `stable`. + +```sh +docker run --rm \ + -e SOCKET_SECURITY_API_TOKEN \ + -v "$PWD:/src" \ + socketdev/cli: --target-path /src --reach +``` + +### Cache the reachability engine + +With `--reach`, the CLI downloads its pinned reachability engine on first use and +caches it under `$XDG_CACHE_HOME/socket/coana` (`/root/.cache/socket/coana` in the +image). A new container starts with an empty cache, so without a persistent +cache every run downloads the engine again. Mount a volume to keep it: + +```sh +docker run --rm \ + -e SOCKET_SECURITY_API_TOKEN \ + -v "$PWD:/src" \ + -v socket-cache:/root/.cache/socket \ + socketdev/cli: --target-path /src --reach +``` + +On CI runners that start every job on a fresh machine, mount a directory your CI +provider caches instead, and key that cache on the CLI version, which determines +the engine version. In GitHub Actions: + +```yaml +- uses: actions/cache@v4 + with: + path: ${{ runner.temp }}/socket-cache + key: socket-cli- +- run: | + docker run --rm \ + -e SOCKET_SECURITY_API_TOKEN \ + -v "$PWD:/src" \ + -v "${{ runner.temp }}/socket-cache:/root/.cache/socket" \ + socketdev/cli: --target-path /src --reach + env: + SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }} +``` + +Keep the cache outside the directory being scanned. A cache restored for a +different version is harmless: the CLI downloads the version it needs. + +### Keep image pulls cheap + +- Pin a version tag, or a digest, rather than `latest`, so runners that already + have the image reuse it. +- Mirror the image to a registry close to your runners (GHCR, ECR, Artifact + Registry, Artifactory). This also avoids Docker Hub's pull rate limits. +- Ephemeral hosted runners pull the whole image on every job. Long-lived or + self-hosted runners pull it once per host. + +### Verify the image + +Each release image carries a provenance attestation and an SPDX SBOM: + +```sh +docker buildx imagetools inspect socketdev/cli: --format '{{ json .Provenance }}' +docker buildx imagetools inspect socketdev/cli: --format '{{ json .SBOM }}' +``` + +The SBOM is generated from the built image, so it covers the Alpine packages and +the Python and npm packages installed in it. Toolchains installed from release +archives rather than a package manager, such as Rust (via rustup) and PyPy, are +listed thinly or not at all. + ## Workflow templates Prebuilt examples in this repo: diff --git a/pyproject.toml b/pyproject.toml index 966fee1..183008b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.11.11" +version = "2.11.12" requires-python = ">= 3.11" license = {"file" = "LICENSE"} dependencies = [ diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index 4a07834..590de20 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,4 +1,4 @@ __author__ = 'socket.dev' -__version__ = '2.11.11' +__version__ = '2.11.12' USER_AGENT_NAME = 'socket-python-cli' USER_AGENT = f'{USER_AGENT_NAME}/{__version__}' diff --git a/uv.lock b/uv.lock index 0a14aad..f9e7a03 100644 --- a/uv.lock +++ b/uv.lock @@ -1332,7 +1332,7 @@ wheels = [ [[package]] name = "socketsecurity" -version = "2.11.11" +version = "2.11.12" source = { editable = "." } dependencies = [ { name = "beautifulsoup4" },