fix(docker): pin base images to SHA256 digests (#7734)

* fix(docker): pin base images to SHA256 digests for supply chain security

Pin all 9 Dockerfiles to immutable SHA256 digests to prevent supply chain
attacks where a compromised upstream image could be silently pulled into
production builds.

Also add Docker ecosystem to Dependabot configuration for automated
digest updates.

Images pinned:
- node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
- node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
- debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
- ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b

Fixes #7731

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(docker): add digest pinning regression coverage

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Coy Geek
2026-02-19 12:42:07 -08:00
committed by GitHub
parent e98ccc8e17
commit 8ae2d5110f
11 changed files with 83 additions and 9 deletions

View File

@@ -111,3 +111,16 @@ updates:
- minor
- patch
open-pull-requests-limit: 5
# Docker base images
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
docker-images:
patterns:
- "*"
open-pull-requests-limit: 5

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
# Install Bun (required for build scripts)
RUN curl -fsSL https://bun.sh/install | bash

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
ENV DEBIAN_FRONTEND=noninteractive

View File

@@ -1,4 +1,4 @@
FROM debian:bookworm-slim
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
ENV DEBIAN_FRONTEND=noninteractive

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm-slim
FROM node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
RUN apt-get update \
&& apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm-slim
FROM node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
RUN apt-get update \
&& apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM ubuntu:24.04
FROM ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
RUN set -eux; \
for attempt in 1 2 3; do \

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm-slim
FROM node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
RUN set -eux; \
for attempt in 1 2 3; do \

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
RUN corepack enable

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
RUN corepack enable

View File

@@ -0,0 +1,61 @@
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { parse } from "yaml";
const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
const DIGEST_PINNED_DOCKERFILES = [
"Dockerfile",
"Dockerfile.sandbox",
"Dockerfile.sandbox-browser",
"scripts/docker/cleanup-smoke/Dockerfile",
"scripts/docker/install-sh-e2e/Dockerfile",
"scripts/docker/install-sh-nonroot/Dockerfile",
"scripts/docker/install-sh-smoke/Dockerfile",
"scripts/e2e/Dockerfile",
"scripts/e2e/Dockerfile.qr-import",
] as const;
type DependabotDockerGroup = {
patterns?: string[];
};
type DependabotUpdate = {
"package-ecosystem"?: string;
directory?: string;
schedule?: { interval?: string };
groups?: Record<string, DependabotDockerGroup>;
};
type DependabotConfig = {
updates?: DependabotUpdate[];
};
describe("docker base image pinning", () => {
it("pins selected Dockerfile FROM lines to immutable sha256 digests", async () => {
for (const dockerfilePath of DIGEST_PINNED_DOCKERFILES) {
const dockerfile = await readFile(resolve(repoRoot, dockerfilePath), "utf8");
const fromLine = dockerfile
.split(/\r?\n/)
.find((line) => line.trimStart().startsWith("FROM "));
expect(fromLine, `${dockerfilePath} should define a FROM line`).toBeDefined();
expect(fromLine, `${dockerfilePath} FROM must be digest-pinned`).toMatch(
/^FROM\s+\S+@sha256:[a-f0-9]{64}$/,
);
}
});
it("keeps Dependabot Docker updates enabled for root Dockerfiles", async () => {
const raw = await readFile(resolve(repoRoot, ".github/dependabot.yml"), "utf8");
const config = parse(raw) as DependabotConfig;
const dockerUpdate = config.updates?.find(
(update) => update["package-ecosystem"] === "docker" && update.directory === "/",
);
expect(dockerUpdate).toBeDefined();
expect(dockerUpdate?.schedule?.interval).toBe("weekly");
expect(dockerUpdate?.groups?.["docker-images"]?.patterns).toContain("*");
});
});