#!/usr/bin/env bash set -uo pipefail # CASAN Plan-16 SEC-14 (M-03 / SC-03) — model-digest env-override is NOT trusted # in enforced mode. # # current_digest() honored CASAN_MODEL_DIGEST unconditionally, so an attacker who # swapped the local model could also set CASAN_MODEL_DIGEST to the pinned value # and pass verification. Under CASAN_PROFILE=prod (or CASAN_MODEL_DIGEST_STRICT=1) # the override is ignored and the digest must come from the live model backend. # This proves: # * dev/CI: override still honored (pin + verify deterministic) — regression, # * dev/CI: a swapped override is a MISMATCH (control still works), # * prod: a forged override equal to the pin is NOT accepted (fail-closed), # * strict flag: same enforced behavior without a full prod profile. # # Deterministic; hermetic (temp pin file; live backend pointed at a dead port). SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" MDC="$PROJECT_ROOT/.specify/scripts/bash/model-digest-check.sh" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT PIN="$WORK/model-digest.pin" DEAD_OLLAMA="127.0.0.1:1" # unreachable → live digest fetch fails deterministically PASS=0; FAIL=0 pass() { echo "PASS: $1"; PASS=$((PASS + 1)); } fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); } echo "===== Plan-16 SEC-14: model-digest env-override not trusted in enforced mode =====" # Setup: pin an approved digest in dev mode (override honored). CASAN_MODEL_DIGEST_PIN="$PIN" CASAN_MODEL_DIGEST="digest-approved" \ bash "$MDC" pin ornith:9b >/dev/null 2>&1 # 1) dev/CI regression: matching override verifies OK. rc=$(set +e; CASAN_MODEL_DIGEST_PIN="$PIN" CASAN_MODEL_DIGEST="digest-approved" \ bash "$MDC" verify ornith:9b >/dev/null 2>&1; echo $?) [[ "$rc" -eq 0 ]] && pass "dev: matching override verifies OK (regression)" \ || fail "dev: matching override should verify OK (rc=$rc)" # 2) dev/CI regression: swapped override is a MISMATCH (block). rc=$(set +e; CASAN_MODEL_DIGEST_PIN="$PIN" CASAN_MODEL_DIGEST="digest-swapped" \ bash "$MDC" verify ornith:9b >/dev/null 2>&1; echo $?) [[ "$rc" -eq 2 ]] && pass "dev: swapped override detected as MISMATCH (rc=2)" \ || fail "dev: swapped override should MISMATCH (rc=$rc)" # 3) SEC-14 core (prod): a FORGED override equal to the pin must NOT be accepted. # Override is ignored; live backend is unreachable → cannot verify (rc=3), never OK. err="$WORK/prod.err" rc=$(set +e; CASAN_PROFILE=prod OLLAMA_HOST="$DEAD_OLLAMA" \ CASAN_MODEL_DIGEST_PIN="$PIN" CASAN_MODEL_DIGEST="digest-approved" \ bash "$MDC" verify ornith:9b >/dev/null 2>"$err"; echo $?) [[ "$rc" -ne 0 ]] && pass "prod: forged override not accepted as OK (rc=$rc)" \ || fail "prod: forged override was accepted (rc=0) — env override still trusted" grep -q "MODEL_DIGEST_OVERRIDE_IGNORED" "$err" \ && pass "prod: override explicitly ignored (MODEL_DIGEST_OVERRIDE_IGNORED)" \ || fail "prod: override was not ignored (no OVERRIDE_IGNORED marker)" # 4) strict flag: same enforced behavior without a full prod profile. rc=$(set +e; CASAN_MODEL_DIGEST_STRICT=1 OLLAMA_HOST="$DEAD_OLLAMA" \ CASAN_MODEL_DIGEST_PIN="$PIN" CASAN_MODEL_DIGEST="digest-approved" \ bash "$MDC" verify ornith:9b >/dev/null 2>&1; echo $?) [[ "$rc" -ne 0 ]] && pass "strict: forged override not accepted (rc=$rc)" \ || fail "strict: forged override accepted (rc=0)" echo "" echo "===== SEC-14 SUMMARY: PASS=$PASS FAIL=$FAIL =====" [[ "$FAIL" -eq 0 ]] || exit 1