01. Observed Symptoms & Why Standard Package Audits Fail

Observed (context): The PHP project announced its fixes on 2026-10-21 (https://www.php.net/archive/2026.php#2026-10-21-5), and Node.js published its security releases at https://nodejs.org/en/blog/vulnerability/. Images built from FROM php:8.3-fpm or FROM node:22-alpine before the base tag was republished, or built later from a cached layer, can still contain the old interpreter. Observed (Docker behaviour): a tag is a mutable pointer, and BuildKit reuses cached layers unless told to pull.

Symptoms: (1) The Dockerfile has not changed since September, and every deploy since has 'succeeded'. (2) Cloud Run, Kubernetes or Lambda reports a healthy revision but nobody can state the base image digest. (3) A scanner reports 'no vulnerabilities' on a freshly built image, because the scanner's database or the image's package metadata does not cover the interpreter binary. (4) Different environments run different patch levels of the same tag.

Why package audits fail: composer audit and npm audit inspect application dependencies. They never open the base image. An image scanner only helps if its database includes these CVEs and it identifies the interpreter correctly. Inference: neither is guaranteed for a CVE announced days ago. The deterministic control is to execute the interpreter inside the image and compare its version to the fixed release.

02. Root-Cause Forensic Analysis (Runtime & Protocol Level)

Root cause: version drift between what the Dockerfile says and what the registry, the build cache and the deployed revision hold. Three places hide the old binary: the CI cache (cache-from restoring pre-fix layers), the registry tag (pushed once and never rebuilt), and the platform revision (Cloud Run revision, Kubernetes pod or Lambda function that has not been redeployed since).

The commands below answer 'what is really running' on each platform. Use image digests, not tags, as the unit of evidence. The final two commands execute the interpreter inside the image, which is the only check that does not depend on a vulnerability database.

What is actually deployed, by platform BASH
# Cloud Run: image (ideally with @sha256 digest) behind the live revision
gcloud run services describe SERVICE --region REGION --format='value(spec.template.spec.containers[0].image)'

# Kubernetes: resolved image digest of every running container
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,IMAGEID:.status.containerStatuses[*].imageID'

# AWS Lambda (container image): digest-resolved URI
aws lambda get-function --function-name FUNCTION --query 'Code.ResolvedImageUri'

# Execute the interpreter inside the image: no scanner database required
docker run --rm --entrypoint php IMAGE@sha256:DIGEST -v
docker run --rm --entrypoint node IMAGE@sha256:DIGEST -v
1:1 EMBEDDED SOLUTION PIPELINE · HL-R5-DOCKERBASE

5-Register Quarantine & Attestation Engine

AWAITING INSPECTION
REGISTER 1 In-Browser AST & Lockfile Inspector
$0 · ALWAYS FREE

Evaluated 100% locally in your browser inside a zero-exfiltration sandbox (connect-src 'none'). Your credentials and proprietary code never touch our servers.

REGISTER 2 Non-Breaking Vaccine & Config Diff
10 cr ($1.00)

Generates targeted unified .patch diffs and perimeter WAF rules. Non-breaking compatibility is mathematically verified against the complete dependency graph.

✓ NON-BREAKING VERIFIED: Compatible across 14 downstream dependencies (0 semver conflicts)
TARGET FILES: CRYPTOGRAPHICALLY VERIFIED
Dockerfile base image pin & multi-stage Alpine digest
Deterministic base image SHA-256 pinning & package cache purge
REGISTER 3 Signed CycloneDX 1.6 SBOM & Attestation PDF
20 cr ($2.00)

Official cryptographic audit certificate with DSSE Ed25519 digital signature, 7-day rolling attestation window, and compliance mapping for NIST CSF 2.0 and SOC2.

ATTESTATION SPEC: No Findings in Scope
Auditor Authority: 45x Google Cloud & DeepMind Skill Badges
Attestation Window: Valid for 7 days (next variant sweep required in 7 days)
Signature Spec: in-toto Statement v1 / DSSE Ed25519 envelope (hadayalab-attest-2026-ed25519-k1)
Inspect Sample Ed25519 Signed SBOM (/sbom/PAIN-2026-005.cdx.json) →
REGISTER 4 Side-Effect & Rollback Manifest
10 cr ($1.00)

Instant crash and compatibility analyzer. Computes peerDependency, PHP extension, and engine compatibility changes with a 1-command rollback manifest.

ROLLBACK INTEGRITY SPEC:
Atomic git-level rollback manifest with state-reversal guarantees for PAIN-2026-005.
REGISTER 5 Continuous CI/CD GitHub Actions OIDC Sweeper
1 cr / run ($0.10)

Automated weekly variant sweep via GitHub Actions OIDC (id-token: write). Auto-renews 7-day rolling attestation tokens without long-lived secrets.

AUTOMATION TARGET:
.github/workflows/hadayalab-attest.yml
Zero long-lived API secrets · OIDC Short-Lived JWT Verification

03. Immediate Zero-Downtime Triage (Non-Breaking Mitigation)

Step 1: rebuild every image with --pull and --no-cache once, to purge pre-fix cached layers. Step 2: resolve the current digest of each base tag and pin it. Step 3: assert the interpreter version in the build, so a stale base fails the build instead of shipping. Step 4: deploy by digest. Cloud Run and Kubernetes roll revisions without downtime when health checks pass.

Version floors: the PHP fixed release must be resolved from the PHP advisory commit (see PAIN-2026-001); the Node floors are v22.23.4, v24.22.1 and v26.12.1. Do not hard-code a PHP version from memory.

Inference (Distroless): distroless Node images shrink the OS attack surface but do not change the Node binary's patch level. A distroless tag is also a mutable pointer, so you still need a digest pin and a version assertion.

Digest-pinned base with a build-time version assertion (Node example) DOCKERFILE
# resolve first:  crane digest node:22-alpine
FROM node:22-alpine@sha256:REPLACE_WITH_CURRENT_DIGEST AS base
RUN node -e "const v=process.versions.node.split('.').map(Number); if (v[0]!==22 || v[1]<23 || (v[1]===23 && v[2]<4)) { console.error('Node below v22.23.4'); process.exit(1); }"
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "server.js"]
Purge stale layers, then deploy by digest BASH
docker build --pull --no-cache -t REGION-docker.pkg.dev/PROJECT/REPO/app:build .
docker push REGION-docker.pkg.dev/PROJECT/REPO/app:build
DIGEST=$(crane digest REGION-docker.pkg.dev/PROJECT/REPO/app:build)
gcloud run deploy SERVICE --region REGION --image REGION-docker.pkg.dev/PROJECT/REPO/app@${DIGEST}

