Pre-fix, the dispose endpoint + storage info block in dashcaddy-api/routes/log-insights.js
HARDCODED /opt/dashcaddy/dashcaddy-api/data/audit-log.json and
/opt/dashcaddy/dashcaddy-api/data/security-events.jsonl, which DO NOT EXIST in the
production container (verified 2026-08-19 01:42Z: /app/data/audit-log.json = 318 KB,
/app/data/security-events.jsonl = 15 MB, /opt/... = ENOENT). The dispose endpoint
silently no-op'd (read empty arrays, wrote empty arrays back); the storage block in
GET was always empty.
Also: parseInt(req.body.keepDays) || 30 accepted negative numbers. keepDays = -1000
produces a cutoff +3 years in the future, then the filter e.timestamp < cutoff
deletes 100% of the audit log. Operators must not be able to wipe forensic context
with a typo.
Fix:
* _resolvePaths() uses process.env.AUDIT_LOG_FILE || path.join(platformPaths.dataDir, 'audit-log.json'),
matching the canonical resolution in src/security/audit-logger.js and src/security/event-store.js.
Both GET + POST share the resolved paths (single source of truth).
* _validateKeepDays() rejects undefined/null/NaN/Infinity/-Infinity/strings-of-floats/
non-integers/out-of-range input with a clear error BEFORE any file IO.
Allowed: integer in [1, 3650] (1 day .. 10 years).
* POST /log-insights/dispose now requires { keepDays: integer 1..3650, confirm: true }.
Preview is read-only. Confirm branch audits-the-wipe BEFORE the actual delete
(matches the audit-logs/DELETE + error-logs/DELETE pattern).
* Atomic write for audit-log.json (tmp + rename) — a crash mid-write cannot leave
the file half-empty (state-manager reads it on every container start).
Tests (23 new, dashcaddy-api/__tests__/routes/log-insights.routes.test.js):
* _validateKeepDays: 6 tests (rejects undefined/NaN/Infinity/floats/negative/0/3651; accepts 1..3650; coerces numeric strings).
* _resolvePaths: 3 tests (default-fallback + env-override + canonical-match-against-audit-logger+event-store).
* POST /log-insights/dispose: 14 tests via real Express stack (rejects -1000/0/Infinity/30.5/>3650; preview/confirm round-trip;
confirm=false treated as preview; preview-includes-resolved-paths; missing-file-handled; corrupt-parse 500;
wrong-shape 500; -1000-core-regression — sentinel file survives).
GLM-5.3 round 1: A.
- New route /api/v1/log-insights: analyzes audit logs + security events
- Shows top IPs with request counts, failures, and top actions
- Plain English insights (heavy users, auth failures, security alerts)
- Summary stats: total requests, unique IPs, failed actions
- Storage info showing log file sizes and entry counts
- New route POST /api/v1/log-insights/dispose: preview-then-confirm cleanup
- First call shows what would be deleted (preview mode)
- Second call with confirm:true actually deletes
- Configurable retention period (default 30 days)
- Frontend panel with modal UI showing insights as cards
- Period selector (1h, 6h, 24h, 7d)
- Top visitors table with IP, requests, failures, actions, last seen
- Storage info footer
- Clean Old Logs button with preview confirmation dialog
- Wired into app.js and dashboard navbar (🔍 Insights button)
- Addresses QA issue: users need to see who is accessing before cleanup