#!/usr/bin/env bash # End-to-end endpoint smoke test against a running substrate-api. # # Usage: BASE=http://localhost:3100 KEY= ./run-endpoint-checks.sh # # Endpoint checks for auth, rate limits, redaction, install validation, # proxy-token auth, path blocklists, and CORS. # # Exits non-zero on any unexpected status. set -uo pipefail BASE="${BASE:-http://localhost:3100}" KEY="${KEY:?KEY env required}" FAILS=0 hdr() { printf '\n=== %s ===\n' "$1"; } pass() { printf ' PASS: %s\n' "$1"; } fail() { printf ' FAIL: %s\n' "$1"; FAILS=$((FAILS+1)); } expect_status() { local desc="$1" expected="$2" url="$3"; shift 3 local code code=$(curl -s -o /tmp/resp.body -w '%{http_code}' "$@" "$url" || echo 000) if [ "$code" = "$expected" ]; then pass "$desc (got $code)" else fail "$desc: expected $expected got $code" head -c 300 /tmp/resp.body; echo fi } expect_body_contains() { local desc="$1" needle="$2" if grep -q -- "$needle" /tmp/resp.body; then pass "$desc body contains '$needle'" else fail "$desc body missing '$needle'" fi } expect_body_absent() { local desc="$1" needle="$2" if grep -q -- "$needle" /tmp/resp.body; then fail "$desc body should not contain '$needle'" head -c 300 /tmp/resp.body; echo else pass "$desc body absent of '$needle'" fi } hdr "Health" expect_status "GET /health unauth ok" 200 "$BASE/health" expect_status "GET /ready returns 200/503" 200 "$BASE/ready" || expect_status "GET /ready returns 503" 503 "$BASE/ready" hdr "Auth" expect_status "missing key 401" 401 "$BASE/services" expect_status "wrong key 401" 401 "$BASE/services" -H "x-substrate-key: wrong" expect_status "correct key 200" 200 "$BASE/services" -H "x-substrate-key: $KEY" hdr "Body size limit (1 MiB cap)" # Send a 2 MiB body via POST to a JSON endpoint; must get 413 dd if=/dev/zero of=/tmp/big-body bs=1024 count=2048 status=none expect_status "2 MiB payload rejected" 413 "$BASE/llm/chat" \ -X POST \ -H "x-substrate-key: $KEY" \ -H "content-type: application/json" \ --data-binary @/tmp/big-body hdr "Workspace install validation" # Workspace is disabled in our test deploy, so validation either returns 400 # for invalid input or 403 for feature-gated. Both prove the validation layer. expect_status "newline injection rejected" 400 "$BASE/workspace/install" \ -X POST \ -H "x-substrate-key: $KEY" \ -H "content-type: application/json" \ --data '{"packages":["pkg\nrm -rf /"],"manager":"npm"}' \ || expect_status "newline injection rejected (feature-gated)" 403 "$BASE/workspace/install" \ -X POST \ -H "x-substrate-key: $KEY" \ -H "content-type: application/json" \ --data '{"packages":["pkg\nrm -rf /"],"manager":"npm"}' expect_status "leading-dash flag rejected" 400 "$BASE/workspace/install" \ -X POST \ -H "x-substrate-key: $KEY" \ -H "content-type: application/json" \ --data '{"packages":["--registry=http://evil"],"manager":"npm"}' \ || expect_status "leading-dash flag rejected (feature-gated)" 403 "$BASE/workspace/install" \ -X POST \ -H "x-substrate-key: $KEY" \ -H "content-type: application/json" \ --data '{"packages":["--registry=http://evil"],"manager":"npm"}' hdr "System fs read blocklist" expect_status "id_rsa refused" 403 "$BASE/fs/read?path=/tmp/id_rsa" -H "x-substrate-key: $KEY" expect_status ".env refused" 403 "$BASE/fs/read?path=/tmp/.env" -H "x-substrate-key: $KEY" expect_status "outside-root refused" 403 "$BASE/fs/read?path=/etc/passwd" -H "x-substrate-key: $KEY" hdr "CORS origin check" # Origin allowed expect_status "local origin allowed" 204 "$BASE/services" \ -X OPTIONS \ -H "Origin: http://localhost:5173" \ -H "Access-Control-Request-Method: GET" # Origin disallowed — preflight should 204 but Access-Control-Allow-Origin header missing curl -s -o /dev/null -D /tmp/resp.headers \ -X OPTIONS \ -H "Origin: https://evil.example" \ -H "Access-Control-Request-Method: GET" \ "$BASE/services" if grep -qi "access-control-allow-origin: https://evil.example" /tmp/resp.headers; then fail "evil origin reflected into ACAO" else pass "evil origin not reflected" fi hdr "LLM error redaction" # No LLM provider configured → listModels returns a sane error. # We can at least confirm the response doesn't echo back sk- pattern. curl -s "$BASE/llm/models" -H "x-substrate-key: $KEY" > /tmp/resp.body if grep -qE 'sk-[A-Za-z0-9]{20,}' /tmp/resp.body; then fail "/llm/models response appears to contain an API key" else pass "/llm/models response has no sk- patterns" fi hdr "Summary" if [ "$FAILS" -eq 0 ]; then printf '\nAll checks passed.\n' exit 0 else printf '\n%d FAILURE(S).\n' "$FAILS" exit 1 fi