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 2465ed7..b2dde4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] -### Changed +### Upgrade notes +- **Socket Tier 1 needs outbound access to `api.socket.dev` and + `storage.googleapis.com`.** Socket npm CLI 1.9.0 and later download the + Coana reachability engine from Socket instead of npm, with no npm fallback. + An npm mirror no longer covers it, `SOCKET_CLI_COANA_LAUNCHER` has no + effect, and a `--reach-version` older than the move fails. The heavy image's + Python CLI made the same change in 2.11.1, which shipped in 3.4.1. See + [Network access for Tier 1](docs/parameters.md#network-access-for-tier-1). +### Changed +- The images set `NODE_USE_ENV_PROXY=1`, so Socket Tier 1 honors `HTTPS_PROXY` + and `NO_PROXY`. The Socket npm CLI does not apply `HTTPS_PROXY` to its own + requests, and Tier 1 behind a proxy failed with `getaddrinfo EAI_AGAIN + api.socket.dev`. Node only acts on it when a proxy variable is set. Native + installs need it set by hand. - **Behavior change:** `socket_additional_params` now uses shell-style, space-separated arguments and preserves commas within argument values. A comma acts as a legacy separator only when it is unquoted and followed by a @@ -17,6 +30,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). separators (for example, `--view,full`) must use a space or `=` instead. Repeat list-valued flags, such as `--exclude-paths=tests --exclude-paths=fixtures`. +- Bundled tools: OpenGrep v1.26.0 -> v1.33.1, TruffleHog 3.96.0 -> 3.99.8, + Socket npm CLI 1.1.165 -> 1.14.0, Socket Python CLI 2.11.3 -> 2.11.11, uv + 0.12.1 -> 0.13.7; in the app-tests image, gosec v2.28.0 -> v2.30.0 and Go + 1.26.5 -> 1.27.3. Java SAST benchmark results are unchanged on OpenGrep 1.33.1. +- Socket Python SDK (`socketdev`) 3.6.1 -> 3.6.2, which Socket Python CLI + 2.11.11 pins exactly. Source installs now require `socketdev>=3.6.2`. +- TruffleHog no longer reports AppOptics or Bing Subscription Key credentials; + upstream retired both detectors in 3.97.0. +- Base and tool images are pinned as literal `FROM` tags so Dependabot can + update them. `--build-arg TRUFFLEHOG_VERSION` no longer has any effect. + +### Fixed +- TruffleHog 3.99.8 is built on Go 1.27.3, clearing the CVE-2026-111943 + finding noted in 3.4.1. ## [3.4.1] - 2026-10-21 diff --git a/Dockerfile b/Dockerfile index 1ea1a4d..99dd236 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,45 @@ -# ─── 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.33.1 . +ARG OPENGREP_VERSION=v1.33.1 +ARG SOCKET_NPM_CLI_VERSION=1.14.0 # -# 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.76.1 . -# TRIVY_VERSION feeds the image label — keep it in sync with the TRIVY_IMAGE tag. -# -socket.N is a trivy-dist toolchain rebuild: same upstream source, newer Go. +# 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.76.1 . +# TRIVY_VERSION feeds the image label — keep it in sync with the tag. +# -socket.N is a trivy-dist toolchain rebuild: same upstream source, newer Go. ARG TRIVY_VERSION=0.76.1 ARG TRIVY_IMAGE=ghcr.io/socketdev/trivy:0.76.1@sha256:883da3593a60e311b2ed87c3a55241051b27bba15ffc3ce041ae570cd0f00915 # ─── 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.99.8 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.13.7 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 \ @@ -45,7 +49,7 @@ RUN curl -fsSL https://raw.githubusercontent.com/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 @@ -76,6 +80,10 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ ARG SOCKET_NPM_CLI_VERSION RUN --mount=type=cache,target=/root/.npm \ npm install -g "socket@${SOCKET_NPM_CLI_VERSION}" +# The Socket CLI does not send its requests through HTTPS_PROXY on its own. +# This makes Node apply HTTPS_PROXY, HTTP_PROXY and NO_PROXY, and does nothing +# when none of them is set. +ENV NODE_USE_ENV_PROXY=1 # Python project files COPY socket_basics /socket-basics/socket_basics @@ -92,8 +100,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.99.8 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 e09b868..558dca6 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.11.3 +# +# 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.33.1 +ARG SOCKET_NPM_CLI_VERSION=1.14.0 +ARG SOCKET_PYTHON_CLI_VERSION=2.11.11 # Socket-built Trivy, pinned by digest — see the note in ./Dockerfile. ARG TRIVY_IMAGE=ghcr.io/socketdev/trivy:0.76.1@sha256:883da3593a60e311b2ed87c3a55241051b27bba15ffc3ce041ae570cd0f00915 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.99.8 AS trufflehog +FROM ghcr.io/astral-sh/uv:0.13.7 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.githubusercontent.com/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 @@ -49,6 +50,8 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ ARG SOCKET_NPM_CLI_VERSION RUN --mount=type=cache,target=/root/.npm \ npm install -g "socket@${SOCKET_NPM_CLI_VERSION}" +# Routes the Socket CLI through HTTPS_PROXY; see the note in ./Dockerfile. +ENV NODE_USE_ENV_PROXY=1 COPY socket_basics /socket-basics/socket_basics COPY pyproject.toml README.md LICENSE uv.lock /socket-basics/ @@ -68,8 +71,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.99.8 ARG SOCKET_NPM_CLI_VERSION ARG SOCKET_PYTHON_CLI_VERSION LABEL org.opencontainers.image.title="Socket Basics Heavy" \ diff --git a/README.md b/README.md index a182bd8..8a3cffa 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,11 @@ The API key is read from the environment (`SOCKET_SECURITY_API_KEY`; there is de **For GitHub Actions & Docker:** No local installation needed for the supported bundled scanners. +**Network access:** Socket Tier 1 downloads its reachability engine at run time +from `api.socket.dev` and `storage.googleapis.com`, not from npm, and installs +dependencies from each scanned ecosystem's registry. Allow these hosts on +restricted networks. See [Network access for Tier 1](docs/parameters.md#network-access-for-tier-1). + **For Local Installation:** - Python 3.10+ - [Socket CLI](https://docs.socket.dev/docs/cli) (for dependency analysis) @@ -273,6 +278,12 @@ Add new connectors by: **Workflow inputs have no effect, or Dockerfile and image scanning finds nothing:** - If the log shows `Loaded Socket Basics API configuration (overrides environment defaults)`, the dashboard configuration took precedence over your inputs, blank fields included (see [Dashboard Settings and Workflow Inputs](docs/github-action.md#dashboard-settings-and-workflow-inputs)) +**Socket Tier 1 fails, or reports `INSTALL ERROR` instead of reachability results:** +- `Failed to get the Coana CLI`: allow `api.socket.dev` and `storage.googleapis.com`; an npm mirror does not cover the engine download. `getaddrinfo EAI_AGAIN api.socket.dev` behind a proxy in a native install means `NODE_USE_ENV_PROXY=1` is missing next to `HTTPS_PROXY`; the images set it (see [Network access for Tier 1](docs/parameters.md#network-access-for-tier-1)) +- `INSTALL ERROR` on every vulnerability: allow the scanned ecosystem's package registry, or install dependencies in the workspace before scanning +- A Tier 1 failure does not change the scan's exit code; look for `Error running connector socket_tier1` in the log +- Remove `--reach-version` from `socket_additional_params` if it pins an engine version older than the Socket CLI supports + **Notifier errors:** - Check that notification credentials (Slack webhook, Jira token, etc.) are properly configured - Remember: Notifiers require Socket Enterprise diff --git a/app_tests/Dockerfile b/app_tests/Dockerfile index 1584c19..5b437e9 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.33.1 -f app_tests/Dockerfile app_tests +ARG GOSEC_VERSION=v2.30.0 +ARG OPENGREP_VERSION=v1.33.1 +ARG SOCKET_NPM_CLI_VERSION=1.14.0 +ARG SOCKET_PYTHON_CLI_VERSION=2.11.11 # -# 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.11.3 -# -# 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.76.1@sha256:883da3593a60e311b2ed87c3a55241051b27bba15ffc3ce041ae570cd0f00915 # ─── 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.99.8 AS trufflehog -# ─── Stage: golang (Dependabot-trackable) ───────────────────────────────────── -FROM golang:${GOLANG_VERSION} AS golang +# ─── Stage: golang ──────────────────────────────────────────────────────────── +FROM golang:1.27.3 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 ────────────────────────────────────────────────────────────── +# Node comes from a named stage, not the nodesource install script, so its +# version is a FROM tag that Dependabot and the pin checkers can read. +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.13.7 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.githubusercontent.com/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.githubusercontent.com/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. @@ -100,6 +97,8 @@ RUN --mount=type=cache,target=/root/.npm \ npm install -g eslint eslint-plugin-security \ @typescript-eslint/parser @typescript-eslint/eslint-plugin \ "socket@${SOCKET_NPM_CLI_VERSION}" +# Routes the Socket CLI through HTTPS_PROXY; see the note in the root ./Dockerfile. +ENV NODE_USE_ENV_PROXY=1 # Bandit + socketsecurity via uv ENV UV_LINK_MODE=copy diff --git a/docs/github-action.md b/docs/github-action.md index cfedadd..64f7bc5 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -286,6 +286,12 @@ path that does not exist is skipped with a `Dockerfile not found` warning. socket_tier_1_enabled: 'true' ``` +> **Tier 1 downloads its reachability engine at run time and requires network +> egress.** The runner must reach `api.socket.dev`, `storage.googleapis.com`, +> and the package registry of each ecosystem it scans (for example +> `registry.npmjs.org`). The engine comes from Socket, not npm, so an npm mirror +> does not cover it. See [Network access for Tier 1](parameters.md#network-access-for-tier-1). + ### Output Configuration ```yaml diff --git a/docs/java-sast-benchmark.md b/docs/java-sast-benchmark.md index da1470c..bb8fdde 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.33.1**, 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, 1.30.0 and 1.33.1 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.33.1. Every number below is unchanged from the +1.26.0 measurement; 1.30.0 and 1.33.1 produce the same findings on +BenchmarkJava, finding for finding. 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 c928b5f..8ffc93c 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.4.1 \ | jq '.[0].Config.Labels' # { # "com.socket.trivy-version": "0.76.1", -# "com.socket.trufflehog-version": "3.96.0", -# "com.socket.opengrep-version": "v1.26.0", +# "com.socket.trufflehog-version": "3.99.8", +# "com.socket.opengrep-version": "v1.33.1", # "org.opencontainers.image.version": "3.4.1", # ... # } @@ -97,6 +97,20 @@ docker run --rm -v "$PWD:/workspace" ghcr.io/socketdev/socket-basics:3.4.1-heavy `latest` and `latest-heavy` are floating aliases published for onboarding convenience. Pin an exact version (or digest) in anything automated. +> [!NOTE] +> With `--socket-tier1`, both images download the Coana reachability engine at +> run time from Socket (`api.socket.dev`, then `storage.googleapis.com`), not +> from npm. The container also needs each scanned ecosystem's package registry +> unless the workspace already has its dependencies installed. Mount a volume at +> `/root/.cache/socket/coana` to keep the engine (about 120 MB) between runs: +> +> ```bash +> docker run --rm -v "$PWD:/workspace" -v socket-coana-cache:/root/.cache/socket/coana \ +> ghcr.io/socketdev/socket-basics:3.4.1 --workspace /workspace --socket-tier1 +> ``` +> +> See [Network access for Tier 1](parameters.md#network-access-for-tier-1). + ### Pinning in CI/CD **GitHub Actions** — prefer the [action itself](github-action.md), which adds PR @@ -200,15 +214,21 @@ docker build --platform linux/amd64 -t socket-basics:3.4.1 . ### 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.33.1 \ + --build-arg SOCKET_NPM_CLI_VERSION=1.14.0 \ -t socket-basics:3.4.1 . ``` +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 03f2bb6..598a56e 100644 --- a/docs/local-installation.md +++ b/docs/local-installation.md @@ -165,6 +165,9 @@ to use, and prefer exact version pins whenever your package manager supports the **Required for:** Socket Tier 1 reachability analysis +**Requires:** Node.js 22.13 or later. On an older Node, the reachability engine +fails to start and Tier 1 fails. + **Installation:** ```bash @@ -175,6 +178,14 @@ npm install -g socket socket --version ``` +Socket CLI 1.9.0 and later download the reachability engine from Socket +(`api.socket.dev`, then `storage.googleapis.com`) instead of npm. The machine +running the scan needs outbound access to both hosts, even if npm itself goes +through a mirror. Behind a proxy, set `NODE_USE_ENV_PROXY=1` along with +`HTTPS_PROXY`, because the CLI does not apply `HTTPS_PROXY` to these requests on +its own. The engine is cached in `~/.cache/socket/coana`. See +[Network access for Tier 1](parameters.md#network-access-for-tier-1). + **Configuration:** ```bash @@ -278,16 +289,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.99.8 # 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.99.8/trufflehog_3.99.8_linux_amd64.tar.gz +tar -xzf trufflehog_3.99.8_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.99.8/trufflehog_3.99.8_darwin_arm64.tar.gz +tar -xzf trufflehog_3.99.8_darwin_arm64.tar.gz sudo mv trufflehog /usr/local/bin/ # Verify installation @@ -295,7 +306,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/docs/parameters.md b/docs/parameters.md index 414f81e..86b4794 100644 --- a/docs/parameters.md +++ b/docs/parameters.md @@ -715,6 +715,54 @@ Enable Socket Tier 1 reachability analysis for dependency scanning. socket-basics --socket-tier1 ``` +#### Network access for Tier 1 + +Tier 1 runs `socket scan reach`, which needs network access to three kinds of +host: + +| Host | Used for | If blocked | +|---|---|---| +| `api.socket.dev` | Socket API calls, and signed URLs for the engine download | Tier 1 fails | +| `storage.googleapis.com` | Downloading the Coana reachability engine and its analyzers (about 120 MB) | Tier 1 fails: `Failed to get the Coana CLI : getaddrinfo ENOTFOUND storage.googleapis.com` | +| The package registry of each scanned ecosystem, for example `registry.npmjs.org` | Installing the project's dependencies so the engine can analyze them | Tier 1 still reports success, but the affected vulnerabilities come back as `INSTALL ERROR` with no reachability verdict | + +Socket CLI 1.9.0 and later (Socket Basics 3.5.0 bundles 1.14.0) download the +engine from Socket rather than npm, with no npm fallback, so an npm mirror does +not cover the second row. The registry row does not apply when the workspace +already has its dependencies installed (for example a `node_modules` directory +from an earlier `npm ci` step). + +**Behind an HTTP(S) proxy**, set `HTTPS_PROXY` (and `NO_PROXY` for anything +that should bypass it). The Socket npm CLI that Tier 1 runs does not apply +`HTTPS_PROXY` to its own requests, so the images set `NODE_USE_ENV_PROXY=1`, +which makes Node route them through the proxy: + +```bash +docker run --rm -e HTTPS_PROXY=http://proxy.internal:3128 ... +``` + +Outside the images, set `NODE_USE_ENV_PROXY=1` yourself (Node 22.21 or later). +Without it, Tier 1 fails with `getaddrinfo EAI_AGAIN api.socket.dev`. The heavy +image's Python CLI (`socketcli`) honors `HTTPS_PROXY` without it. + +A Tier 1 failure is logged as `Error running connector socket_tier1` and does +not fail the overall scan, so check the log rather than the exit code. + +- The engine is cached in `$XDG_CACHE_HOME/socket/coana` + (`~/.cache/socket/coana` by default). A fresh container downloads it on every + run unless that directory is persisted. With a warm cache, Tier 1 does not + contact `storage.googleapis.com` at all. +- `SOCKET_CLI_COANA_LAUNCHER` has no effect. + `SOCKET_CLI_COANA_LOCAL_PATH` still runs a local copy of the engine instead. +- `--reach-version` in `socket_additional_params` can only select engine + versions published since the move. An older version fails with an API error + saying it needs an older Socket CLI. + +The Socket Python CLI (`socketcli`) in the heavy image made the same change +in 2.11.1, so the heavy image has fetched the engine from Socket since Socket +Basics 3.4.1 (`socketcli` 2.11.3). It needs the same hosts, and with the +engine host blocked `socketcli --reach` exits 3. + ### `--socket-additional-params SOCKET_ADDITIONAL_PARAMS` Additional CLI params for `socket scan reach`, parsed with shell-style quoting. diff --git a/pyproject.toml b/pyproject.toml index b763318..f0f5575 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "light-s3-client~=0.0.30", "PyYAML>=6.0.0", "tomli; python_version < '3.11'", - "socketdev>=3.5.0", + "socketdev>=3.6.2", "jsonschema>=4.25.1", # requests' CA bundle; 2026.10.19 drops the roots behind the compromised # eMudhra intermediates. Covers source installs (`pip install -e .`). diff --git a/scripts/check_core_tools.py b/scripts/check_core_tools.py index 74ccf5e..57f035d 100644 --- a/scripts/check_core_tools.py +++ b/scripts/check_core_tools.py @@ -243,6 +243,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. @@ -321,7 +344,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." + ) diff --git a/uv.lock b/uv.lock index ada5c55..157ba63 100644 --- a/uv.lock +++ b/uv.lock @@ -724,7 +724,7 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.1.0" }, { name = "pyyaml", specifier = ">=6.0.0" }, { name = "requests", specifier = ">=2.33.0" }, - { name = "socketdev", specifier = ">=3.5.0" }, + { name = "socketdev", specifier = ">=3.6.2" }, { name = "tabulate", specifier = "~=0.10.0" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] @@ -735,16 +735,16 @@ dev = [] [[package]] name = "socketdev" -version = "3.6.1" +version = "3.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/de/c62e2e76ae5f7f55901d1d1cefc5b141309342fd1ae19d44f350fd264efc/socketdev-3.6.1.tar.gz", hash = "sha256:4086d6842a51dd4fd91ecf6aa3f1e24e7c7200f110e50c866784bc09e1fc9986", size = 176415, upload-time = "2026-10-21T21:30:12.548Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/c2/48db5863e4e1d86f7b92334b91e8f9c59f4e96fc5949f0b594fff86dfeed/socketdev-3.6.2.tar.gz", hash = "sha256:ad51c879cad8d897ed1b949339fb7aded30d0fe25b2a3810c83079887e8610b5", size = 182918, upload-time = "2026-11-03T23:18:52.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/70/fe1e09c59dd566391aaf7bd0fb89ed9d1011e13f8fc4f4e8cc9376243888/socketdev-3.6.1-py3-none-any.whl", hash = "sha256:2297c7442c5debfa14ac464fd08b2755edbd71063b4cee3844bff173167f30e4", size = 73395, upload-time = "2026-10-21T21:30:10.718Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3a/7ed253ade4cd47e6af02bf61668c9c2824a35e617668b6f4f12e41d617a5/socketdev-3.6.2-py3-none-any.whl", hash = "sha256:ea94b2da1bdb89f3b70209b606f3abe5c609f66549c1deb160cf18f58e0c2e48", size = 75559, upload-time = "2026-11-03T23:18:50.423Z" }, ] [[package]]