T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- src/app.ts:46
- Finding
- Broken read authentication permits cross-tenant run disclosure<![CDATA[ ## Vulnerability Details **File Location**: `src/app.ts:46-51`, affecting run read routes at `src/app.ts:292-388` and `src/app.ts:400-407` **Vulnerability Type**: Broken authentication and missing object-level authorization **Risk Level**: High ### Vulnerable Code ```ts const requireReadToken = (req: express.Request): { ok: true } | { ok: false } => { 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
- <![CDATA[ ## Remediation Suggestions 1. Replace `requireReadToken` with a function that validates the token against `RUN_TOKENS` and returns the authenticated owner. 2. Require `run.token_owner === authenticatedOwner` on every run-scoped read route. 3. Perform ownership checks inside the orchestrator or storage layer so future routes cannot bypass them. 4. Return a uniform `404` for nonexistent and unauthorized run IDs to reduce object discovery. 5. Add tests using two valid tenants and an invalid token for every run read endpoint. 6. Do not treat possession of a run UUID as authorization. ]]>
