framework: port the credential-helper regression suite (byte-faithful from brain 15f6979a; verified green from the stack tree)
(cherry picked from commit 62b163a254)
This commit is contained in:
@@ -34,20 +34,23 @@ REPO_DIR="$WORK_DIR/repo"
|
|||||||
BRAIN_DIR="$WORK_DIR/brain"
|
BRAIN_DIR="$WORK_DIR/brain"
|
||||||
SPOOL_DIR="$WORK_DIR/spool"
|
SPOOL_DIR="$WORK_DIR/spool"
|
||||||
SVC_STORE="$FAKE_HOME/.config/mosaic/secrets/gitea-tokens"
|
SVC_STORE="$FAKE_HOME/.config/mosaic/secrets/gitea-tokens"
|
||||||
# Mirror the real deployed layout (~/.config/mosaic/tools/{git,_lib}/) under the
|
# Mirror the real deployed layout (~/.mosaic/tools/{git,_lib}/) under the
|
||||||
# fake HOME: git-credential-mosaic resolves its credentials.sh sibling via a
|
# fake HOME: git-credential-mosaic resolves its credentials.sh sibling via a
|
||||||
# script-relative path (BASH_SOURCE), so the copy must live next to a stubbed
|
# script-relative path (BASH_SOURCE), so the copy must live next to a stubbed
|
||||||
# _lib/credentials.sh, not the real one, to keep this test hermetic.
|
# _lib/credentials.sh, not the real one, to keep this test hermetic.
|
||||||
HELPER="$FAKE_HOME/.config/mosaic/tools/git/git-credential-mosaic"
|
HELPER="$FAKE_HOME/.mosaic/tools/git/git-credential-mosaic"
|
||||||
|
IMPL="$FAKE_HOME/.mosaic/tools/git/git-credential-mosaic.impl"
|
||||||
|
|
||||||
rm -rf "$WORK_DIR"
|
rm -rf "$WORK_DIR"
|
||||||
mkdir -p "$SVC_STORE" \
|
mkdir -p "$SVC_STORE" \
|
||||||
"$FAKE_HOME/.config/mosaic/tools/git" \
|
"$FAKE_HOME/.mosaic/tools/git" \
|
||||||
"$FAKE_HOME/.config/mosaic/tools/_lib" \
|
"$FAKE_HOME/.mosaic/tools/_lib" \
|
||||||
"$REPO_DIR" "$BRAIN_DIR"
|
"$REPO_DIR" "$BRAIN_DIR"
|
||||||
|
|
||||||
cp "$SCRIPT_DIR/git-credential-mosaic" "$HELPER"
|
cp "$SCRIPT_DIR/git-credential-mosaic" "$HELPER"
|
||||||
chmod +x "$HELPER"
|
chmod +x "$HELPER"
|
||||||
|
cp "$SCRIPT_DIR/git-credential-mosaic.impl" "$IMPL"
|
||||||
|
chmod +x "$IMPL"
|
||||||
|
|
||||||
git -C "$REPO_DIR" init -q
|
git -C "$REPO_DIR" init -q
|
||||||
git -C "$REPO_DIR" config user.email "[email protected]"
|
git -C "$REPO_DIR" config user.email "[email protected]"
|
||||||
@@ -55,7 +58,7 @@ git -C "$REPO_DIR" config user.name "Test"
|
|||||||
|
|
||||||
# Fake shared-account credential loader — stands in for
|
# Fake shared-account credential loader — stands in for
|
||||||
# tools/_lib/credentials.sh's load_credentials(), scoped to this test only.
|
# tools/_lib/credentials.sh's load_credentials(), scoped to this test only.
|
||||||
cat > "$FAKE_HOME/.config/mosaic/tools/_lib/credentials.sh" <<'SH'
|
cat > "$FAKE_HOME/.mosaic/tools/_lib/credentials.sh" <<'SH'
|
||||||
load_credentials() {
|
load_credentials() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
gitea-mosaicstack) GITEA_URL="https://git.mosaicstack.dev"; GITEA_TOKEN="shared-mosaicstack-token"; export GITEA_URL GITEA_TOKEN; return 0 ;;
|
gitea-mosaicstack) GITEA_URL="https://git.mosaicstack.dev"; GITEA_TOKEN="shared-mosaicstack-token"; export GITEA_URL GITEA_TOKEN; return 0 ;;
|
||||||
@@ -82,7 +85,7 @@ run_helper() {
|
|||||||
(
|
(
|
||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$SPOOL_DIR" "$@" \
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$SPOOL_DIR" "$@" \
|
||||||
bash "$HELPER" get <<EOF
|
"$HELPER" get <<EOF
|
||||||
host=$host
|
host=$host
|
||||||
username=$username_in
|
username=$username_in
|
||||||
|
|
||||||
@@ -90,6 +93,80 @@ EOF
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Lineage harness (rev-code-02 F1/F2 rework) ─────────────────────────────
|
||||||
|
# Establishes a seat CALLER the way production does — frozen into an
|
||||||
|
# ancestor's exec environment — instead of injecting MOSAIC_AGENT_NAME into
|
||||||
|
# the helper's own env. Two scripts are generated into WORK_DIR:
|
||||||
|
#
|
||||||
|
# lineage-root.sh (pid A): invoked with a fence marker and NO agent name.
|
||||||
|
# With a non-empty caller arg it forks the carrier (pid C); with an
|
||||||
|
# empty caller it forks the helper directly (deterministic anonymous
|
||||||
|
# lineage even when the suite itself runs inside a seat).
|
||||||
|
# lineage-carrier.sh (pid C): MOSAIC_AGENT_NAME=<caller> frozen at exec;
|
||||||
|
# forks the helper (pid D) with a fully controlled env.
|
||||||
|
#
|
||||||
|
# The helper's walk then sees exactly: self -> C(caller) or D-direct ->
|
||||||
|
# A(fence, empty name -> stop). EXTRA assignments ride pid D's environment
|
||||||
|
# (that is where a rewrite would live — which is the point of the F1 arms).
|
||||||
|
cat > "$WORK_DIR/lineage-root.sh" <<'LINROOT'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# pid A — lineage root. Args: <caller> <carrier-script> <helper> <spool>
|
||||||
|
# <brain> <repo> [extra KEY=VALUE...]
|
||||||
|
set -u
|
||||||
|
caller="$1"; carrier="$2"; helper="$3"; spool="$4"; brain="$5"; repo="$6"; shift 6
|
||||||
|
if [ -n "$caller" ]; then
|
||||||
|
env MOSAIC_AGENT_NAME="$caller" PATH="$PATH" HOME="$HOME" \
|
||||||
|
bash "$carrier" "$helper" "$spool" "$brain" "$repo" "$@"
|
||||||
|
else
|
||||||
|
env -i HOME="$HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$spool" \
|
||||||
|
MOSAIC_BRAIN_HOME="$brain" "$@" "$helper" get
|
||||||
|
fi
|
||||||
|
LINROOT
|
||||||
|
cat > "$WORK_DIR/lineage-carrier.sh" <<'LINCARR'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# pid C — the caller's frozen environment. Forks the helper (pid D).
|
||||||
|
set -u
|
||||||
|
helper="$1"; spool="$2"; brain="$3"; repo="$4"; shift 4
|
||||||
|
cd "$repo"
|
||||||
|
env -i HOME="$HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$spool" \
|
||||||
|
MOSAIC_BRAIN_HOME="$brain" "$@" "$helper" get
|
||||||
|
LINCARR
|
||||||
|
chmod +x "$WORK_DIR/lineage-root.sh" "$WORK_DIR/lineage-carrier.sh"
|
||||||
|
|
||||||
|
run_lineage() {
|
||||||
|
# run_lineage <caller|empty-for-anonymous> [helper-env KEY=VALUE...]
|
||||||
|
local caller="$1"; shift
|
||||||
|
printf 'host=git.mosaicstack.dev\nusername=probe\n\n' | \
|
||||||
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_LINEAGE_FENCE=1 \
|
||||||
|
bash "$WORK_DIR/lineage-root.sh" "$caller" "$WORK_DIR/lineage-carrier.sh" \
|
||||||
|
"$HELPER" "$SPOOL_DIR" "$BRAIN_DIR" "$REPO_DIR" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_refused_lineage() {
|
||||||
|
# assert_refused_lineage <desc> <caller> <want-reason> [helper-env...]
|
||||||
|
local desc="$1" caller="$2" want="$3"; shift 3
|
||||||
|
local stderr_file="$WORK_DIR/stderr-lin.tmp" rc stdout
|
||||||
|
: > "$stderr_file"
|
||||||
|
set +e
|
||||||
|
stdout=$(run_lineage "$caller" "$@" 2>"$stderr_file")
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
local stderr; stderr=$(cat "$stderr_file")
|
||||||
|
if [[ "$rc" -eq 0 ]]; then
|
||||||
|
echo "FAIL: $desc — expected nonzero exit, got 0 (stdout='$stdout')" >&2; fail=1
|
||||||
|
fi
|
||||||
|
if [[ -n "$stdout" ]]; then
|
||||||
|
echo "FAIL: $desc — expected empty stdout, got '$stdout'" >&2; fail=1
|
||||||
|
fi
|
||||||
|
if [[ -n "$want" && "$stderr" != *"$want"* ]]; then
|
||||||
|
echo "FAIL: $desc — stderr lacks '$want':" >&2; echo "$stderr" >&2; fail=1
|
||||||
|
fi
|
||||||
|
if [[ "$stdout$stderr" == *"seatG-slot-token"* || "$stdout$stderr" == *"seatE-slot-token"* \
|
||||||
|
|| "$stdout$stderr" == *"shared-mosaicstack-token"* || "$stdout$stderr" == *"shared-usc-token"* ]]; then
|
||||||
|
echo "FAIL: $desc — a slot or shared token VALUE appeared in output" >&2; fail=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# A refusal must be observable in four independent ways: nonzero exit, EMPTY
|
# A refusal must be observable in four independent ways: nonzero exit, EMPTY
|
||||||
# stdout, a stderr diagnostic naming the identity and host, and — the assertion
|
# stdout, a stderr diagnostic naming the identity and host, and — the assertion
|
||||||
# that actually catches a regression to the old behavior — NO shared token value
|
# that actually catches a regression to the old behavior — NO shared token value
|
||||||
@@ -222,7 +299,10 @@ fi
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
mkdir -p "$BRAIN_DIR/fleet/agents/seatE/secrets"
|
mkdir -p "$BRAIN_DIR/fleet/agents/seatE/secrets"
|
||||||
echo -n "seatE-slot-token" > "$BRAIN_DIR/fleet/agents/seatE/secrets/gitea-mosaicstack-seatE.token"
|
echo -n "seatE-slot-token" > "$BRAIN_DIR/fleet/agents/seatE/secrets/gitea-mosaicstack-seatE.token"
|
||||||
out=$(run_helper "git.mosaicstack.dev" "seatE" MOSAIC_BRAIN_HOME="$BRAIN_DIR")
|
chmod 600 "$BRAIN_DIR/fleet/agents/seatE/secrets/gitea-mosaicstack-seatE.token"
|
||||||
|
# Seat arms run through the lineage harness below (rev-code-02 F1/F2 rework):
|
||||||
|
# a seat caller must be established by ancestry, not by the helper's own env.
|
||||||
|
out=$(run_lineage seatE MOSAIC_AGENT_NAME=seatE MOSAIC_GIT_IDENTITY=seatE)
|
||||||
assert_eq "seat reads its own slot: username" "username=seatE" "$(echo "$out" | grep '^username=')"
|
assert_eq "seat reads its own slot: username" "username=seatE" "$(echo "$out" | grep '^username=')"
|
||||||
assert_eq "seat reads its own slot: password" "password=seatE-slot-token" "$(echo "$out" | grep '^password=')"
|
assert_eq "seat reads its own slot: password" "password=seatE-slot-token" "$(echo "$out" | grep '^password=')"
|
||||||
|
|
||||||
@@ -236,8 +316,8 @@ assert_eq "seat reads its own slot: password" "password=seatE-slot-token" "$(ech
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
mkdir -p "$BRAIN_DIR/fleet/agents/seatF/secrets"
|
mkdir -p "$BRAIN_DIR/fleet/agents/seatF/secrets"
|
||||||
echo -n "seatF-SERVICE-STORE-token" > "$SVC_STORE/gitea-mosaicstack-seatF.token"
|
echo -n "seatF-SERVICE-STORE-token" > "$SVC_STORE/gitea-mosaicstack-seatF.token"
|
||||||
assert_fail_closed "seat with empty slot does NOT fall back to the framework store" \
|
assert_refused_lineage "seat with empty slot does NOT fall back to the framework store" \
|
||||||
"git.mosaicstack.dev" "seatF" "fleet/agents/seatF/secrets" MOSAIC_BRAIN_HOME="$BRAIN_DIR"
|
seatF no-token-for-identity MOSAIC_AGENT_NAME=seatF MOSAIC_GIT_IDENTITY=seatF
|
||||||
: > "$WORK_DIR/stderr.tmp"
|
: > "$WORK_DIR/stderr.tmp"
|
||||||
set +e
|
set +e
|
||||||
xstore_out=$(run_helper "git.mosaicstack.dev" "seatF" MOSAIC_BRAIN_HOME="$BRAIN_DIR" 2>"$WORK_DIR/stderr.tmp")
|
xstore_out=$(run_helper "git.mosaicstack.dev" "seatF" MOSAIC_BRAIN_HOME="$BRAIN_DIR" 2>"$WORK_DIR/stderr.tmp")
|
||||||
@@ -288,7 +368,7 @@ assert_eq "unknown host on a fleet host: still passthrough, not a refusal" "" "$
|
|||||||
# 13. Non-"get" verb (store/erase) -> exit 0, no output (git-credential
|
# 13. Non-"get" verb (store/erase) -> exit 0, no output (git-credential
|
||||||
# protocol: this helper only implements get).
|
# protocol: this helper only implements get).
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
store_out=$(cd "$REPO_DIR" && env -i HOME="$FAKE_HOME" PATH="$PATH" bash "$HELPER" store <<EOF
|
store_out=$(cd "$REPO_DIR" && env -i HOME="$FAKE_HOME" PATH="$PATH" "$HELPER" store <<EOF
|
||||||
host=git.mosaicstack.dev
|
host=git.mosaicstack.dev
|
||||||
username=no-such-agent
|
username=no-such-agent
|
||||||
password=whatever
|
password=whatever
|
||||||
@@ -310,7 +390,7 @@ hostile_spool="$WORK_DIR/spool-hostile"
|
|||||||
cd "$hostile_dir"
|
cd "$hostile_dir"
|
||||||
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$hostile_spool" \
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$hostile_spool" \
|
||||||
MOSAIC_GIT_IDENTITY=no-such-agent \
|
MOSAIC_GIT_IDENTITY=no-such-agent \
|
||||||
bash "$HELPER" get <<EOF >/dev/null 2>&1
|
"$HELPER" get <<EOF >/dev/null 2>&1
|
||||||
host=git.mosaicstack.dev
|
host=git.mosaicstack.dev
|
||||||
username=no-such-agent
|
username=no-such-agent
|
||||||
|
|
||||||
@@ -349,7 +429,7 @@ nospool_err=$(
|
|||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$unwritable_spool" \
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$unwritable_spool" \
|
||||||
MOSAIC_GIT_IDENTITY=no-such-agent \
|
MOSAIC_GIT_IDENTITY=no-such-agent \
|
||||||
bash "$HELPER" get <<EOF 2>&1 >/dev/null
|
"$HELPER" get <<EOF 2>&1 >/dev/null
|
||||||
host=git.mosaicstack.dev
|
host=git.mosaicstack.dev
|
||||||
username=no-such-agent
|
username=no-such-agent
|
||||||
|
|
||||||
@@ -365,6 +445,193 @@ if [[ "$nospool_err" != *"NOT WRITTEN"* ]]; then
|
|||||||
fail=1
|
fail=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 11. P5-RM-006 (+r1 rework) — caller identity from exec-frozen ancestry.
|
||||||
|
# A seat caller is established by lineage, not by the helper's own env;
|
||||||
|
# disagreement anywhere in the lineage is a rewrite and refuses; an
|
||||||
|
# anonymous caller resolves NOTHING on a fleet host (seat or service);
|
||||||
|
# a loose seat-slot mode refuses. Enforcement-removal red control at 11h.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
mkdir -p "$BRAIN_DIR/fleet/agents/seatG/secrets"
|
||||||
|
echo -n "seatG-slot-token" > "$BRAIN_DIR/fleet/agents/seatG/secrets/gitea-mosaicstack-seatG.token"
|
||||||
|
chmod 600 "$BRAIN_DIR/fleet/agents/seatG/secrets/gitea-mosaicstack-seatG.token"
|
||||||
|
|
||||||
|
# 11a. Cross-seat negative (lineage seatE, env ident=seatG): still refused.
|
||||||
|
assert_refused_lineage "seat cannot override identity to another seat's slot" \
|
||||||
|
seatE cross-seat-identity-refused MOSAIC_GIT_IDENTITY=seatG
|
||||||
|
|
||||||
|
# 11b. Seat asking for a SERVICE identity: cross-seat territory.
|
||||||
|
assert_refused_lineage "seat cannot resolve a service identity either" \
|
||||||
|
seatE cross-seat-identity-refused MOSAIC_GIT_IDENTITY=agentA
|
||||||
|
|
||||||
|
# 11c. Anonymous caller asking for a SEAT slot: refused (T94 jarvis@ class).
|
||||||
|
assert_refused_lineage "anonymous caller cannot resolve a seat slot on a fleet host" \
|
||||||
|
"" anonymous-credential-refused MOSAIC_GIT_IDENTITY=seatG
|
||||||
|
|
||||||
|
# 11d. Anonymous caller asking for a SERVICE identity: ALSO refused
|
||||||
|
# (rev-code-02 F2 — credentialed services are seats; the legacy store is
|
||||||
|
# not anonymously reachable on fleet hosts).
|
||||||
|
assert_refused_lineage "anonymous caller cannot resolve a legacy service credential either" \
|
||||||
|
"" anonymous-credential-refused MOSAIC_GIT_IDENTITY=agentA
|
||||||
|
|
||||||
|
# 11e. Slot permissions: a group-readable slot is refused; mode restored -> serves.
|
||||||
|
chmod 644 "$BRAIN_DIR/fleet/agents/seatG/secrets/gitea-mosaicstack-seatG.token"
|
||||||
|
assert_refused_lineage "loose slot mode is refused" \
|
||||||
|
seatG slot-permission-violation MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG
|
||||||
|
chmod 600 "$BRAIN_DIR/fleet/agents/seatG/secrets/gitea-mosaicstack-seatG.token"
|
||||||
|
out=$(run_lineage seatG MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG)
|
||||||
|
assert_eq "mode restored to 600: seatG serves again" "password=seatG-slot-token" "$(echo "$out" | grep '^password=')"
|
||||||
|
|
||||||
|
# 11f. rev-code-02 F1 repro: dual-variable override (caller lineage seatE,
|
||||||
|
# helper env carrying MOSAIC_AGENT_NAME=seatG AND MOSAIC_GIT_IDENTITY=seatG).
|
||||||
|
assert_refused_lineage "F1: dual MOSAIC_AGENT_NAME+MOSAIC_GIT_IDENTITY override refused" \
|
||||||
|
seatE caller-identity-spoof-refused MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG
|
||||||
|
|
||||||
|
# 11g. Stripped lineage still serves the rightful seat: caller frozen at the
|
||||||
|
# ancestor, helper env clean (self empty), own ident.
|
||||||
|
out=$(run_lineage seatE MOSAIC_GIT_IDENTITY=seatE)
|
||||||
|
assert_eq "lineage consensus with stripped self still serves the owning seat" \
|
||||||
|
"password=seatE-slot-token" "$(echo "$out" | grep '^password=')"
|
||||||
|
|
||||||
|
# 11h. RED CONTROL: delete the ancestry binding between markers from a copy
|
||||||
|
# of the IMPLEMENTATION (run directly with the clean marker — a red control
|
||||||
|
# measures the binding itself, deliberately not through the wrapper);
|
||||||
|
# the F1 dual-override request must then RESOLVE seatG's token — the
|
||||||
|
# exact measured failure — proving the binding is the enforcement.
|
||||||
|
RED_HELPER="$WORK_DIR/red/git-credential-mosaic.impl"
|
||||||
|
mkdir -p "$WORK_DIR/red"
|
||||||
|
sed '/P5-RM-006r1 ancestry binding begin/,/P5-RM-006r1 ancestry binding end/d' "$IMPL" > "$RED_HELPER"
|
||||||
|
chmod +x "$RED_HELPER"
|
||||||
|
if cmp -s "$IMPL" "$RED_HELPER"; then
|
||||||
|
echo "FAIL: red control is vacuous — marker deletion removed nothing" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
set +e
|
||||||
|
red_out=$(printf 'host=git.mosaicstack.dev\nusername=probe\n\n' | \
|
||||||
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_LINEAGE_FENCE=1 \
|
||||||
|
bash "$WORK_DIR/lineage-root.sh" seatE "$WORK_DIR/lineage-carrier.sh" \
|
||||||
|
"$RED_HELPER" "$SPOOL_DIR" "$BRAIN_DIR" "$REPO_DIR" \
|
||||||
|
_MOSAIC_HELPER_CLEAN=1 MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG 2>/dev/null)
|
||||||
|
set -e
|
||||||
|
if [[ "$(echo "$red_out" | grep '^password=')" != "password=seatG-slot-token" ]]; then
|
||||||
|
echo "FAIL: red control — with the binding removed, the F1 dual-override should have resolved seatG's token, got: $red_out" >&2
|
||||||
|
fail=1
|
||||||
|
else
|
||||||
|
echo "ok: red control — binding removed -> F1 dual-override resolves the victim token (the binding is the enforcement)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 11h2. rev-code-02 R1 F1: PATH-shadowed tr/sed/head/grep must not forge the
|
||||||
|
# ancestry. The dual-override request runs with a hostile PATH whose
|
||||||
|
# utilities claim the victim name for every /proc read; the walker uses
|
||||||
|
# only bash builtins, so the shadows never execute and the refusal holds.
|
||||||
|
HOSTILE_BIN="$WORK_DIR/hostile-bin"
|
||||||
|
mkdir -p "$HOSTILE_BIN"
|
||||||
|
for tool in tr sed head grep cat stat; do
|
||||||
|
printf '#!/usr/bin/env bash\ncat >/dev/null\necho "MOSAIC_AGENT_NAME=seatG"\nexit 0\n' > "$HOSTILE_BIN/$tool"
|
||||||
|
chmod +x "$HOSTILE_BIN/$tool"
|
||||||
|
done
|
||||||
|
assert_refused_lineage "F1-R1: hostile PATH utilities cannot forge ancestry (dual override still refused)" \
|
||||||
|
seatE caller-identity-spoof-refused \
|
||||||
|
MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG PATH="$HOSTILE_BIN:$PATH"
|
||||||
|
|
||||||
|
# 11i. Service automation integration arm (rev-code-02 R2 bar): a SERVICE
|
||||||
|
# seat bound by lineage resolves its own slot — the brain-git-sync shape.
|
||||||
|
mkdir -p "$BRAIN_DIR/fleet/agents/svc-fixture/secrets"
|
||||||
|
echo -n "svc-fixture-slot-token" > "$BRAIN_DIR/fleet/agents/svc-fixture/secrets/gitea-mosaicstack-svc-fixture.token"
|
||||||
|
chmod 600 "$BRAIN_DIR/fleet/agents/svc-fixture/secrets/gitea-mosaicstack-svc-fixture.token"
|
||||||
|
out=$(run_lineage svc-fixture MOSAIC_AGENT_NAME=svc-fixture MOSAIC_GIT_IDENTITY=svc-fixture)
|
||||||
|
assert_eq "service automation with bound seat lineage resolves its own slot" \
|
||||||
|
"password=svc-fixture-slot-token" "$(echo "$out" | grep '^password=')"
|
||||||
|
|
||||||
|
# 11j. rev-code-02 R3 B1: BASH_ENV shaping. A read() shadow defined through
|
||||||
|
# BASH_ENV must be refused before any resolution — the guard scrubs and
|
||||||
|
# refuses with bash-environment-injection-refused.
|
||||||
|
INJ_SH="$WORK_DIR/inj-read.sh"
|
||||||
|
printf 'read() { builtin read -r _x || return 0; printf "MOSAIC_AGENT_NAME=seatG\\n"; return 0; }\n' > "$INJ_SH"
|
||||||
|
assert_refused_lineage "F1-R3: BASH_ENV read() shadow is dropped at the wrapper boundary (identity gate governs)" \
|
||||||
|
seatE caller-identity-spoof-refused \
|
||||||
|
MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG BASH_ENV="$INJ_SH"
|
||||||
|
|
||||||
|
# 11k. rev-code-02 R3 B1: exported functions (BASH_FUNC_* import) refused too.
|
||||||
|
assert_refused_lineage "F1-R3: exported BASH_FUNC_* import never crosses the wrapper boundary (identity gate governs)" \
|
||||||
|
seatE caller-identity-spoof-refused \
|
||||||
|
MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG \
|
||||||
|
'BASH_FUNC_read%%=() { builtin read -r _x || return 0; printf "MOSAIC_AGENT_NAME=seatG\\n"; return 0; }'
|
||||||
|
|
||||||
|
# 11l. rev-code-02 R3 B2: hostile stat cannot launder a loose slot. Own-slot
|
||||||
|
# lineage (legit caller), 0644 slot, PATH-shadowed stat reporting 600 —
|
||||||
|
# mode inspection must come from the trusted PATH and still refuse.
|
||||||
|
chmod 644 "$BRAIN_DIR/fleet/agents/svc-fixture/secrets/gitea-mosaicstack-svc-fixture.token"
|
||||||
|
printf '#!/usr/bin/env bash\necho 600\n' > "$HOSTILE_BIN/stat"
|
||||||
|
assert_refused_lineage "F2-R3: hostile stat cannot make a 0644 slot pass as 600 (own-slot path)" \
|
||||||
|
svc-fixture slot-permission-violation \
|
||||||
|
MOSAIC_AGENT_NAME=svc-fixture MOSAIC_GIT_IDENTITY=svc-fixture PATH="$HOSTILE_BIN:$PATH"
|
||||||
|
chmod 600 "$BRAIN_DIR/fleet/agents/svc-fixture/secrets/gitea-mosaicstack-svc-fixture.token"
|
||||||
|
|
||||||
|
# 11m. rev-code-02 R3 probe 1 through the PRODUCTION ENTRYPOINT: BASH_ENV
|
||||||
|
# defines unset()/exit() no-ops (defeating in-bash scrub/termination).
|
||||||
|
# The python wrapper never passes BASH_ENV across the boundary, so the
|
||||||
|
# implementation cannot be shaped and the dual override still refuses.
|
||||||
|
INJ_P1="$WORK_DIR/inj-probe1.sh"
|
||||||
|
cat > "$INJ_P1" <<'P1'
|
||||||
|
unset() { return 0; }
|
||||||
|
exit() { return 0; }
|
||||||
|
read() { builtin read -r _x || return 0; printf 'MOSAIC_AGENT_NAME=seatG\n'; return 0; }
|
||||||
|
P1
|
||||||
|
assert_refused_lineage "F1-R4 probe1: BASH_ENV unset/exit no-ops cannot shape the helper (wrapper boundary)" \
|
||||||
|
seatE caller-identity-spoof-refused \
|
||||||
|
MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG BASH_ENV="$INJ_P1"
|
||||||
|
|
||||||
|
# 11n. rev-code-02 R3 probe 2 through the PRODUCTION ENTRYPOINT: declare()
|
||||||
|
# hides imported functions, unsets the marker, printf() forges the
|
||||||
|
# ancestry. Dropped at the wrapper boundary; refusal holds.
|
||||||
|
INJ_P2="$WORK_DIR/inj-probe2.sh"
|
||||||
|
cat > "$INJ_P2" <<'P2'
|
||||||
|
declare() { return 0; }
|
||||||
|
printf() { builtin printf '%s' "MOSAIC_AGENT_NAME=seatG"; return 0; }
|
||||||
|
read() { builtin read -r _x || return 0; printf 'MOSAIC_AGENT_NAME=seatG\n'; return 0; }
|
||||||
|
P2
|
||||||
|
assert_refused_lineage "F1-R4 probe2: declare-hide + printf-forge cannot shape the helper (wrapper boundary)" \
|
||||||
|
seatE caller-identity-spoof-refused \
|
||||||
|
MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG BASH_ENV="$INJ_P2"
|
||||||
|
|
||||||
|
# 11o. RED CONTROL for the wrapper boundary (enforcement-removal): invoke the
|
||||||
|
# IMPLEMENTATION directly, bypassing the wrapper, with the PROBE-1 shape
|
||||||
|
# (unset/exit no-ops) and a forged clean marker — exactly the falsified
|
||||||
|
# in-bash world the reviewer measured: the refusal prints, exit is
|
||||||
|
# no-oped, execution continues, and the forged ancestry SERVES seatG.
|
||||||
|
# The wrapper boundary is the enforcement; this arm proves it bites.
|
||||||
|
set +e
|
||||||
|
bypass_out=$(cd "$REPO_DIR" && printf 'host=git.mosaicstack.dev\nusername=probe\n\n' | \
|
||||||
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$SPOOL_DIR" \
|
||||||
|
MOSAIC_BRAIN_HOME="$BRAIN_DIR" _MOSAIC_HELPER_CLEAN=1 BASH_ENV="$INJ_P1" \
|
||||||
|
MOSAIC_AGENT_NAME=seatG MOSAIC_GIT_IDENTITY=seatG \
|
||||||
|
bash "$IMPL" get 2>/dev/null)
|
||||||
|
set -e
|
||||||
|
if [[ "$(echo "$bypass_out" | grep -c '^password=')" -lt 1 ]]; then
|
||||||
|
echo "FAIL: wrapper red control — direct shaped .impl should have served (wrapper is the enforcement), got: $bypass_out" >&2
|
||||||
|
fail=1
|
||||||
|
else
|
||||||
|
echo "ok: red control — wrapper bypassed + probe1 shape serves (the wrapper boundary is the enforcement)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 11p. Direct .impl invocation WITHOUT the clean marker: refused by the
|
||||||
|
# implementation's own entrypoint assert.
|
||||||
|
set +e
|
||||||
|
direct_out=$(cd "$REPO_DIR" && printf 'host=git.mosaicstack.dev\nusername=probe\n\n' | \
|
||||||
|
env -i HOME="$FAKE_HOME" PATH="$PATH" MOSAIC_CREDENTIAL_SPOOL="$SPOOL_DIR" \
|
||||||
|
MOSAIC_BRAIN_HOME="$BRAIN_DIR" MOSAIC_GIT_IDENTITY=seatE \
|
||||||
|
bash "$IMPL" get 2>"$WORK_DIR/stderr-direct.tmp")
|
||||||
|
direct_rc=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$direct_rc" -eq 0 || -n "$direct_out" ]]; then
|
||||||
|
echo "FAIL: direct .impl without marker must refuse (got rc=$direct_rc out='$direct_out')" >&2
|
||||||
|
fail=1
|
||||||
|
elif ! grep -q 'direct-entrypoint-refused' "$WORK_DIR/stderr-direct.tmp"; then
|
||||||
|
echo "FAIL: direct .impl refusal lacks direct-entrypoint-refused" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$fail" -eq 0 ]]; then
|
if [[ "$fail" -eq 0 ]]; then
|
||||||
echo "git-credential-mosaic identity resolution regression passed"
|
echo "git-credential-mosaic identity resolution regression passed"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user