diff --git a/CLAUDE.md b/CLAUDE.md index 89a4c55..e0a0287 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,7 +67,7 @@ curl -H "Authorization: Bearer $(cat ~/.coolify-token)" \ - `__tests__/hosts.test.js` — matrice exhaustive des endpoints postes (155 cas : regex de route comme controle anti-traversee, 401 avant 403, cloisonnement des deux tokens, 503 fail-closed, reconstruction par liste blanche, 413, `neverSeen`, fraicheur serveur sous horloge figee, degradation de lecture) - `__tests__/agent.test.js` — agent poste (50 cas : contrat de payload verifie contre `sanitizeSnapshot()`, une seule tentative sans rejeu, timeout, codes de sortie, wrapper shell, et **le token absent de toute sortie d'echec, fragments de 5 caracteres compris**) - Runtime reste 0-dep ; vitest en devDep uniquement -- `bash test-curl.sh` — passe au niveau socket, complementaire et non redondante (41 cas). Vitest couvre la logique ; ce script couvre ce que seuls un vrai client et un vrai TCP peuvent montrer : le 413 rendu **avant** que la connexion soit coupee, les deux tokens qui se refusent mutuellement leurs routes, et le 503 d'une instance demarree sans `HOSTS_INGEST_TOKEN` (elle tourne sur un second port, le temps du script). Il boote ses propres serveurs sur 3099 et 3098 — aucun service exterieur requis. +- `bash test-curl.sh` — passe au niveau socket, complementaire et non redondante (45 cas). Vitest couvre la logique ; ce script couvre ce que seuls un vrai client et un vrai TCP peuvent montrer : le 413 rendu **avant** que la connexion soit coupee, les deux tokens qui se refusent mutuellement leurs routes, le fichier servi octet pour octet par `/defenseurs/state`, et le 503 d'une instance demarree sans `HOSTS_INGEST_TOKEN` (elle tourne sur un second port, le temps du script). Il boote ses propres serveurs sur 3099 et 3098 — aucun service exterieur requis. ## Gotchas diff --git a/test-curl.sh b/test-curl.sh index fee00a3..e9c0058 100755 --- a/test-curl.sh +++ b/test-curl.sh @@ -126,11 +126,18 @@ JSON # be writable — the same requirement the /data/hosts mount carries in prod. mkdir -p "$TMP_DIR/hosts" +# Sergent findings state, served verbatim by GET /defenseurs/state. The spacing +# is deliberately non-canonical: a server that parsed and re-serialised the file +# would not reproduce it byte for byte. +printf '%s\n' '{ "schemaVersion":1, "findings": {"a-deps-1": {"firstSeen":"2026-09-01T00:00:00.000Z"}},"history":[] }' \ + > "$TMP_DIR/findings-state.json" + # Boot the server with the temp REPORTS_DIR and HOSTS_DIR. PORT=3099 \ HEALTH_TOKEN="$TOKEN" \ REPORTS_DIR="$TMP_DIR/reports" \ HOSTS_DIR="$TMP_DIR/hosts" \ +DEFENSEURS_FINDINGS_STATE_PATH="$TMP_DIR/findings-state.json" \ HOSTS_ALLOWED_IDS="thinkpad,workbench" \ HOSTS_INGEST_TOKEN="$INGEST_TOKEN" \ LOGTO_HEALTH_URL="http://127.0.0.1:1/never" \ @@ -145,6 +152,7 @@ PORT=3098 \ HEALTH_TOKEN="$TOKEN" \ REPORTS_DIR="$TMP_DIR/reports" \ HOSTS_DIR="$TMP_DIR/hosts-noingest" \ +DEFENSEURS_FINDINGS_STATE_PATH="$TMP_DIR/absent-findings-state.json" \ HOSTS_ALLOWED_IDS="thinkpad" \ LOGTO_HEALTH_URL="http://127.0.0.1:1/never" \ node "$(dirname "$0")/index.js" >/dev/null 2>&1 & @@ -444,6 +452,24 @@ code=$(http_code -X POST -H "Authorization: Bearer $INGEST_TOKEN" -H "Content-Ty code=$(http_code -H "Authorization: Bearer $TOKEN" "$NOINGEST_URL/hosts") [[ "$code" == "200" ]] && pass "no HOSTS_INGEST_TOKEN -> GET /hosts still 200" || fail "read path on fail-closed instance -> got $code" +# Case 32 : GET /defenseurs/state serves findings-state.json byte for byte. +# Compared through files so the trailing newline is part of the comparison +# (command substitution would strip it). +http_body -H "Authorization: Bearer $TOKEN" "$BASE_URL/defenseurs/state" > "$TMP_DIR/state-served.json" +cmp -s "$TMP_DIR/state-served.json" "$TMP_DIR/findings-state.json" \ + && pass "GET /defenseurs/state -> file served byte for byte" \ + || fail "GET /defenseurs/state -> body differs from the file" +# Absent file -> 200 no_data, the same contract as /defenseurs. +body=$(http_body -H "Authorization: Bearer $TOKEN" "$NOINGEST_URL/defenseurs/state") +[[ "$body" == '{"status":"no_data"}' ]] \ + && pass "GET /defenseurs/state, file absent -> no_data" \ + || fail "GET /defenseurs/state absent -> $body" +code=$(http_code "$BASE_URL/defenseurs/state") +[[ "$code" == "401" ]] && pass "GET /defenseurs/state no-auth -> 401" || fail "GET /defenseurs/state no-auth -> got $code" +# A read route: the ingest token must not open it. +code=$(http_code -H "Authorization: Bearer $INGEST_TOKEN" "$BASE_URL/defenseurs/state") +[[ "$code" == "401" ]] && pass "GET /defenseurs/state with ingest token -> 401" || fail "GET /defenseurs/state ingest token -> got $code" + echo echo "=== Results: $PASS passed, $FAIL failed ===" [[ "$FAIL" == "0" ]] || exit 1