feat(security): activate caddy security-event pipeline — bounded first-start replay, self-noise filter, host fidelity (DC-113) [glm-grade=A]
The caddy tail worker (DC-112) was 100% dead in prod: no /var/log/caddy mount, no CADDY_ACCESS_LOG env, no global access log in the Caddyfile. Store census 45,912 events, 100% source_type 'api', ZERO 'caddy'. - createTail: firstStartMaxBytes (5 MiB) bounds first-ever-start replay against a long-lived access.log; multi-chunk partial-line discard on the jump; normal restarts resume at exact persisted offset (judge r1 fix-first fold) - worker: self-noise filter drops our own probe UAs (DashCaddy-Probe/1.0, DashCaddy-HealthCheck/1.0) from the derived store — raw log keeps everything; ~50-100 events/min of probe noise would otherwise bury perimeter signal in the 100k-cap store - worker: metadata.host reads request.host (real caddy JSON nests it; verified against live /var/log/caddy/seeds.log — the DC-112 read was always null on live lines); top-level fallback kept - worker: onAppear recovery log (DC-112 judge polish fold) - start.sh: -v /var/log/caddy:/var/log/caddy:ro + CADDY_ACCESS_LOG env - README: dead caddy-api/ dir refs -> dashcaddy-api/ (queue item e) - tests: +12 DC-113 pins (hermetic, real worker + store); DC-112 fixtures corrected to the real nested request.host shape 126 suites / 2841 tests green. Judge: GLM-5.3 round-1 B, round-2 A (SHIP). Verdict urn:ump:mccln523fptotuvrmddlqpf4zpkrxf273tg4kyytuyy3776rj3pq
This commit is contained in:
@@ -128,11 +128,11 @@ describe('caddy worker end-to-end (real tail + real store)', () => {
|
||||
|
||||
test('gate hit is named, escalated, and carries array-normalized UA + host', async () => {
|
||||
// A realistic forward_auth gate miss, exactly as caddy logs it:
|
||||
// headers as arrays, host at top level, duration in seconds.
|
||||
// headers as arrays, host nested in request, duration in seconds.
|
||||
fs.appendFileSync(ACCESS_LOG, JSON.stringify({
|
||||
ts: 1787500800,
|
||||
host: 'plex.sami',
|
||||
request: {
|
||||
request: { host: 'plex.sami',
|
||||
|
||||
remote_ip: '10.9.9.9',
|
||||
method: 'GET',
|
||||
uri: '/api/auth/gate/plex',
|
||||
@@ -163,15 +163,15 @@ describe('caddy worker end-to-end (real tail + real store)', () => {
|
||||
test('canonical gate hit and sso-exchange POST are named too', async () => {
|
||||
fs.appendFileSync(ACCESS_LOG, JSON.stringify({
|
||||
ts: 1787500801,
|
||||
host: 'status.sami',
|
||||
request: { remote_ip: '10.9.9.8', method: 'GET', uri: '/api/v1/auth/gate/sonarr', proto: 'HTTP/2.0', headers: { 'User-Agent': ['Mozilla/5.0'] } },
|
||||
request: { host: 'status.sami',
|
||||
remote_ip: '10.9.9.8', method: 'GET', uri: '/api/v1/auth/gate/sonarr', proto: 'HTTP/2.0', headers: { 'User-Agent': ['Mozilla/5.0'] } },
|
||||
status: 401,
|
||||
duration: 0.002,
|
||||
}) + '\n');
|
||||
fs.appendFileSync(ACCESS_LOG, JSON.stringify({
|
||||
ts: 1787500802,
|
||||
host: 'status.sami',
|
||||
request: { remote_ip: '10.9.9.8', method: 'POST', uri: '/api/auth/sso-exchange', proto: 'HTTP/2.0', headers: { 'user-agent': ['DashCaddy-Login/1.0'] } },
|
||||
request: { host: 'status.sami',
|
||||
remote_ip: '10.9.9.8', method: 'POST', uri: '/api/auth/sso-exchange', proto: 'HTTP/2.0', headers: { 'user-agent': ['DashCaddy-Login/1.0'] } },
|
||||
status: 200,
|
||||
duration: 0.084,
|
||||
}) + '\n');
|
||||
@@ -192,8 +192,8 @@ describe('caddy worker end-to-end (real tail + real store)', () => {
|
||||
test('ordinary traffic keeps http.<status> naming and default severity', async () => {
|
||||
fs.appendFileSync(ACCESS_LOG, JSON.stringify({
|
||||
ts: 1787500803,
|
||||
host: 'status.sami',
|
||||
request: { remote_ip: '100.121.150.22', method: 'GET', uri: '/api/health', proto: 'HTTP/2.0', headers: { 'User-Agent': ['watchdog'] } },
|
||||
request: { host: 'status.sami',
|
||||
remote_ip: '100.121.150.22', method: 'GET', uri: '/api/health', proto: 'HTTP/2.0', headers: { 'User-Agent': ['watchdog'] } },
|
||||
status: 401,
|
||||
duration: 0.004,
|
||||
}) + '\n');
|
||||
@@ -216,8 +216,8 @@ describe('caddy worker end-to-end (real tail + real store)', () => {
|
||||
test('restart does not re-emit: offset persistence across worker instances', async () => {
|
||||
fs.appendFileSync(ACCESS_LOG, JSON.stringify({
|
||||
ts: 1787500804,
|
||||
host: 'plex.sami',
|
||||
request: { remote_ip: '10.9.9.9', method: 'GET', uri: '/api/auth/gate/plex', headers: {} },
|
||||
request: { host: 'plex.sami',
|
||||
remote_ip: '10.9.9.9', method: 'GET', uri: '/api/auth/gate/plex', headers: {} },
|
||||
status: 401,
|
||||
}) + '\n');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user