Verified medium/low security hardening items from the audit (the high-severity token exp check shipped in #12). Each was confirmed against source.
MEDIUM
- Default + enforce
aud — config.ts:215 (auth.audience optional, no default), auth.ts:51 (check skipped when unset), auth.ts:45 (SDK verify called without audience). A token minted by the same issuer for a different audience is accepted. Fix: default auth.audience (e.g. "codeoid"), pass it to client.tokens.verify(token, aud), and require ZeroID to stamp aud.
- Reject empty-tenancy tokens —
auth.ts:68-69 passes accountId/projectId through even when ""; two claimless tokens then share a ""/"" bucket and can list/attach/send to each other's sessions. Fix: throw in verifyToken/identityToAuthContext when either is empty.
/oauth2/token proxy is unauthenticated + uncapped — server.ts:226-250: no rate limit, no body-size cap, no grant_type check, strips client IP (Bun's ~128MB default body applies). Not SSRF (single fixed upstream) but a DoS-amplification surface against the issuer. Fix: per-IP rate limit, small body cap, restrict Content-Type, forward X-Forwarded-For.
session.search leaks cross-tenant snippets — session-manager.ts:933 scopes by path-hash workspaceId, not (account,project); :958-965 spreads the full hit (summary/excerpt/filePaths) even when the session fails #getOwnedSession — only name/workdir are masked. Fires when two tenants share a path-hash on one host. Fix: drop (not just mask) hits that don't resolve via #getOwnedSession, or thread (account,project) into searchSessions.
LOW
- No Origin/CORS enforcement —
server.ts:204-211 (WS upgrade), :218-220 (/config), :226-250 (proxy). Low because WS auth is JWT-in-body (no ambient cookie → CSWSH largely neutered) and the daemon binds localhost. Fix: Origin allowlist on the upgrade + state-changing routes.
- Sub-agent delegation fallback loses the chain —
agent-identity.ts:239-242: on tokens.delegate failure it mints a token from the sub-agent's own api_key (still scope-capped by allowed_scopes, but no act chain → breaks cascading revocation for that token). Fix: fail closed or bind a short-TTL fallback to the parent.
- Auth error reflects the SDK reason to the client (
server.ts:303), disclosing configured iss/aud. Fix: return a generic message, log detail server-side.
Verified medium/low security hardening items from the audit (the high-severity token
expcheck shipped in #12). Each was confirmed against source.MEDIUM
aud—config.ts:215(auth.audienceoptional, no default),auth.ts:51(check skipped when unset),auth.ts:45(SDKverifycalled without audience). A token minted by the same issuer for a different audience is accepted. Fix: defaultauth.audience(e.g."codeoid"), pass it toclient.tokens.verify(token, aud), and require ZeroID to stampaud.auth.ts:68-69passesaccountId/projectIdthrough even when""; two claimless tokens then share a""/""bucket and can list/attach/send to each other's sessions. Fix: throw inverifyToken/identityToAuthContextwhen either is empty./oauth2/tokenproxy is unauthenticated + uncapped —server.ts:226-250: no rate limit, no body-size cap, nogrant_typecheck, strips client IP (Bun's ~128MB default body applies). Not SSRF (single fixed upstream) but a DoS-amplification surface against the issuer. Fix: per-IP rate limit, small body cap, restrict Content-Type, forwardX-Forwarded-For.session.searchleaks cross-tenant snippets —session-manager.ts:933scopes by path-hashworkspaceId, not(account,project);:958-965spreads the full hit (summary/excerpt/filePaths) even when the session fails#getOwnedSession— only name/workdir are masked. Fires when two tenants share a path-hash on one host. Fix: drop (not just mask) hits that don't resolve via#getOwnedSession, or thread(account,project)intosearchSessions.LOW
server.ts:204-211(WS upgrade),:218-220(/config),:226-250(proxy). Low because WS auth is JWT-in-body (no ambient cookie → CSWSH largely neutered) and the daemon binds localhost. Fix: Origin allowlist on the upgrade + state-changing routes.agent-identity.ts:239-242: ontokens.delegatefailure it mints a token from the sub-agent's own api_key (still scope-capped byallowed_scopes, but noactchain → breaks cascading revocation for that token). Fix: fail closed or bind a short-TTL fallback to the parent.server.ts:303), disclosing configurediss/aud. Fix: return a generic message, log detail server-side.