You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
security: /health exposes raw SIEM webhook error bodies unauthenticated #989
The processor's `/health` endpoint is unauthenticated (by design — it's a liveness probe) but now surfaces `siem.sources..lastError` per source. That field is populated directly from the SIEM delivery worker and contains raw webhook response bodies.
Where
Writer: `apps/processor/src/services/siem-adapter.ts` around line 260 — `sendWebhook` sets `error: `HTTP ${response.status}: ${errorText}`` where `errorText` is the full `response.text()`.
Persister: `apps/processor/src/workers/siem-delivery-worker.ts` writes that string into `siem_delivery_cursors.lastError`.
Exposer: `apps/processor/src/server.ts` → `/health` handler returns `siem.sources..lastError` via `buildSiemHealth`. `/health` has no auth middleware.
Why it's a problem
If a customer's SIEM receiver returns a verbose error — internal stack trace, authentication detail (`invalid token: abc123…`), schema info, user IDs, or any debug output — that content is readable by anyone who can reach the processor's health endpoint. In the `cloud` deployment mode where processors share infrastructure across tenants this is a cross-tenant information disclosure surface; even in `onprem` it's a reconnaissance aid for attackers fingerprinting the downstream SIEM.
Not a regression
This was introduced earlier (the pre-Wave-3c `/health` already surfaced `cursor.lastError` for a single source). Wave 3c (#987) preserved the behavior per-source but did not widen or narrow it. Filing now because the review pass flagged it and it deserves its own fix.
Proposed mitigations (pick one or combine)
Sanitize at write time — in `sendWebhook`, store only the HTTP status code and a generic classification (`transport_error`, `http_4xx`, `http_5xx`, `hmac_rejected`), never the raw body. Full bodies still go to processor logs for operators.
Sanitize at read time — in `buildSiemHealth` or `siem-cursor-reader`, replace `lastError` with a boolean `hasError: true` plus an enum classification, dropping the message.
Authenticate `/health` — wrap behind `authenticateService` like the other processor routes. Downside: breaks k8s liveness probes unless they inject the service token.
Two endpoints — keep the current unauthenticated `/health` reporting only status + booleans, add authenticated `/health/detail` for operators who need the full error strings.
My preference: #1 + #4. Scrub raw bodies at the source of truth, and if operators need detail gate it behind auth.
Acceptance criteria
`siem.sources..lastError` on unauthenticated `/health` never contains customer-controlled strings (webhook response bodies, header values, syslog server banners, etc.).
Operators can still see the raw error during triage (logs, authenticated detail endpoint, or admin API).
Tests cover: error classification mapping, and that raw bodies do not appear in the `/health` response even when the cursor row contains them.
Summary
The processor's `/health` endpoint is unauthenticated (by design — it's a liveness probe) but now surfaces `siem.sources..lastError` per source. That field is populated directly from the SIEM delivery worker and contains raw webhook response bodies.
Where
Why it's a problem
If a customer's SIEM receiver returns a verbose error — internal stack trace, authentication detail (`invalid token: abc123…`), schema info, user IDs, or any debug output — that content is readable by anyone who can reach the processor's health endpoint. In the `cloud` deployment mode where processors share infrastructure across tenants this is a cross-tenant information disclosure surface; even in `onprem` it's a reconnaissance aid for attackers fingerprinting the downstream SIEM.
Not a regression
This was introduced earlier (the pre-Wave-3c `/health` already surfaced `cursor.lastError` for a single source). Wave 3c (#987) preserved the behavior per-source but did not widen or narrow it. Filing now because the review pass flagged it and it deserves its own fix.
Proposed mitigations (pick one or combine)
My preference: #1 + #4. Scrub raw bodies at the source of truth, and if operators need detail gate it behind auth.
Acceptance criteria
Related