fix(wake): #912 exercise the digest/HMAC trust suite in real CI
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Make the wake digest/HMAC suite RUN and PASS in the real Woodpecker CI
runner (Alpine/musl, root), then hard-require the HMAC legs in CI.
Root cause of the runner-only D1/D4/D5/D6 failures (a masked-local run
passed, so it was runner-specific): a TOOLCHAIN divergence, not locale or
root. digest.sh's _scrub_ctrl used GNU-sed `\xNN` hex-escape byte matching.
The CI runner is node:24-alpine, whose sed is BusyBox — BusyBox sed REJECTS
a `\xNN` character range ("bad regex ... Invalid character range"), aborting
the whole scrub sed and silently VOIDING the scrub. Every scrubbed value
collapsed to empty, cascading into D1 (blank locators), D4 (no scrub/redact,
SHA blanked), D5 (blank agent prefix), D6 (blank [digest] class). Confirmed
by reproducing the exact 9-assertion failure in the ci-base image as root.
Fix (at the correct layer — a wake digest must render identically on any
runner):
- digest.sh _scrub_ctrl: patterns are now LITERAL bytes (printf %b), matching
byte-identically under GNU sed (glibc dev) and BusyBox sed (Alpine CI).
Verified identical output on both. Contract preserved: two-tier trust,
exit-4 hard-locator FAIL-LOUD, secret-scrub, and 40-hex SHA preservation
all unchanged — deterministic, not weakened.
- test-wake-digest-hmac.sh D4: replaced PCRE `grep -qP` (BusyBox grep has no
-P; the `&&` silently skipped the check in CI) with portable literal-byte
`grep -E` ranges (two disjoint bidi/zero-width ranges, excluding legit
U+2014 em-dash).
CI enablement:
- Dockerfile.ci + .woodpecker/ci.yml test step: add openssl (the non-circular
HMAC signer) so H1/H2, beacon B12, install I8 can run. The apk add in the
test step covers PR pipelines before ci-base rebuilds.
- Flip the 3 openssl skip-guards (digest whole-file, beacon B12, install I8)
to HARD-REQUIRE openssl when CI is set (Woodpecker CI=woodpecker) and FAIL
loud if absent; KEEP the skip for openssl-less local dev.
- manifest.txt: wake 0.6.2 -> 0.6.3 (digest.sh scrub portability; precedent).
Red-first verified in the ci-base container (root): D4 catches a broken
redaction, H1 catches a tamper that doesn't break the MAC, B12/I8 catch a
corrupted signer.
Closes #912
Part of #892
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
This commit is contained in:
@@ -41,11 +41,20 @@ command -v jq >/dev/null 2>&1 || {
|
||||
echo "SKIP: jq not available" >&2
|
||||
exit 0
|
||||
}
|
||||
# NOTE: whole-suite openssl-skip retained; unmasking D1-D6 to run in CI is tracked in #912 (they currently fail under the CI env).
|
||||
command -v openssl >/dev/null 2>&1 || {
|
||||
echo "SKIP: openssl not available" >&2
|
||||
# openssl gates the HMAC legs (H1/H2). #912: the wake trust layer MUST be
|
||||
# exercised in real CI, so when running under CI (Woodpecker sets CI=woodpecker)
|
||||
# openssl is HARD-REQUIRED — a missing openssl fails the suite LOUD rather than
|
||||
# silently skipping the signer (the §4 G6 evidence must come from an
|
||||
# actually-run HMAC leg, not a skipped one). In an openssl-less LOCAL DEV env
|
||||
# the whole suite still skips so `pnpm test` stays runnable without openssl.
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
if [ -n "${CI:-}" ]; then
|
||||
echo "FATAL (#912): openssl is REQUIRED in CI to exercise the wake digest/HMAC trust layer, but is not on PATH. The CI image must provide openssl (see .woodpecker/ci-image.yml)." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "SKIP: openssl not available (local dev; CI hard-requires it)" >&2
|
||||
exit 0
|
||||
}
|
||||
fi
|
||||
|
||||
TMP_ROOT="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_ROOT"' EXIT
|
||||
@@ -209,10 +218,26 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
|
||||
# ANSI escape / CSI must be gone.
|
||||
printf '%s' "$out" | LC_ALL=C grep -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub"
|
||||
# bidi/zero-width/BOM UTF-8 sequences must be gone.
|
||||
printf '%s' "$out" | LC_ALL=C grep -qP '\xe2\x80[\x8b-\x8f\xaa-\xae]|\xef\xbb\xbf' &&
|
||||
# #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox
|
||||
# grep (Alpine/musl CI) has no `-P` — a `grep -qP` there errors
|
||||
# ("unrecognized option: P"), returns non-zero, and the `&&` silently skips
|
||||
# the assertion, so the scrub was NEVER checked in CI. Literal-byte ranges
|
||||
# under `grep -E` + LC_ALL=C match identically on BusyBox and GNU grep.
|
||||
# Two DISJOINT byte ranges: U+200B..U+200F (E2 80 8B..8F, zero-width) and
|
||||
# U+202A..U+202E (E2 80 AA..AE, bidi). NOT a single 8B..AE range — that would
|
||||
# wrongly flag legitimate E2 80 xx punctuation in between, e.g. U+2014 EM DASH
|
||||
# (E2 80 94) which the digest body uses.
|
||||
_b280="$(printf '%b' '\xe2\x80')"
|
||||
_b8b="$(printf '%b' '\x8b')"; _b8f="$(printf '%b' '\x8f')"
|
||||
_baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')"
|
||||
_bbom="$(printf '%b' '\xef\xbb\xbf')"
|
||||
printf '%s' "$out" | LC_ALL=C grep -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" &&
|
||||
fail_msg "D4: bidi/zero-width/BOM survived the scrub"
|
||||
# C0 control bytes (except tab/newline) must be gone.
|
||||
printf '%s' "$out" | LC_ALL=C grep -qP '[\x00-\x08\x0e-\x1f\x7f]' && fail_msg "D4: a C0 control byte survived the scrub"
|
||||
_c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')"
|
||||
_c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')"
|
||||
printf '%s' "$out" | LC_ALL=C grep -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" &&
|
||||
fail_msg "D4: a C0 control byte survived the scrub"
|
||||
# Secret canaries must be redacted, never inlined.
|
||||
printf '%s' "$out" | grep -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest"
|
||||
printf '%s' "$out" | grep -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
|
||||
|
||||
Reference in New Issue
Block a user