- routes/ca.js: declare format before pfx/pem/crt dispatch (was ReferenceError
on every request that passed validation); add CA_CERT_FORMATS single source
of truth + hardened format extraction (string-coerce, whitelist)
- routes/caddycode.js: fix upstream-validation regexes (control-char classes)
- routes/logs.js: SSE/validation lint fixes
- routes/openclaw.js: remove useless regex escape in ALLOWED_PATH_RE
- fleet-validation.js + http-caddy-admin-origin test: eslint-disable for
intentional control-regex security sentinels
- ca-dc076.routes.test.js: regression test for declared format + behavioral
coverage of format validation (now pre-PKI)
Judge: Qwen lane (qwen3.8-max) grade B, 0 blocking, verdict
/tmp/judge-batch1-verdict.json. API suite 2859/2859 green.
Two mirrored health-handler test suites (health-endpoints, health-probe-aliases)
probed the Caddy admin API with raw Origin-less native fetch. On the prod host
the adversarial cron runs the full jest suite every 30 min against a live Caddy
admin with enforce_origin: 12 journal 403 lines per run (~700/day of
'client is not allowed to access from origin' spam) while tests stayed green.
- Mirrors now call fetchT (byte-identical to src/app.js:930 probe) with fetchT
jest.spyOn-mocked at buildApp scope; caddyOk-configurable in both suites
- New guard test in utils-http-caddy-admin-origin.test.js: any __tests__ file
pairing a raw await-fetch with a Caddy-admin token (:2019|adminUrl|
CADDY_ADMIN) fails the suite — file-level pairing catches the historical
cross-line drift shape a call-window regex missed
- DC-087-ALLOW-RAW-FETCH comment escape hatch (raw-text marker, guard file
never self-exempts, skips logged to jest output)
Judge: GLM-5.3 cold read via delegate_task deleg_4d384dea (round 1 C -> round 2
B, zero blockers, polish folded). Verdict URN: urn:ump:azrv2xp72koiwi5r4yb6ureu4aqqloqq64sgmftsajh6ci2mzj2q
Mutation probes: historical drift reintroduction -> guard red; hatch marker ->
skipped+logged; restore -> 33/33. Full suite 2603/2603.
Fixes the recurring 403 spam in Caddy's admin API log:
{"error":"client is not allowed to access from origin ''","status_code":403}
from User-Agent:node + Sec-Fetch-Mode:cors at remote_ip=loopback, every ~10s
while the readiness workflow probes the Caddy admin endpoint for liveness.
Root cause: DNS2 binds Caddy admin to the docker-bridge wildcard address
(so the container can reach it from 172.17.0.1). Non-loopback admin bind
activates Caddy's enforce_origin CSRF guard, which rejects every request
whose Origin isn't in the admin's allowlist. Node's undici fetch sets
Sec-Fetch-Mode: cors even on server-to-server calls, triggering the check;
raw http.request sends no Origin at all, which also fails.
Fix: dashcaddy-api/src/utils/http.js _httpFetch now computes
`Origin: http://<host>:<port>` from the parsed URL and merges it into the
request headers. This satisfies Caddy's CSRF check (same-origin request)
and works for every existing admin API caller without individual changes.
Caller-provided Origin (via opts.headers) wins so future proxies / tests
can override.
Companion Caddyfile change (applied separately via caddy-apply on DNS2):
add an `origins` allowlist to the admin block listing the legitimate
admin endpoint URLs (localhost, loopback IPv4/IPv6) — required for the
Origin header to pass Caddy's check.
Tests: 5/5 passing (regression-proofed):
- http.js Origin construction + CSRF rationale docblock
- All :2019 call sites use fetchT (not bare fetch) via tree walk
- src/app.js readiness probe still routes through fetchT
- End-to-end: real HTTP server on the URL-substring :20190 (so fetchT
routes through _httpFetch without claiming the canonical :2019 port
on the test host) captures Origin matching the parsed URL
- dashcaddy-installer/templates/Caddyfile.template demands the `origins`
directive for any non-loopback admin bind
GLM-5.3 round 1 (140s, 0.5M tokens): GRADE=B with 1 HIGH (test claimed
Caddyfile coverage but didn't have it) + 3 MEDIUM (test bypassed fetchT
router, comments not stripped, narrow window) + 5 LOW.
Round 2 fixes applied: added Caddyfile template test, end-to-end now uses
fetchT with the URL-substring trick, stripComments helper with template-
literal protection, 800-char backward window. Self-grade A.
Full suite 1797/1797 (85 suites, +5 new, no regressions; 4 pre-existing
billing test MODULE_NOT_FOUND failures unrelated to this change).
Pair with: STATE.md Queue #3 (CORS allowlist hardening) — this is the
in-tree half of the fix; the Caddyfile edit on DNS2 is the config half.