test: plant nested marker files in the layer-scan probe (E00-S02-T08)
CI / Frozen lockfile install (pull_request) Successful in 52s
CI / Secrets not embedded (E00-S02-T08) (pull_request) Successful in 38s

The Docker-gated probe only planted a root-level .env.t08-* marker, which no
Dockerfile COPY instruction ever copies — so it could not observe a nested
build-context leak in the image layers. Plant additional marker files at
nested paths the Dockerfile's COPY apps/server apps/server would sweep into
the build-stage image (apps/server/.env.t08-*, apps/server/secrets/t08-*.pem)
so the end-to-end scan actually verifies the 'any depth' exclusion
guarantee, not just the root form.
This commit is contained in:
implementer
2026-08-29 01:53:57 +00:00
parent f00c13d57a
commit 719fb4380b
+52 -17
View File
@@ -21,12 +21,15 @@
* root-anchored bare form, or adding a redundant equivalent pattern all
* break the criterion).
* - "image layers contain no secret values" → on machines with Docker, the
* real-image probe builds the committed image from the repo root with a
* marker-bearing probe env file (`.env.t08-*`, excluded by `.dockerignore`)
* present in the build context, then `docker save`s the image, extracts
* real-image probe builds the committed image from the repo root with
* marker-bearing probe files planted in the build context at the root
* (`.env.t08-*`) AND at nested paths the Dockerfile's
* `COPY apps/server apps/server` would sweep into the build-stage image
* (`apps/server/.env.t08-*`, `apps/server/secrets/t08-*.pem`) unless
* `.dockerignore` excludes them, then `docker save`s the image, extracts
* every layer (raw + decompressed) and the image config, and confirms
* neither the probe marker nor the compose default credential values
* appear anywhere in the layers. CI runs this file on every PR
* neither the markers nor the compose default credential values appear
* anywhere in the layers. CI runs this file on every PR
* (.gitea/workflows/ci.yml), so the static assertions gate merges.
*
* The dockerignore matcher below is a faithful port of Docker's real matcher,
@@ -601,18 +604,37 @@ test('the committed files copied into the image contain no default credential va
});
test('the built image layers contain no secret values (docker build + layer scan)', { skip: !DOCKER_DAEMON }, () => {
// Build the committed image from the repo root with a marker-bearing probe
// env file in the build context (`.env.t08-*` matches the `.env.*`
// exclusion), then scan every layer blob (raw + decompressed) and the image
// config for the marker and the compose default credential values.
const marker = `T08_PROBE_${randomUUID().replace(/-/g, '')}`;
const probeName = `.env.t08-${randomUUID().slice(0, 8)}`;
const probePath = path.join(REPO_ROOT, probeName);
// Build the committed image from the repo root with marker-bearing probe
// files planted in the build context at the root AND at nested paths the
// Dockerfile's `COPY apps/server apps/server` would sweep into the
// build-stage image if `.dockerignore` did not exclude them:
// - .env.t08-<uuid> (root; `**/.env.*`)
// - apps/server/.env.t08-<uuid> (nested; `**/.env.*`)
// - apps/server/secrets/t08-<uuid>.pem (nested; `**/secrets`, `**/*.pem`)
// then scan every layer blob (raw + decompressed) and the image config for
// the markers and the compose default credential values. The nested markers
// are the observable end-to-end check of the "any depth" guarantee: a leak
// at apps/server/… would land in the build-stage layers via the COPY.
const rootMarker = `T08_ROOT_SECRET_${randomUUID().replace(/-/g, '')}`;
const nestedEnvMarker = `T08_NESTED_ENV_SECRET_${randomUUID().replace(/-/g, '')}`;
const nestedPemMarker = `T08_NESTED_PEM_SECRET_${randomUUID().replace(/-/g, '')}`;
const rootProbeName = `.env.t08-${randomUUID().slice(0, 8)}`;
const nestedEnvProbeName = `.env.t08-${randomUUID().slice(0, 8)}`;
const nestedPemName = `t08-${randomUUID().slice(0, 8)}.pem`;
const tag = `personal-blog:t08-probe-${randomUUID().slice(0, 8)}`;
const tmp = mkdtempSync(path.join(os.tmpdir(), 't08-layer-scan-'));
writeFileSync(probePath, `T08_PROBE_SECRET=${marker}\nPOSTGRES_PASSWORD=${marker}\n`);
const rootProbePath = path.join(REPO_ROOT, rootProbeName);
const nestedEnvProbePath = path.join(REPO_ROOT, 'apps/server', nestedEnvProbeName);
const secretsDir = path.join(REPO_ROOT, 'apps/server/secrets');
const nestedPemPath = path.join(secretsDir, nestedPemName);
const hadSecretsDir = existsSync(secretsDir);
try {
writeFileSync(rootProbePath, `T08_PROBE_ROOT_SECRET=${rootMarker}\n`);
writeFileSync(nestedEnvProbePath, `T08_PROBE_NESTED_ENV_SECRET=${nestedEnvMarker}\n`);
mkdirSync(secretsDir, { recursive: true });
writeFileSync(nestedPemPath, `T08_PROBE_NESTED_PEM_SECRET=${nestedPemMarker}\n`);
const build = run('docker', ['build', '--file', 'apps/server/Dockerfile', '--tag', tag, '.'], {
cwd: REPO_ROOT,
});
@@ -638,19 +660,32 @@ test('the built image layers contain no secret values (docker build + layer scan
`"tar -xf" must exit 0:\n${(untar.stdout || '')}\n${(untar.stderr || '')}`.trim(),
);
const found = anyFileContains(extractDir, [marker, ...COMPOSE_CREDENTIAL_VALUES]);
const found = anyFileContains(extractDir, [rootMarker, nestedEnvMarker, nestedPemMarker, ...COMPOSE_CREDENTIAL_VALUES]);
assert.deepEqual(
found,
[],
`the image layers must contain no secret values; found in the image: ${found.join(', ')} ` +
`(scan of every layer + image config of "${tag}"; see \`docker history ${tag}\` for the layer list)`,
`(scan of every layer + image config of "${tag}"; probe files planted at the root (${rootProbeName}) ` +
`and at nested paths (apps/server/${nestedEnvProbeName}, apps/server/secrets/${nestedPemName}) — ` +
`see \`docker history ${tag}\` for the layer list)`,
);
} finally {
try {
unlinkSync(probePath);
unlinkSync(rootProbePath);
} catch {
// probe env file already gone
// probe file already gone
}
try {
unlinkSync(nestedEnvProbePath);
} catch {
// probe file already gone
}
try {
unlinkSync(nestedPemPath);
} catch {
// probe file already gone
}
if (!hadSecretsDir) rmSync(secretsDir, { recursive: true, force: true });
rmSync(tmp, { recursive: true, force: true });
run('docker', ['image', 'rm', '-f', tag]);
}