Publish image from the script + Docker Scout hardening#7
Merged
Conversation
…workflow Replaces .github/workflows/publish.yml with a local `./security-scan.sh publish` subcommand. Rationale: the maintainer is already logged in to Docker Hub on the console (`docker login`), so storing repo secrets just to mirror that auth is needless complexity. ./security-scan.sh publish # multi-arch, push to Docker Hub ./security-scan.sh publish v0.3.0-rc1 # explicit tag (skips version check) ./security-scan.sh publish --no-push # build-only release dry-run ./security-scan.sh publish --single-arch # host arch only (skip buildx multi-arch) ./security-scan.sh publish --repo me/img # different docker hub repo By default the tag is derived from SECURITY-SCAN-MANIFEST.yaml; the script verifies pyproject.toml's version matches (same guard the old workflow did) before tagging. An explicit `vX.Y.Z` argument overrides without the check. Push happens via `docker buildx build --push` so credentials come from the host's existing `~/.docker/config.json` — never seen by the script. `--no-push` paired with multi-arch builds to the buildx cache only (no local load — buildx --load doesn't support multi-platform). The build itself runs end-to-end so it's still a real release dry-run. After the push, a smoke step runs `docker run --rm --entrypoint cat leverj/security-scan:vX.Y.Z /app/SECURITY-SCAN-MANIFEST.yaml | head -5` to prove the manifest landed in the pushed image. README updated to document the publish flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR moves the Docker image publishing flow from GitHub Actions to a new local ./security-scan.sh publish subcommand, and removes the old .github/workflows/publish.yml workflow. The goal is to rely on the developer’s existing local Docker credential store (via docker login) rather than mirroring auth into CI secrets.
Changes:
- Added
publishsubcommand tosecurity-scan.shto build (optionally multi-arch) and push to Docker Hub, with version alignment checks againstpyproject.tomlandSECURITY-SCAN-MANIFEST.yaml. - Documented the local publish flow in
README.md. - Deleted the GitHub Actions publish workflow.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| security-scan.sh | Adds local publish command, argument parsing, version resolution, buildx build/push, and a post-push smoke check. |
| README.md | Adds a “Publish a new image” section describing the local publish workflow and flags. |
| .github/workflows/publish.yml | Removes the previous CI-based Docker publish pipeline. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+239
to
+240
| echo "smoke test: extracting manifest from the pushed image..." | ||
| docker run --rm --entrypoint cat "$image_versioned" /app/SECURITY-SCAN-MANIFEST.yaml | head -5 |
Comment on lines
+125
to
+131
| # read_version_from <file> <regex> | ||
| # Pulls the first match of <regex> from <file>, returns just the captured version. | ||
| read_version_from() { | ||
| local file="$1" pattern="$2" | ||
| [[ -f "$file" ]] || die "missing file: $file" | ||
| grep -E "$pattern" "$file" | head -1 | sed -E 's/.*"([^"]+)".*/\1/' | ||
| } |
Comment on lines
+145
to
+146
| --repo) repo="$2"; shift 2 ;; | ||
| --repo=*) repo="${1#--repo=}"; shift ;; |
Comment on lines
+159
to
+162
| --no-push build + tag locally; do NOT push. Useful to dry-run a release. | ||
| --single-arch build only for the host architecture (skip multi-arch buildx). | ||
| Default: linux/amd64 + linux/arm64. | ||
| --repo override the image repo. Default: leverj/security-scan. |
Comment on lines
+241
to
+243
| else | ||
| echo "built locally (not pushed): $image_versioned + $image_latest" | ||
| fi |
| echo "publish: using explicit tag $tag (skipping version-alignment check)" | ||
| fi | ||
|
|
||
| # Sanity: the version-stripped tag should match what's in the manifest if no override. |
Comment on lines
+195
to
+198
| `leverj/security-scan:v<version>` + `:latest` for amd64 + arm64. Pass an | ||
| explicit tag to override (`./security-scan.sh publish v0.3.0-rc1`), or | ||
| `--no-push` to do a release dry-run that builds locally without pushing. | ||
| Run `./security-scan.sh publish --help` for the full set of flags. |
Addresses every warning in Docker Scout's 'F' health score for v0.2.0: 1. Missing supply chain attestation(s) security-scan.sh's `publish` now passes `--sbom=true` and `--provenance=mode=max` to `docker buildx build`. The first push of v0.2.1 will attach a CycloneDX SBOM and SLSA build provenance to the manifest list — the syft scanner runs cleanly in --no-push dry-run. 2. High-profile vulnerabilities found 3. Fixable critical or high vulnerabilities found Base image bumped from python:3.11-slim to python:3.12-slim. Added `apt-get upgrade -y` between update and install so the layer carries the latest OS-level security patches. setuptools pinned <80 because newer setuptools dropped the bundled `pkg_resources` module that semgrep 1.97's opentelemetry dep still imports at runtime. 4. No default non-root user found (also closes #1) Added a non-root `scanner` user (uid 1000) and a USER directive at the end of the Dockerfile. /work and the trivy DB cache (moved to /var/cache/trivy) are chowned so the unprivileged user can still write the clone, gitleaks tempfile, SBOM output, and read the pre-cached vuln DBs. Verified with `docker run --entrypoint id`: uid=1000(scanner) gid=1000(scanner) groups=1000(scanner) Entrypoint sanity-checked: `python -m security_scan --help` runs cleanly as the new user. Version + manifest bumped 0.2.0 -> 0.2.1. The manifest's changelog leads with the 0.2.1 hardening note so the consumer skill surfaces it to users on the upgrade prompt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related changes:
Publish from the script, not GH Actions. Replaces
.github/workflows/publish.ymlwith a local./security-scan.sh publishsubcommand that uses the maintainer's existingdocker logincredentials. No CI secrets needed.Harden the image for Docker Scout — closes all four warnings on v0.2.0's 'F' score:
--sbom=true --provenance=mode=maxadded to the buildx invocation.python:3.11-slim→python:3.12-slim;apt-get upgrade -yinserted between update and install; setuptools pinned <80 (newer setuptools droppedpkg_resourceswhich semgrep's opentelemetry dep still imports).scanneruser (uid 1000),USER scannerat the end of the Dockerfile,/workand the trivy DB cache (moved to/var/cache/trivy) chowned. Also closes Pin SHA-256 sums for scanner binaries downloaded in Dockerfile #1.Version bumped
0.2.0→0.2.1in pyproject + manifest +__init__.py. The manifest's changelog entry for 0.2.1 surfaces the hardening so the consumer skill shows it on the upgrade prompt.Publish usage
Test plan
./security-scan.sh publish --helpshows full usage../security-scan.sh publish --no-pushend-to-end: tag resolves tov0.2.1from manifest, multi-arch buildx succeeds, SBOM syft scanner runs, prints "built locally (not pushed)".docker build .succeeds locally on python:3.12-slim with the setuptools pin.docker run --rm --entrypoint id <image>→uid=1000(scanner) gid=1000(scanner) groups=1000(scanner).docker run --rm --entrypoint python <image> -m security_scan --helpruns cleanly as the unprivileged user./var/cache/trivy/{db,java-db}present../security-scan.sh publishfrom your shell to pushv0.2.1and let Docker Scout re-grade.Closes #1.
🤖 Generated with Claude Code