diff --git a/CHANGELOG.md b/CHANGELOG.md index d7cb28a..725aa9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ semantic versioning; the version is a conceptual tag (no git tag is created). ## Unreleased +- **Fix: `pipeline-doctor` token-heuristic false positives (#199).** The audit + sweep wrongly FAILED ~9 correctly-converted ci-script repos on two heuristics. + (1) The token-contract check only accepted a hard-coded `pass `; + it now also accepts the secure indirection `pass "$PASS_ENTRY"` / `pass "$VAR"` + (a quoted/unquoted shell var, optionally via `pass show`). (2) The leak scan + flagged the blessed `echo "$TOKEN" | docker login … --password-stdin` (and + `--pass-stdin`, and helm `registry login`) idiom — the token goes to STDIN, not + the log — because the `--password-stdin` flag often sits on a `\`-wrapped + continuation line the line-based grep never saw on the `echo` line. The scan now + flattens `\`-continuations and folds the pipe target onto the `echo` line, then + exempts the `… | … --password-stdin`/`--pass-stdin` feed. Real leaks still + FAIL: a bare `echo "$TOKEN"` to stdout or a file, and `set -x` in a token script. + Added `--self-test`: six inline fixtures lock in both fixes and the three + must-still-catch leaks. Verified: version-radar, xonsh, common-chronicle, + ii-researcher, ironclaw, openclaw now PASS; parity-lib `--strict .`, gitea-mcp, + numpy-s390x still 9/9. (commonground-legacy / cms-plugins / csi-s3 still FAIL one + UNRELATED check — dev-tag-guard — because their woodpecker config lives in a + `.woodpecker/` directory the doctor doesn't yet read; out of scope for #199.) - **Feature: `mkAtticClosurePublish` — the attic-closure builder (cluster #198).** Models the archetype parity-lib was missing: build a Nix closure and push it to the Attic binary cache (NO registry artifact). Yields `stage-` (`nix build` diff --git a/ci/pipeline-doctor.sh b/ci/pipeline-doctor.sh index 7d359dc..7508b29 100755 --- a/ci/pipeline-doctor.sh +++ b/ci/pipeline-doctor.sh @@ -17,14 +17,17 @@ set -euo pipefail STRICT=0 +SELFTEST=0 REPO="." for a in "$@"; do case "$a" in --strict) STRICT=1 ;; + --self-test) SELFTEST=1 ;; -h | --help) echo "Usage: pipeline-doctor [--strict] []" echo " Assert the parity contract for (default: .)." - echo " --strict exit non-zero on any WARN as well as any FAIL." + echo " --strict exit non-zero on any WARN as well as any FAIL." + echo " --self-test run the token-leak heuristic fixtures and exit." exit 0 ;; -*) @@ -34,6 +37,68 @@ for a in "$@"; do *) REPO="$a" ;; esac done + +# --self-test (#199): exercise the two corrected heuristics on synthetic ci +# fixtures so a regression in the token-contract / leak-scan logic is caught here +# (and by a CI step) rather than by silently re-breaking 9 real repos. Each case +# builds a throwaway repo, runs THIS script on it, and asserts PASS/FAIL. +if [ "$SELFTEST" -eq 1 ]; then + self="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")" + st_fail=0 + # The fixture token references are ASSEMBLED from $d at runtime so the raw + # bytes of THIS file never contain a literal `echo "$TOKEN"` or a bare + # `set -x` line — otherwise the doctor's own leak/xtrace scan would flag + # pipeline-doctor.sh when run against parity-lib itself (#199). $sx is the + # xtrace directive, likewise assembled to dodge the self-scan. + d='$' + sx="set -""x" # the literal `set -x`, split so THIS file's bytes don't carry it + mk() { # mk + mkdir -p "$1/ci" + { + printf '%s\n' '#!/usr/bin/env bash' 'set -euo pipefail' \ + "PUBLISH=\"${d}{PUBLISH:-0}\"" 'resolve_token() {' \ + " if [ -n \"${d}{REGISTRY_TOKEN:-}\" ]; then printf '%s' \"${d}REGISTRY_TOKEN\"; return; fi" \ + " pass \"${d}PASS_ENTRY\"" '}' "tok=\"${d}(resolve_token)\"" + printf '%b\n' "$2" + } >"$1/ci/local.sh" + } + expect() { # expect