04. Permanent Remediation & Cryptographic SBOM Attestation

Tooling correction (Observed): SBOM generation is done by anchore/syft. anchore/grype consumes an SBOM or image and reports vulnerabilities. The pipeline is: syft emits a CycloneDX 1.6 SBOM, grype scans it, and the signed SBOM becomes an attestation on the image digest.

Pipeline: (1) build and push; (2) syft generates the CycloneDX 1.6 SBOM from the digest; (3) grype scans the SBOM and fails on high severity; (4) cosign attaches the SBOM as a signed attestation; (5) a Binary Authorization attestation is created for the digest; (6) the Cloud Run service or GKE cluster enforces that policy so unattested digests cannot deploy. Inference: because scanner coverage of a days-old CVE is not guaranteed, keep the in-image version assertion from Section 03 as a separate, database-independent gate.

Credential reference (HadayaLab badge ledger): GSP521 / Badge #1164 covers Binary Authorization attestation practice.

Primary sources: https://www.php.net/archive/2026.php#2026-10-21-5 | https://nodejs.org/en/blog/vulnerability/

HadayaLab operates with 45x Google Cloud & DeepMind Skill Badges (https://www.skills.google/public_profiles/49cdbdb6-20d4-4661-934a-d882c7f5c6cf). We audit Docker, Kubernetes and Cloud Run estates and wire the attestation gate into your CI.

SBOM (CycloneDX 1.6), scan, attest, enforce BASH
IMG=REGION-docker.pkg.dev/PROJECT/REPO/app@${DIGEST}

syft "$IMG" -o cyclonedx-json@1.6=sbom.cdx.json
grype sbom:./sbom.cdx.json --fail-on high

cosign attest --yes --type cyclonedx --predicate sbom.cdx.json "$IMG"

gcloud container binauthz attestations sign-and-create --artifact-url="$IMG" --attestor=ATTESTOR --attestor-project=PROJECT --keyversion-project=PROJECT --keyversion-location=global --keyversion-keyring=KEYRING --keyversion-key=KEY --keyversion=1

gcloud run services update SERVICE --region REGION --binary-authorization=default

Frequently Asked Questions

How do I check whether my Docker image still contains a vulnerable PHP or Node binary?

Run the interpreter inside the exact image digest: docker run --rm --entrypoint php IMAGE@sha256:DIGEST -v, or the same with node. Compare the result with the fixed releases (Node v22.23.4+, v24.22.1+, v26.12.1+; PHP from the commit lookup in PAIN-2026-001). This does not depend on any scanner database.

Why is FROM php:8.3-fpm or FROM node:22-alpine risky in CI/CD?

Tags are mutable pointers and BuildKit reuses cached layers. A pipeline that cached the base before 2026-10-21 can keep rebuilding the old interpreter indefinitely. Pin by SHA-256 digest, build with --pull, and assert the interpreter version at build time.

Which tool generates a CycloneDX 1.6 SBOM: Syft or Grype?

Syft generates it, for example syft IMAGE -o cyclonedx-json@1.6=sbom.cdx.json. Grype scans an SBOM or image for vulnerabilities, for example grype sbom:./sbom.cdx.json --fail-on high. They are from the same vendor and are normally run in sequence.

How do I stop unverified images from deploying to Cloud Run?

Create a Binary Authorization attestor and policy, sign each image digest after your build, scan and version checks pass, and enable enforcement on the service with gcloud run services update --binary-authorization=default. A digest without a valid attestation is then rejected at deploy time.