mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 21:31:34 +00:00
Bundle A: Container & supply-chain hardening — 3 findings closed; All High closed
Closes H-001 + M-012 + M-014 from comprehensive-audit-2026-04-25.
H-001 (CWE-829) — Container base images SHA-pinned
Pre-bundle: 5 FROM lines pulled by tag only — registry-side tag
swap could silently change the build.
Post-bundle: every FROM pinned to immutable digest fetched live
from Docker Hub at audit time:
node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293
golang:1.25-alpine@sha256:5caaf1cca9dc351e13deafbc3879fd4754801acba8653fa9540cea125d01a71f (x2)
alpine:3.19@sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1 (x2)
Dockerfile header comment documents the operator bump procedure
(quarterly cadence; docker manifest inspect or Hub Registry API).
CI step Forbidden bare FROM regression guard (H-001) fails build
if any new FROM lacks @sha256.
M-012 (CWE-250) — Verified-already-clean + USER guard
Recon found both Dockerfile:75 and Dockerfile.agent:59 already
carry USER certctl directives; pre-USER RUN calls are build-setup
steps that legitimately need root, each happening before the
USER drop.
CI step Forbidden missing USER regression guard (M-012) greps
every Dockerfile* for the LAST USER directive; fails build if
missing OR equals root/0. Future Dockerfile additions must
preserve the privilege drop.
M-014 — npm ci explicit retry helper
Pre-bundle Dockerfile:25:
RUN npm ci --include=dev || npm ci --include=dev && \
tsc --version && npm run build
Broken bash precedence: A || (B && C && D) means tsc+build only
ran on success path of the second npm ci. A transient registry
blip silently skipped the production step — build would succeed
with no node_modules + no tsc verification.
Post-bundle: deterministic 3-attempt retry loop with 5s backoff
plus explicit [ -d node_modules ] post-check that fails loudly
if directory wasn't created. Silent failure is now impossible.
Audit deliverables:
audit-report.md: H-001/M-012/M-014 flipped [x] with closure
notes; score 49/55 closed (High 9/9 = 100%; Medium 24/27;
Low 19/19 with L-004 deferred). All High audit findings now
closed for the first time.
findings.yaml: 3 status flips
CHANGELOG.md: Bundle A section
Verification:
Self-test of both new CI guards locally — PASS for current state
(every FROM has @sha256; every Dockerfile drops to non-root).
This commit is contained in:
@@ -166,6 +166,54 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Forbidden bare FROM regression guard (H-001)
|
||||
# Bundle A / Audit H-001 (CWE-829): every FROM line in every
|
||||
# Dockerfile in the repo MUST carry an @sha256:... digest pin in
|
||||
# addition to the human-readable tag. A registry-side tag swap
|
||||
# cannot then change what we pull. This step grep-fails the
|
||||
# build if any new FROM lands without the @sha256 suffix.
|
||||
run: |
|
||||
set -e
|
||||
# Match any "FROM image[:tag]" that does NOT contain @sha256.
|
||||
# Strip comments and blank lines defensively.
|
||||
BAD=$(find . -name 'Dockerfile*' -not -path './web/node_modules/*' \
|
||||
-exec grep -HnE '^FROM\s+[^@#]+(\s+AS\s+\S+)?\s*$' {} \; || true)
|
||||
if [ -n "$BAD" ]; then
|
||||
echo "::error::Dockerfile has bare FROM (no @sha256 digest pin):"
|
||||
echo "$BAD"
|
||||
echo ""
|
||||
echo "Pin every FROM to an immutable digest. See the bump"
|
||||
echo "procedure in Dockerfile's header comment (Bundle A / H-001)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Forbidden missing USER regression guard (M-012)
|
||||
# Bundle A / Audit M-012 (CWE-250): every Dockerfile in the repo
|
||||
# MUST end with a `USER <non-root>` directive before the
|
||||
# ENTRYPOINT/CMD so the container never runs as uid=0. This step
|
||||
# grep-fails the build if any Dockerfile is missing such a USER.
|
||||
# `USER root` and `USER 0` are explicitly rejected.
|
||||
run: |
|
||||
set -e
|
||||
BAD=""
|
||||
for df in $(find . -name 'Dockerfile*' -not -path './web/node_modules/*'); do
|
||||
# Find the LAST USER directive in the file.
|
||||
last_user=$(grep -E '^USER\s+\S+' "$df" | tail -1 | awk '{print $2}')
|
||||
if [ -z "$last_user" ]; then
|
||||
BAD="$BAD\n$df: no USER directive at all"
|
||||
continue
|
||||
fi
|
||||
if [ "$last_user" = "root" ] || [ "$last_user" = "0" ]; then
|
||||
BAD="$BAD\n$df: terminal USER is $last_user (must drop privileges)"
|
||||
continue
|
||||
fi
|
||||
done
|
||||
if [ -n "$BAD" ]; then
|
||||
echo "::error::Dockerfile USER-drop regression:"
|
||||
echo -e "$BAD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Forbidden README JWT advertising regression guard (H-009)
|
||||
# H-009 closed by Bundle D as verified-already-clean: at audit time
|
||||
# the README does NOT advertise JWT support (certctl does not ship
|
||||
|
||||
Reference in New Issue
Block a user