T05 · Unauthorized Access and Privilege Escalation
- Location
src/app.ts:46- Finding
Broken read authentication permits cross-tenant run disclosure
- Content
View full analysis
{ const token = req.header('X-Run-Token')?.trim(); if (tokenRequired()) return token ? { ok: true } : { ok: false }; if (getConfig().ALLOW_ANONYMOUS_READ || getConfig().NODE_ENV === 'test') return { ok: true }; return token ? { ok: true } : { ok: false }; }; ``` Affected routes retrieve a run solely by its identifier: ```ts app.get('/v1/run/:id', (req, res) => { const token = requireReadToken(req); if (!token.ok) return sendError(res, 401, { code: 'AUTH_INVALID_TOKEN', message: 'Missing or invalid X-Run-Token', retryable: false, at: 'auth' }); const run = orchestrator.getRun(req.params.id); if (!run) return sendError(res, 404, { code: 'RUN_NOT_FOUND', message: 'Run not found', retryable: false, at: req.params.id }); return res.json(run); }); ``` ### Technical Analysis When `REQUIRE_RUN_TOKEN=1`, `requireReadToken` accepts any non-empty `X-Run-Token`. It does not validate the supplied value against `RUN_TOKENS`, unlike `parseTokenOwner`. After this incomplete authentication check, the read endpoints do not compare the authenticated principal with `run.token_owner`. This creates both an authentication bypass and an insecure direct object reference. The same pattern affects: - `GET /v1/run/:id` - `GET /v1/run/:id/stream` - `GET /v1/run/:id/report` - `GET /v1/run/:id/replay` - `GET /v1/run/:id/events` Run identifiers are generated using cryptographically strong UUIDs, so blind enumeration is difficult. However, any run ID leaked through logs, browser history, telemetry, links, or another application defect becomes sufficient to exploit the issue. ...[truncated 738 chars]- Remediation
View remediation
