From e5d5c8495a070af2dcd393cace287fe74a8a819e Mon Sep 17 00:00:00 2001 From: be-coder-05 Date: Wed, 5 Aug 2026 18:09:19 -0500 Subject: [PATCH] test(installer): pin B8 redaction evidence --- .../01-exact-failing-assertion.txt | 14 + .../02-exact-masked-log-and-image-probe.txt | 16 + .../03-positive-control-result.txt | 14 + .../positive-control.log | 10 + .../positive-control.test.sh | 578 ++++++++++++++++++ .../1050-install-state-machine-red-fixture.md | 2 +- tools/install-next-lane.test.sh | 12 +- 7 files changed, 641 insertions(+), 5 deletions(-) create mode 100644 docs/reports/verification/1050-b8-redaction-control/01-exact-failing-assertion.txt create mode 100644 docs/reports/verification/1050-b8-redaction-control/02-exact-masked-log-and-image-probe.txt create mode 100644 docs/reports/verification/1050-b8-redaction-control/03-positive-control-result.txt create mode 100644 docs/reports/verification/1050-b8-redaction-control/positive-control.log create mode 100755 docs/reports/verification/1050-b8-redaction-control/positive-control.test.sh diff --git a/docs/reports/verification/1050-b8-redaction-control/01-exact-failing-assertion.txt b/docs/reports/verification/1050-b8-redaction-control/01-exact-failing-assertion.txt new file mode 100644 index 00000000..9a4771fd --- /dev/null +++ b/docs/reports/verification/1050-b8-redaction-control/01-exact-failing-assertion.txt @@ -0,0 +1,14 @@ +subject_head=3edde464b3891ad439019fcc19aad7728e4c2fb8 +source=git show HEAD:tools/install-next-lane.test.sh + + 477 echo 'credentialed URL userinfo leaked to terminal output' >&2; exit 1 + 478 fi + 479 [[ "$(grep -oF '[REDACTED]@' <<<"$OUTPUT" | wc -l | tr -d ' ')" -ge 5 ]] \ + 480 || { echo 'credentialed URL redaction controls were not all exercised' >&2; exit 1; } + 481 secret_active="$TMP/secret-state/active.json" +-- + 525 echo 'framework nested capture leaked credential diagnostics' >&2; exit 1 + 526 fi + 527 [[ "$(grep -oF '[REDACTED]@' "$framework_log" | wc -l | tr -d ' ')" -ge 5 ]] \ + 528 || { echo 'framework URL redaction controls were not exercised' >&2; exit 1; } + 529 diff --git a/docs/reports/verification/1050-b8-redaction-control/02-exact-masked-log-and-image-probe.txt b/docs/reports/verification/1050-b8-redaction-control/02-exact-masked-log-and-image-probe.txt new file mode 100644 index 00000000..f523ca06 --- /dev/null +++ b/docs/reports/verification/1050-b8-redaction-control/02-exact-masked-log-and-image-probe.txt @@ -0,0 +1,16 @@ +source=/tmp/c1-ci-next-x.log (exact failing canonical-image xtrace) +credential material is already replaced by the redactor token [REDACTED]; no live secret is reproduced + + urls=https://[REDACTED]@example.com/a https://[REDACTED]@example.net/b https://[REDACTED]@example.org/c https://[REDACTED]@example.dev/d https://[REDACTED]@example.io/e + urls=https://[REDACTED]@example.com/a https://[REDACTED]@example.net/b https://[REDACTED]@example.org/c https://[REDACTED]@example.dev/d https://[REDACTED]@example.io/e + +line_count=2 +occurrence_count=10 +observed_assertion_value=2 (from xtrace: [[ 2 -ge 5 ]]) + +canonical-image discriminator (same locally cached digest as failing run): +image_id=sha256:d40fb1a218b72d3dcbf8a427a5076facf2a6d958b6854e6bbd057f7264540841 repo_digests=["git.mosaicstack.dev/mosaicstack/stack/ci-base@sha256:0f1d996a6cfcc09e6dcf979ee66c872a1b0be4f1bfde852b4790f520ddd0d776"] +busybox=BusyBox v1.37.0 (2026-01-10 15:38:28 UTC) +regex_-o_single_line=5 +fixed_-oF_single_line=1 +fixed_-oF_two_lines=2 diff --git a/docs/reports/verification/1050-b8-redaction-control/03-positive-control-result.txt b/docs/reports/verification/1050-b8-redaction-control/03-positive-control-result.txt new file mode 100644 index 00000000..1243d377 --- /dev/null +++ b/docs/reports/verification/1050-b8-redaction-control/03-positive-control-result.txt @@ -0,0 +1,14 @@ +positive_control_exit=1 +seeded_line=https://[MASKED-USERINFO]@example.io/e (actual synthetic userinfo intentionally omitted here) +expected_failure=credentialed URL redaction control missing for example.io +transcript_tail: +[test] --next fast path pins resolved package versions +[test] fast path failure falls back to source build +[test] source-build failure is fatal and restores the pre-install prefix +[test] corrupt source archive is fatal and restores the pre-install prefix +[test] --dev source install does not require registry version resolution +[test] explicit --ref keeps source lane and avoids @next lookup +[test] --check --next rejects mismatched prerelease pipeline suffixes +[test] full framework path receives P3 absolute CLI without relying on PATH +[test] captured diagnostics redact seeded credential canary everywhere +credentialed URL redaction control missing for example.io diff --git a/docs/reports/verification/1050-b8-redaction-control/positive-control.log b/docs/reports/verification/1050-b8-redaction-control/positive-control.log new file mode 100644 index 00000000..023dc269 --- /dev/null +++ b/docs/reports/verification/1050-b8-redaction-control/positive-control.log @@ -0,0 +1,10 @@ +[test] --next fast path pins resolved package versions +[test] fast path failure falls back to source build +[test] source-build failure is fatal and restores the pre-install prefix +[test] corrupt source archive is fatal and restores the pre-install prefix +[test] --dev source install does not require registry version resolution +[test] explicit --ref keeps source lane and avoids @next lookup +[test] --check --next rejects mismatched prerelease pipeline suffixes +[test] full framework path receives P3 absolute CLI without relying on PATH +[test] captured diagnostics redact seeded credential canary everywhere +credentialed URL redaction control missing for example.io diff --git a/docs/reports/verification/1050-b8-redaction-control/positive-control.test.sh b/docs/reports/verification/1050-b8-redaction-control/positive-control.test.sh new file mode 100755 index 00000000..8df258ba --- /dev/null +++ b/docs/reports/verification/1050-b8-redaction-control/positive-control.test.sh @@ -0,0 +1,578 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="/work" +TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-next-install-test-XXXXXX")" +trap 'rm -rf "$TMP"' EXIT +export TMPDIR="$TMP/runtime-tmp" +mkdir -p "$TMPDIR" + +FAKE_BIN="$TMP/bin" +HOME_DIR="$TMP/home" +PREFIX="$HOME_DIR/prefix" +MOSAIC_HOME="$HOME_DIR/mosaic" +STATE="$TMP/state" +LOG="$TMP/npm.log" +mkdir -p "$FAKE_BIN" "$HOME_DIR" "$STATE" + +# Model the supported non-root/glibc target explicitly even when this harness +# itself runs as root in Alpine/BusyBox CI. +cat > "$FAKE_BIN/id" <<'FAKE_ID' +#!/usr/bin/env bash +case "${1:-}" in + -u) echo 1001 ;; + -g) echo 1001 ;; + -un) echo fixture-user ;; + *) exec /bin/id "$@" ;; +esac +FAKE_ID +cat > "$FAKE_BIN/getent" < "$FAKE_BIN/ldd" <<'FAKE_LDD' +#!/usr/bin/env bash +printf 'ldd (GNU libc) 2.36\n' +FAKE_LDD +cat > "$FAKE_BIN/stat" <<'FAKE_STAT' +#!/usr/bin/env bash +if [[ "${1:-} ${2:-}" == '-c %u' ]]; then + [[ "${3:-}" == "${MOSAIC_TEST_WRONG_OWNER_PATH:-__none__}" ]] && echo 9999 || echo 1001 + exit 0 +fi +if [[ "${1:-} ${2:-}" == '-c %g' ]]; then + [[ "${3:-}" == "${MOSAIC_TEST_WRONG_GROUP_PATH:-__none__}" ]] && echo 9999 || echo 1001 + exit 0 +fi +exec /bin/stat "$@" +FAKE_STAT +cat > "$FAKE_BIN/realpath" <<'FAKE_REALPATH' +#!/usr/bin/env python3 +import os, sys +args=sys.argv[1:] +mode=args.pop(0) if args and args[0] in ('-e','-m') else '-m' +if args and args[0]=='--': args.pop(0) +if len(args)!=1 or (mode=='-e' and not os.path.exists(args[0])): raise SystemExit(1) +print(os.path.realpath(args[0])) +FAKE_REALPATH +chmod 0755 "$FAKE_BIN/id" "$FAKE_BIN/getent" "$FAKE_BIN/ldd" "$FAKE_BIN/stat" "$FAKE_BIN/realpath" + +cat > "$FAKE_BIN/npm" <<'FAKE_NPM' +#!/usr/bin/env bash +set -euo pipefail +LOG="${MOSAIC_TEST_NPM_LOG:?}" +STATE="${MOSAIC_TEST_STATE:?}" +echo "$*" >> "$LOG" + +if [[ "${1:-}" == "--version" ]]; then + echo "10.6.2" + exit 0 +fi + +install_cli() { + local version="$1" + echo "$version" > "$STATE/mosaic" + mkdir -p "${MOSAIC_PREFIX:?}/bin" + cat > "$MOSAIC_PREFIX/bin/mosaic" <> "\${MOSAIC_TEST_NPM_LOG:?}" + mkdir -p "\${MOSAIC_HOME:?}" "\${HOME:?}/.config/mosaic-gateway" + printf '# Soul\\n\\nConfigured.\\n' > "\$MOSAIC_HOME/SOUL.md" + printf '# User\\n\\nConfigured.\\n' > "\$MOSAIC_HOME/USER.md" + chmod 0600 "\$MOSAIC_HOME/SOUL.md" "\$MOSAIC_HOME/USER.md" + exit 0 +fi +printf '%s\\n' '$version' +CLI + chmod +x "$MOSAIC_PREFIX/bin/mosaic" +} + +if [[ "$1" == "view" ]]; then + if [[ "${MOSAIC_TEST_FAIL_NPM_VIEW:-0}" == "1" ]]; then + echo "forced registry metadata failure" >&2 + exit 1 + fi + case "$2 $3" in + "@mosaicstack/mosaic@next version") echo "0.0.49-next.999" ;; + "@mosaicstack/gateway@next version") echo "${MOSAIC_TEST_GATEWAY_NEXT_VERSION:-0.0.7-next.999}" ;; + "@mosaicstack/mosaic version") echo "0.0.48" ;; + *) echo "unexpected npm view: $*" >&2; exit 1 ;; + esac + exit 0 +fi + +if [[ "$1" == "install" ]]; then + if [[ -n "${MOSAIC_INSTALL_SECRET_CANARY:-}" ]]; then + printf 'registry diagnostic authToken=%s\n' "$MOSAIC_INSTALL_SECRET_CANARY" + printf 'urls=https://alice:p@ss@example.com/a https://bob:pa:ss@example.net/b https://carol:p%%40ss@example.org/c https://token@example.dev/d https://public.example/e\n' + printf 'Authorization: Basic QWxhZGRpbjpvcGVu\n//registry/:_auth=Ym9iOnNlY3JldA==\nCookie: session=abc123\nSet-Cookie: sid=xyz789\n' + printf '%s\n' "$MOSAIC_INSTALL_SECRET_CANARY" > "${MOSAIC_TEST_CANARY_OBSERVATION:?}" + fi + case "$*" in + *"@mosaicstack/mosaic@0.0.49-next.999"*) + install_cli "0.0.49-next.999" + ;; + *"@mosaicstack/gateway@0.0.7-next.999"*) + if [[ "${MOSAIC_TEST_FAIL_NEXT_GATEWAY_INSTALL:-0}" == "1" ]]; then + echo "forced gateway install failure" >&2 + exit 1 + fi + echo "0.0.7-next.999" > "$STATE/gateway" + ;; + *"mosaicstack-mosaic-0.0.0-source.tgz"*) + install_cli "0.0.0-source" + ;; + *"mosaicstack-gateway-0.0.0-source.tgz"*) + echo "0.0.0-source" > "$STATE/gateway" + ;; + *) echo "unexpected npm install: $*" >&2; exit 1 ;; + esac + exit 0 +fi + +if [[ "$1" == "ls" ]]; then + cli="$(cat "$STATE/mosaic" 2>/dev/null || true)" + gateway="$(cat "$STATE/gateway" 2>/dev/null || true)" + node -e ' + const cli = process.argv[1]; + const gateway = process.argv[2]; + const dependencies = {}; + if (cli) dependencies["@mosaicstack/mosaic"] = { version: cli }; + if (gateway) dependencies["@mosaicstack/gateway"] = { version: gateway }; + process.stdout.write(JSON.stringify({ dependencies })); + ' "$cli" "$gateway" + exit 0 +fi + +echo "unexpected npm command: $*" >&2 +exit 1 +FAKE_NPM +chmod +x "$FAKE_BIN/npm" + +cat > "$FAKE_BIN/curl" <<'FAKE_CURL' +#!/usr/bin/env bash +set -euo pipefail +headers=""; output=""; url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -D) headers="$2"; shift 2 ;; + -o) output="$2"; shift 2 ;; + --max-filesize) shift 2 ;; + -*) shift ;; + *) url="$1"; shift ;; + esac +done +case "$url" in + */api/v1/repos/mosaicstack/stack/commits?sha=*) + printf 'HTTP/1.1 200 OK\r\ncontent-type: application/json; charset=utf-8\r\n\r\n' > "$headers" + printf '[{"sha":"1111111111111111111111111111111111111111"}]\n' > "$output" + ;; + */archive/*.tar.gz) + if [[ "${MOSAIC_TEST_CORRUPT_ARCHIVE:-0}" == "1" ]]; then + printf 'not-a-tarball\n' > "$output" + else + archive_root="$(mktemp -d)" + mkdir -p "$archive_root/stack" + printf 'fixture\n' > "$archive_root/stack/.fixture" + /bin/tar czf "$output" -C "$archive_root" stack + rm -rf "$archive_root" + fi + ;; +esac +FAKE_CURL +chmod +x "$FAKE_BIN/curl" + +cat > "$FAKE_BIN/tar" <<'FAKE_TAR' +#!/usr/bin/env bash +set -euo pipefail +dest=""; list=false +while [[ $# -gt 0 ]]; do + case "$1" in + -C) dest="$2"; shift 2 ;; + -*t*|t*) list=true; shift ;; + *) shift ;; + esac +done +[[ "$list" == true ]] && exit 0 +if [[ -z "$dest" ]]; then + echo "fake tar missing -C destination" >&2 + exit 1 +fi +mkdir -p "$dest/stack/packages/mosaic/framework" "$dest/stack/apps/gateway" +cat > "$dest/stack/packages/mosaic/framework/install.sh" <<'FRAMEWORK' +#!/usr/bin/env bash +set -euo pipefail +expected="${MOSAIC_PREFIX:?}/bin/mosaic" +[[ "${MOSAIC_CLI_PATH:-}" == "$expected" && -x "$MOSAIC_CLI_PATH" ]] || { + echo "framework did not receive P3 absolute CLI (got=${MOSAIC_CLI_PATH:-unset} expected=$expected)" >&2 + exit 61 +} +printf 'framework-cli=%s version=%s\n' "$MOSAIC_CLI_PATH" "$($MOSAIC_CLI_PATH --version)" >> "${MOSAIC_TEST_NPM_LOG:?}" +mkdir -p "${MOSAIC_HOME:?}/credentials" +chmod 0700 "$MOSAIC_HOME/credentials" +printf '# framework fixture\n' > "$MOSAIC_HOME/AGENTS.md" +FRAMEWORK +chmod 0755 "$dest/stack/packages/mosaic/framework/install.sh" +FAKE_TAR +chmod +x "$FAKE_BIN/tar" + +cat > "$FAKE_BIN/pnpm" <<'FAKE_PNPM' +#!/usr/bin/env bash +set -euo pipefail +LOG="${MOSAIC_TEST_NPM_LOG:?}" +echo "pnpm $*" >> "$LOG" + +if [[ "$1" == "pack" ]]; then + out="" + while [[ $# -gt 0 ]]; do + case "$1" in + --pack-destination) out="$2"; shift 2 ;; + *) shift ;; + esac + done + if [[ -z "$out" ]]; then + echo "fake pnpm pack missing destination" >&2 + exit 1 + fi + mkdir -p "$out" + case "$PWD" in + */apps/gateway) touch "$out/mosaicstack-gateway-0.0.0-source.tgz" ;; + */packages/mosaic) touch "$out/mosaicstack-mosaic-0.0.0-source.tgz" ;; + *) echo "unexpected pnpm pack cwd: $PWD" >&2; exit 1 ;; + esac + exit 0 +fi + +if [[ "${MOSAIC_TEST_FAIL_PNPM_INSTALL:-0}" == "1" && "$1" == "install" ]]; then + echo "forced pnpm install failure" >&2 + exit 42 +fi + +# Other install/build commands are no-ops in this harness. +exit 0 +FAKE_PNPM +chmod +x "$FAKE_BIN/pnpm" + +reset_state() { + : > "$LOG" + rm -f "$STATE"/* +} + +tree_fingerprint() { + local root="$1" + if [[ ! -d "$root" ]]; then printf 'ABSENT\n'; return; fi + python3 - "$root" <<'PY' +import hashlib, os, stat, sys +root=os.path.abspath(sys.argv[1]); rows=[] +for current, dirs, files in os.walk(root, topdown=True, followlinks=False): + for name in dirs + files: + path=os.path.join(current,name); meta=os.lstat(path) + rel=os.path.relpath(path,root) + target=os.readlink(path) if stat.S_ISLNK(meta.st_mode) else '' + digest='' + if stat.S_ISREG(meta.st_mode): + with open(path,'rb') as handle: digest=hashlib.sha256(handle.read()).hexdigest() + rows.append((rel,stat.S_IFMT(meta.st_mode),stat.S_IMODE(meta.st_mode),target,digest)) +payload='\n'.join('|'.join(map(str,row)) for row in sorted(rows)).encode() +print(hashlib.sha256(payload).hexdigest()) +PY +} + +prefix_fingerprint() { tree_fingerprint "$PREFIX"; } + +reset_state +echo "[test] --next fast path pins resolved package versions" +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch +)" + +grep -qF 'Installed @next packages: CLI 0.0.49-next.999, gateway 0.0.7-next.999' <<<"$OUTPUT" +grep -qF 'install -g @mosaicstack/gateway@0.0.7-next.999' "$LOG" +grep -qF 'install -g @mosaicstack/mosaic@0.0.49-next.999' "$LOG" +if grep -qE '^install -g .+@next( |$)' "$LOG"; then + echo "expected exact-version installs, found mutable @next install" >&2 + exit 1 +fi +if grep -qF 'Downloading source ref next at pinned commit' <<<"$OUTPUT"; then + echo "fast path unexpectedly fell back to source" >&2 + exit 1 +fi + +ACTIVE="$HOME_DIR/.local/state/mosaic/install/active.json" +[[ "$(node -p "require('$ACTIVE').status")" == "committed" ]] +JOURNAL="$(node -p "require('$ACTIVE').journal")" +[[ "$(stat -c '%a' "$JOURNAL")" == "444" ]] +( cd "$(dirname "$JOURNAL")" && sha256sum -c "$(basename "$JOURNAL").sha256" >/dev/null ) +grep -q '"event":"mutation".*"phase":"P3".*path=.*prior=.*reverse=' "$JOURNAL" + +reset_state +echo "[test] fast path failure falls back to source build" +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + MOSAIC_TEST_FAIL_NEXT_GATEWAY_INSTALL=1 \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch +)" + +grep -qF 'Fast gateway @next install failed.' <<<"$OUTPUT" +grep -qF 'Falling back to source build at ref next; --next will not hard-fail on registry issues.' <<<"$OUTPUT" +grep -qF 'Downloading source ref next at pinned commit 1111111111111111111111111111111111111111' <<<"$OUTPUT" +grep -qF 'Installed from source: CLI 0.0.0-source' <<<"$OUTPUT" +grep -qF 'install -g @mosaicstack/mosaic@0.0.49-next.999' "$LOG" +grep -qE 'install -g .*/mosaicstack-gateway-0\.0\.0-source\.tgz' "$LOG" +grep -qE 'install -g .*/mosaicstack-mosaic-0\.0\.0-source\.tgz' "$LOG" +[[ "$(cat "$STATE/mosaic")" == "0.0.0-source" ]] +[[ "$(cat "$STATE/gateway")" == "0.0.0-source" ]] + +reset_state +echo "[test] source-build failure is fatal and restores the pre-install prefix" +before_prefix="$(prefix_fingerprint)" +set +e +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + MOSAIC_TEST_FAIL_NEXT_GATEWAY_INSTALL=1 \ + MOSAIC_TEST_FAIL_PNPM_INSTALL=1 \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch 2>&1 +)" +FAIL_STATUS=$? +set -e +[[ "$FAIL_STATUS" -ne 0 ]] +[[ "$(prefix_fingerprint)" == "$before_prefix" ]] +grep -qF 'forced pnpm install failure' <<<"$OUTPUT" +[[ "$(node -p "require('$ACTIVE').status")" == "rolled-back" ]] + +reset_state +echo "[test] corrupt source archive is fatal and restores the pre-install prefix" +before_prefix="$(prefix_fingerprint)" +set +e +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + MOSAIC_TEST_FAIL_NEXT_GATEWAY_INSTALL=1 \ + MOSAIC_TEST_CORRUPT_ARCHIVE=1 \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch 2>&1 +)" +FAIL_STATUS=$? +set -e +[[ "$FAIL_STATUS" -ne 0 ]] +[[ "$(prefix_fingerprint)" == "$before_prefix" ]] +grep -qF 'archive safety/integrity check failed' <<<"$OUTPUT" +[[ "$(node -p "require('$ACTIVE').status")" == "rolled-back" ]] + +reset_state +echo "[test] --dev source install does not require registry version resolution" +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + MOSAIC_TEST_FAIL_NPM_VIEW=1 \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --cli --dev --ref feature-x --yes --no-auto-launch +)" +grep -qF 'Downloading source ref feature-x at pinned commit 1111111111111111111111111111111111111111' <<<"$OUTPUT" +grep -qF 'Installed from source: CLI 0.0.0-source' <<<"$OUTPUT" +grep -q '^\[P2\] PASS: source_ref=feature-x pinned_commit=1111111111111111111111111111111111111111 sha256=' <<<"$OUTPUT" + +reset_state +echo "[test] explicit --ref keeps source lane and avoids @next lookup" +set +e +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --check --cli --next --ref feature-x +)" +CHECK_STATUS=$? +set -e +[[ "$CHECK_STATUS" -ne 0 ]] +grep -q '^\[P2\] PASS: source_ref=feature-x pinned_commit=1111111111111111111111111111111111111111 sha256=' <<<"$OUTPUT" +if grep -qF '@next version' "$LOG"; then + echo "explicit ref should not query @next dist-tags" >&2 + exit 1 +fi + +reset_state +echo "[test] --check --next rejects mismatched prerelease pipeline suffixes" +set +e +OUTPUT="$( + HOME="$HOME_DIR" \ + MOSAIC_HOME="$MOSAIC_HOME" \ + MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" \ + MOSAIC_TEST_STATE="$STATE" \ + MOSAIC_TEST_GATEWAY_NEXT_VERSION="0.0.7-next.1000" \ + PATH="$FAKE_BIN:$PATH" \ + bash "$ROOT/tools/install.sh" --check --cli --next +)" +CHECK_STATUS=$? +set -e +[[ "$CHECK_STATUS" -ne 0 ]] +grep -q '^\[P2\] FAIL: resolved_version=unavailable' <<<"$OUTPUT" + +printf '[test] full framework path receives P3 absolute CLI without relying on PATH\n' +rm -rf "$HOME_DIR" "$STATE"; mkdir -p "$HOME_DIR" "$STATE"; reset_state +set +e +OUTPUT="$( + HOME="$HOME_DIR" MOSAIC_HOME="$MOSAIC_HOME" MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_INSTALL_STATE_DIR="$TMP/full-state" MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" MOSAIC_TEST_STATE="$STATE" \ + PATH="$FAKE_BIN:/usr/local/bin:/usr/bin:/bin" \ + bash "$ROOT/tools/install.sh" --next --yes --no-auto-launch 2>&1 +)" +FULL_STATUS=$? +set -e +[[ "$FULL_STATUS" -ne 0 ]] # P4 remains intentionally undeclared until C5. +grep -qF "framework-cli=$PREFIX/bin/mosaic version=0.0.49-next.999" "$LOG" +if grep -q "CLI not found on PATH\|did not receive P3 absolute CLI" <<<"$OUTPUT"; then + echo "internal framework phase depended on PATH instead of P3 absolute CLI" >&2 + exit 1 +fi + +printf '[test] captured diagnostics redact seeded credential canary everywhere\n' +rm -rf "$HOME_DIR" "$STATE"; mkdir -p "$HOME_DIR" "$STATE"; reset_state +canary='C1_SECRET_CANARY_7df4c2' +OUTPUT="$( + HOME="$HOME_DIR" MOSAIC_HOME="$MOSAIC_HOME" MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_INSTALL_STATE_DIR="$TMP/secret-state" MOSAIC_NO_COLOR=1 \ + MOSAIC_INSTALL_SECRET_CANARY="$canary" MOSAIC_TEST_CANARY_OBSERVATION="$TMP/canary-observed" \ + MOSAIC_TEST_NPM_LOG="$LOG" MOSAIC_TEST_STATE="$STATE" \ + PATH="$FAKE_BIN:/usr/local/bin:/usr/bin:/bin" \ + bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch 2>&1 +)" +# Positive control: replace the removed redacted example.io source with one deliberately unredacted userinfo URL. +OUTPUT+=$'\nhttps://leaked@example.io/e' +if grep -qF "$canary" <<<"$OUTPUT"; then echo 'credential canary leaked to terminal output' >&2; exit 1; fi +if grep -Eq 'alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass|QWxhZGRpbjpvcGVu|Ym9iOnNlY3JldA|session=abc123|sid=xyz789' <<<"$OUTPUT"; then + echo 'credentialed URL userinfo leaked to terminal output' >&2; exit 1 +fi +for host in example.com example.net example.org example.dev example.io; do + grep -qF "https://[REDACTED]@$host" <<<"$OUTPUT" \ + || { echo "credentialed URL redaction control missing for $host" >&2; exit 1; } +done +secret_active="$TMP/secret-state/active.json" +secret_journal="$(node -p "require('$secret_active').journal")" +secret_command_log="$(dirname "$secret_journal")/commands.log" +if grep -R -F "$canary" "$secret_command_log" "$HOME_DIR" 2>/dev/null; then + echo 'credential canary leaked to persistent installer output' >&2; exit 1 +fi +if grep -E 'alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass|QWxhZGRpbjpvcGVu|Ym9iOnNlY3JldA|session=abc123|sid=xyz789' "$secret_command_log" >/dev/null; then + echo 'credentialed URL userinfo leaked to persistent installer output' >&2; exit 1 +fi +if [[ "$(cat "$TMP/canary-observed" 2>/dev/null || true)" != "$canary" ]]; then + echo 'credential canary positive control was not exercised' >&2; exit 1 +fi +if find "$TMPDIR" -maxdepth 1 -type f \( -name 'mosaic-phase-redacted.*' -o -name 'mosaic-post-redacted.*' \) -print -quit | grep -q .; then + echo 'redacted diagnostic staging file survived normal completion' >&2; exit 1 +fi + +printf '[test] framework nested capture redacts the same canary and URL variants\n' +framework_test_home="$TMP/framework-redact-home" +framework_target="$framework_test_home/.config/mosaic" +framework_cli="$TMP/framework-redact-cli" +framework_log="$TMP/framework-redact-commands.log" +framework_status="$TMP/framework-redact-status.tsv" +mkdir -p "$framework_test_home"; : > "$framework_log"; : > "$framework_status" +cat > "$framework_cli" <<'FRAMEWORK_CLI' +#!/usr/bin/env bash +printf 'nested authToken=%s\n' "${MOSAIC_INSTALL_SECRET_CANARY:?}" +printf 'nested=https://alice:p@ss@example.com/a https://bob:pa:ss@example.net/b https://carol:p%%40ss@example.org/c https://token@example.dev/d https://user%%3Apass@example.io/e\n' +printf 'Authorization: Basic QWxhZGRpbjpvcGVu\n//registry/:_auth=Ym9iOnNlY3JldA==\nCookie: session=abc123\nSet-Cookie: sid=xyz789\n' +exit 1 +FRAMEWORK_CLI +chmod 0755 "$framework_cli" +set +e +FRAMEWORK_OUTPUT="$( + HOME="$framework_test_home" MOSAIC_HOME="$framework_target" MOSAIC_INSTALL_MODE=overwrite \ + MOSAIC_CLI_PATH="$framework_cli" MOSAIC_INSTALL_SECRET_CANARY="$canary" \ + MOSAIC_INSTALL_COMMAND_LOG="$framework_log" MOSAIC_INSTALL_PHASE_STATUS_FILE="$framework_status" \ + MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1 MOSAIC_SKIP_SKILLS_SYNC=1 \ + bash "$ROOT/packages/mosaic/framework/install.sh" 2>&1 +)" +framework_install_status=$? +set -e +[[ "$framework_install_status" -eq 0 ]] +if grep -Eq "$canary|alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass|QWxhZGRpbjpvcGVu|Ym9iOnNlY3JldA|session=abc123|sid=xyz789" <<<"$FRAMEWORK_OUTPUT" \ + || grep -Eq "$canary|alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass|QWxhZGRpbjpvcGVu|Ym9iOnNlY3JldA|session=abc123|sid=xyz789" "$framework_log"; then + echo 'framework nested capture leaked credential diagnostics' >&2; exit 1 +fi +for host in example.com example.net example.org example.dev example.io; do + grep -qF "https://[REDACTED]@$host" "$framework_log" \ + || { echo "framework URL redaction control missing for $host" >&2; exit 1; } +done + +printf '[test] real P2-P8 actions run under fault injection and restore actual surfaces\n' +for phase in P2 P3 P4 P5 P6 P7 P8; do + rm -rf "$HOME_DIR" "$STATE" "$TMP/fault-$phase"; mkdir -p "$HOME_DIR" "$STATE" "$TMP/fault-$phase" + printf 'operator-sentinel\n' > "$HOME_DIR/operator.txt" + reset_state + before="$(tree_fingerprint "$HOME_DIR")" + set +e + HOME="$HOME_DIR" MOSAIC_HOME="$MOSAIC_HOME" MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_INSTALL_STATE_DIR="$TMP/fault-$phase" MOSAIC_INSTALL_FAULT_AFTER="$phase" \ + MOSAIC_INSTALL_SELF_TEST_ALLOW=1 MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" MOSAIC_TEST_STATE="$STATE" \ + PATH="$FAKE_BIN:/usr/local/bin:/usr/bin:/bin" \ + bash "$ROOT/tools/install.sh" --state-machine-self-test --next --yes \ + >"$TMP/fault-$phase.log" 2>&1 + status=$? + set -e + [[ "$status" -eq 97 ]] || { echo "$phase real fault expected 97, got $status" >&2; exit 1; } + [[ -s "$LOG" ]] || { echo "$phase fault never entered the real action path" >&2; exit 1; } + [[ "$(tree_fingerprint "$HOME_DIR")" == "$before" ]] || { echo "$phase real rollback mismatch" >&2; exit 1; } + grep -q "phase=$phase" "$TMP/fault-$phase.log" + if find "$TMP/fault-$phase" -type f -exec grep -l '"status"[[:space:]]*:[[:space:]]*"in-progress"' {} + 2>/dev/null | grep -q .; then + echo "$phase left an in-progress transaction" >&2; exit 1 + fi +done + +printf '[test] stale projection is preserved while the real fault path acquires a free OS lock\n' +rm -rf "$HOME_DIR" "$STATE" "$TMP/stale-state"; mkdir -p "$HOME_DIR" "$STATE" "$TMP/stale-state" +printf '{"status":"in-progress","journal":"%s"}\n' "$TMP/stale-state/dead-run/journal.ndjson" > "$TMP/stale-state/active.json" +reset_state +set +e +HOME="$HOME_DIR" MOSAIC_HOME="$MOSAIC_HOME" MOSAIC_PREFIX="$PREFIX" \ + MOSAIC_INSTALL_STATE_DIR="$TMP/stale-state" MOSAIC_INSTALL_FAULT_AFTER=P2 \ + MOSAIC_INSTALL_SELF_TEST_ALLOW=1 MOSAIC_NO_COLOR=1 \ + MOSAIC_TEST_NPM_LOG="$LOG" MOSAIC_TEST_STATE="$STATE" \ + PATH="$FAKE_BIN:/usr/local/bin:/usr/bin:/bin" \ + bash "$ROOT/tools/install.sh" --state-machine-self-test --next --yes >"$TMP/stale.log" 2>&1 +stale_status=$? +set -e +[[ "$stale_status" -eq 97 ]] +find "$TMP/stale-state" -name prior-active.json -type f -print -quit | grep -q . +[[ "$(node -p "require('$TMP/stale-state/active.json').status")" == rolled-back ]] + +echo "[test] installer next lane tests passed" diff --git a/docs/scratchpads/1050-install-state-machine-red-fixture.md b/docs/scratchpads/1050-install-state-machine-red-fixture.md index 9af9c34c..5fe9d10e 100644 --- a/docs/scratchpads/1050-install-state-machine-red-fixture.md +++ b/docs/scratchpads/1050-install-state-machine-red-fixture.md @@ -69,7 +69,7 @@ Implement C1 from the canonical greenfield-install PRD v2: a transactional P0– - B6 RED: fault injection only wrote `.selftest-*` files. The synthetic path was removed; the P2–P8 matrix enters the normal action flow, proves an action observation occurred, injects after each real phase, and fingerprints rollback. - B7 RED: an HTTP-200 empty body exits zero when piped to Bash. The fetched installer must now be non-empty, digest-equal to `tools/install.sh.sha256`, and that exact file is executed; failed/empty/mismatch controls are blocking and CI has a remote immutable-commit arm. - B8 RED: raw combined command output was duplicated to terminal and `commands.log`. Both capture layers now redact before output/persistence; a seeded canary is positively emitted by the fake credential-capable registry and must remain absent from terminal, command log, npmrc, generated files and observed argv. The real greenfield fixture also scans those populations. -- Advisory code review findings are fixed: URL userinfo redaction now handles raw `@`, repeated `:`, percent encoding and multiple URLs in both capture layers; the real greenfield path positively emits its canary through `state_run_captured`; and verified-fetch removes its temporary body after successful execution. +- Advisory code review findings are fixed: URL userinfo redaction now handles raw `@`, token-only and percent-encoded forms, repeated `:`, multiple URLs, Authorization/Basic, npm `_auth`, and Cookie headers in both capture layers; the real greenfield path positively emits its canary through `state_run_captured`; verified-fetch removes its temporary body after successful execution; and plaintext diagnostics exist only in process-substitution pipes rather than interruptible temporary files. - Advisory security review's independent trust-root finding is **DEFERRED by canonical PRD v2 §3**, which explicitly excludes signed provenance. README now states precisely that the same-origin sidecar detects empty/corrupt/inconsistent publication but cannot authenticate against repository/server compromise; no stronger claim remains. - The web1 no-manifest representativeness observation is recorded but intentionally not acted on: it is explicitly outside these eight blockers. This remediation does not weaken or otherwise change P9's manifest-presence assertion. diff --git a/tools/install-next-lane.test.sh b/tools/install-next-lane.test.sh index ef320d94..b69bdd2a 100755 --- a/tools/install-next-lane.test.sh +++ b/tools/install-next-lane.test.sh @@ -476,8 +476,10 @@ if grep -qF "$canary" <<<"$OUTPUT"; then echo 'credential canary leaked to termi if grep -Eq 'alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass|QWxhZGRpbjpvcGVu|Ym9iOnNlY3JldA|session=abc123|sid=xyz789' <<<"$OUTPUT"; then echo 'credentialed URL userinfo leaked to terminal output' >&2; exit 1 fi -[[ "$(grep -oF '[REDACTED]@' <<<"$OUTPUT" | wc -l | tr -d ' ')" -ge 5 ]] \ - || { echo 'credentialed URL redaction controls were not all exercised' >&2; exit 1; } +for host in example.com example.net example.org example.dev example.io; do + grep -qF "https://[REDACTED]@$host" <<<"$OUTPUT" \ + || { echo "credentialed URL redaction control missing for $host" >&2; exit 1; } +done secret_active="$TMP/secret-state/active.json" secret_journal="$(node -p "require('$secret_active').journal")" secret_command_log="$(dirname "$secret_journal")/commands.log" @@ -524,8 +526,10 @@ if grep -Eq "$canary|alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass || grep -Eq "$canary|alice:p@ss|bob:pa:ss|carol:p%40ss|token@example|user%3Apass|QWxhZGRpbjpvcGVu|Ym9iOnNlY3JldA|session=abc123|sid=xyz789" "$framework_log"; then echo 'framework nested capture leaked credential diagnostics' >&2; exit 1 fi -[[ "$(grep -oF '[REDACTED]@' "$framework_log" | wc -l | tr -d ' ')" -ge 5 ]] \ - || { echo 'framework URL redaction controls were not exercised' >&2; exit 1; } +for host in example.com example.net example.org example.dev example.io; do + grep -qF "https://[REDACTED]@$host" "$framework_log" \ + || { echo "framework URL redaction control missing for $host" >&2; exit 1; } +done printf '[test] real P2-P8 actions run under fault injection and restore actual surfaces\n' for phase in P2 P3 P4 P5 P6 P7 P8; do