test-curl.sh predates vitest and still claimed to be the authoritative suite
for one endpoint. It is now the socket-level pass, and its header says so:
vitest covers the logic, this script covers what only a real client and a
real TCP connection can show.
Twenty-four cases for POST /hosts/<id> and GET /hosts, chosen for what they
prove rather than for coverage: the 413 answered before the connection is
cut, the two tokens refusing each other's routes in both directions, 401
landing ahead of 403 so an unauthenticated caller cannot probe the allowlist,
the route regex turning a malformed id and a traversal attempt into 404
before any handler runs, and the ingest-persist-read round trip closing on a
GET that shows the pushed hostname online.
Two cases needed care to be worth anything:
The hostile payload splices "__proto__" into the JSON as text. Written as
__proto__: in an object literal it would set the prototype and JSON.stringify
would emit nothing, leaving the case asserting against a payload that never
carried the key. It now checks the response and the persisted file, whose key
set must be exactly the whitelist.
The 413 case runs under set -e while the server destroys the socket right
after flushing the response, so curl can exit 55/56 having already read the
status line. Every probe goes through an http_code helper that absorbs that;
a curl which truly got nothing reports 000, which fails the case rather than
passing it quietly. -H "Expect:" also suppresses the 100-continue handshake,
which otherwise changes which side notices the reset first.
The fail-closed 503 needs a server booted without HOSTS_INGEST_TOKEN, so the
script now runs a second instance on 3098 for it, and confirms reads still
answer 200 there — the two tokens are independent, including in absence.
The EXIT trap dereferenced an unset SERVER_PID under set -u, which made it
error out instead of cleaning up when anything failed before the boot. Both
PIDs are initialised and the kills guarded.
CLAUDE.md: the vitest count was stale (244 -> 251, auth.test.js 19 -> 26) and
test-curl.sh was undocumented.
Refs #16
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sergent renameSync() rotates reports/ -> reports/archive/ at 07:30 UTC daily,
so for ~22h per day the only copy of a fresh scan lives in archive/. The
handler now scans both directories and concatenates with top-level priority
on filename collision. archive/ missing is a silent skip.
Tests : 17/17 in test-curl.sh (11 existing + 6 new for archive coverage).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the SSH/rsync canal between Max's workstation cron and the VPS
for fetching defenseur scan reports. The defenseur-auto orchestrator now
pulls reports/defenseur-X_<date>*.json over HTTPS, reusing HEALTH_TOKEN.
The handler mirrors the style of index.js (HTTP native, no framework),
includes the same isScanReport guard as defenseurs/src/report.ts (filters
out defenseur-auto_*.json run reports), and validates the date param
against /^\d{4}-\d{2}-\d{2}$/ to short-circuit path traversal before any
filesystem access.
Validated by test-curl.sh — 11 cases covering auth, validation, date
filter, isScanReport filter, sort order, GET-only and 404 paths.
Spike: ~/claude-code/.spikes/archived/endpoint-reports-sur-vps-health-api-pour/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>