Files
CASAN/AINative_OKR_CASAN5/.specify/scripts/bash/ci-harness-gate.sh
T
thanhnvandClaude Opus 4.8 8c06a55aed feat: plan 16 P2 batch 1 (SEC-13 SSRF, SEC-27 log-escape, SEC-28 path-traversal)
- SEC-13 (M-09): SSRF allowlist on provider-usage-fetch (always block non-http(s)
  schemes; enforced mode blocks internal/link-local IPs + non-allowlisted hosts,
  dev keeps loopback mocks); dashboard refuses non-loopback bind in enforced mode.
- SEC-27 (X-02): casan-log strips ESC/CSI + CR/LF (terminal-escape + fake-log-line
  injection) while keeping tab and visible text.
- SEC-28 (X-04): new path-guard.sh — realpath resolve + reject symlink/.. escapes
  outside the allowed root.

Verify: SEC-13 6/0, SEC-27 3/0, SEC-28 4/0, adversarial 44/44, run-casan4 0-FAIL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:39:18 +09:00

149 lines
5.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -uo pipefail
# CASAN CI harness gate.
# Runs the reproducible harness suites in a safe order. `run-casan4` must run
# first because it rewrites `.specify/logs`.
#
# Env:
# CASAN_CI_RUN_FRONTEND=0|1 default 1
# CASAN_CI_RUN_INFRA_LAB=0|1 default 0 (Docker Compose lab is optional in CI)
# CASAN_CI_STEP_TIMEOUT_SEC default 600
# CASAN_CI_SUITE_FILTER optional regex; run matching suite names only
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
TESTS="$ROOT/.specify/tests"
PASS=0
FAIL=0
SKIP=0
run() {
local name="$1"
shift
if [[ -n "${CASAN_CI_SUITE_FILTER:-}" && ! "$name" =~ $CASAN_CI_SUITE_FILTER ]]; then
skip "$name (filtered)"
return 0
fi
echo "==> $name"
if run_with_timeout "$@"; then
echo "CI_GATE_PASS $name"
PASS=$((PASS + 1))
else
echo "CI_GATE_FAIL $name" >&2
FAIL=$((FAIL + 1))
fi
}
skip() {
echo "CI_GATE_SKIP $1"
SKIP=$((SKIP + 1))
}
run_with_timeout() {
if ! command -v python3 >/dev/null 2>&1; then
"$@"
return $?
fi
CASAN_CI_STEP_TIMEOUT_SEC="${CASAN_CI_STEP_TIMEOUT_SEC:-600}" python3 - "$@" <<'PY'
import os
import subprocess
import sys
timeout = int(os.environ.get("CASAN_CI_STEP_TIMEOUT_SEC", "600"))
cmd = sys.argv[1:]
try:
raise SystemExit(subprocess.run(cmd, timeout=timeout).returncode)
except subprocess.TimeoutExpired:
print(f"CI_GATE_TIMEOUT seconds={timeout} command={' '.join(cmd)}", file=sys.stderr)
raise SystemExit(124)
PY
}
cd "$ROOT" || exit 1
run "run-casan4-harness" bash "$TESTS/run-casan4-harness-tests.sh"
run "adversarial-harness" bash "$TESTS/adversarial-harness-tests.sh"
run "phase1-track-a" bash "$TESTS/phase1-track-a-tests.sh"
run "phase2-track-c" bash "$TESTS/phase2-track-c-tests.sh"
run "phase2-sourcegen" bash "$TESTS/phase2-sourcegen-tests.sh"
run "phase3-evidence-pack" bash "$TESTS/phase3-evidence-pack-tests.sh"
run "phase3-model-router" bash "$TESTS/phase3-model-router-tests.sh"
run "phase-h5-approval" bash "$TESTS/phase-h5-approval-tests.sh"
run "phase-h5-infra" bash "$TESTS/phase-h5-infra-tests.sh"
run "phase-h6-agentops" bash "$TESTS/phase-h6-agentops-tests.sh"
run "phase-c7-incident" bash "$TESTS/phase-c7-incident-tests.sh"
run "phase-h4-multilingual" bash "$TESTS/phase-h4-multilingual-tests.sh"
run "phase-c6-sandbox" bash "$TESTS/phase-c6-sandbox-tests.sh"
run "phase-h4-split-inject" bash "$TESTS/phase-h4-split-inject-tests.sh"
run "phase10-traceability" bash "$TESTS/phase10-traceability-tests.sh"
run "phase08-compression" bash "$TESTS/phase08-compression-tests.sh"
run "phase-control-plane" bash "$TESTS/phase-control-plane-tests.sh"
run "phase-rbac" bash "$TESTS/phase-rbac-tests.sh"
run "phase-rai" bash "$TESTS/phase-rai-tests.sh"
run "phase-selfimprove" bash "$TESTS/phase-selfimprove-tests.sh"
run "phase-governance-report" bash "$TESTS/phase-governance-report-tests.sh"
run "phase-preflight" bash "$TESTS/phase-preflight-tests.sh"
# Plan-16 security-audit remediation (P0) — each control has a fail-able test.
run "phase-sec01-unsigned-fail" bash "$TESTS/phase-sec01-tests.sh"
run "phase-sec02-no-local-key" bash "$TESTS/phase-sec02-tests.sh"
run "phase-sec03-rollback-rce" bash "$TESTS/phase-sec03-tests.sh"
run "phase-sec04-action-gate" bash "$TESTS/phase-sec04-tests.sh"
run "phase-sec05-json-safe" bash "$TESTS/phase-sec05-tests.sh"
run "phase-sec06-cp-signed" bash "$TESTS/phase-sec06-tests.sh"
run "phase-sec16-bundle" bash "$TESTS/phase-sec16-tests.sh"
run "phase-sec17-prod-profile" bash "$TESTS/phase-sec17-tests.sh"
run "phase-sec18-test-integrity" bash "$TESTS/phase-sec18-tests.sh"
# Plan-16 P1 (authz / fail-open / DoS)
run "phase-sec08-pii-failclosed" bash "$TESTS/phase-sec08-tests.sh"
run "phase-sec09-input-caps" bash "$TESTS/phase-sec09-tests.sh"
run "phase-sec19-atomic-store" bash "$TESTS/phase-sec19-tests.sh"
run "phase-sec20-toolchain" bash "$TESTS/phase-sec20-tests.sh"
run "phase-sec21-model-budget" bash "$TESTS/phase-sec21-tests.sh"
run "phase-sec07-approval" bash "$TESTS/phase-sec07-tests.sh"
run "phase-sec10-agent-identity" bash "$TESTS/phase-sec10-tests.sh"
# Plan-16 P2 (depth / hardening)
run "phase-sec13-ssrf" bash "$TESTS/phase-sec13-tests.sh"
run "phase-sec27-log-controlchar" bash "$TESTS/phase-sec27-tests.sh"
run "phase-sec28-path-traversal" bash "$TESTS/phase-sec28-tests.sh"
# ARCH-02: coverage cannot silently drop; ARCH-01: harness/policy cannot silently
# drift. Both SKIP cleanly when no manifest is provisioned (non-strict dev/CI).
run "test-integrity" python3 "$SCRIPT_DIR/test-integrity.py" verify
run "bundle-integrity" python3 "$SCRIPT_DIR/bundle-integrity.py" verify
if [[ "${CASAN_CI_RUN_BACKEND:-1}" == "1" ]]; then
if command -v npm >/dev/null 2>&1; then
run "backend-tests" npm test -w backend
else
skip "backend-tests (npm unavailable)"
fi
else
skip "backend-tests (CASAN_CI_RUN_BACKEND=0)"
fi
if [[ "${CASAN_CI_RUN_FRONTEND:-1}" == "1" ]]; then
if command -v npm >/dev/null 2>&1; then
run "frontend-vitest" npm test -w frontend
else
skip "frontend-vitest (npm unavailable)"
fi
else
skip "frontend-vitest (CASAN_CI_RUN_FRONTEND=0)"
fi
if [[ "${CASAN_CI_RUN_INFRA_LAB:-0}" == "1" ]]; then
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
run "local-prod-infra-lab" bash "$TESTS/phase-prod-infra-lab-tests.sh"
else
skip "local-prod-infra-lab (docker compose unavailable)"
fi
else
skip "local-prod-infra-lab (CASAN_CI_RUN_INFRA_LAB=0)"
fi
echo "CI_GATE_SUMMARY PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
[[ "$FAIL" -eq 0 ]] || exit 1