Skip to content

feat(auth): audit-log the is_admin bypasses on non-admin procedures - #5126

Merged
RSO merged 2 commits into
mainfrom
admin-access-follow-up-1
Aug 7, 2026
Merged

RSO merged 2 commits into
mainfrom
admin-access-follow-up-1

Conversation

@RSO

@RSO RSO commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #5124.

Why

#5124 gates admin_access on adminOnly: true and adminProcedure. That covers the /admin console, but Kilo's highest-value admin capability — an employee reading an arbitrary customer organization's data — flows through the is_admin escape hatch on regular, non-admin procedures. Those paths touch neither choke point, so they emitted nothing.

The risk isn't just the missing events, it's the false confidence: an Axiom detection built on admin_access would look complete while the most sensitive paths stayed invisible. This closes that.

How

Every event now carries two orthogonal dimensions instead of one, so event:"admin_access" still returns the whole picture and can then be split:

Rather than sprinkling emit calls, the bypasses funnel through elevateViaKiloAdmin / recordKiloAdminElevation, so the choke-point property is structural instead of relying on everyone remembering to log. elevateViaKiloAdmin returns the elevated value, which makes the grant and its audit record a single expression. The ensureOrganizationAccess family was the natural starting point — it already centralises the org-access sites, and covers the 19 baseProcedure org procedures behind it.

route/method for emits inside a resolver come from a new baseProcedure middleware that publishes the procedure path onto the context; resolvers only receive ctx, not the middleware's path/type, so without this the events couldn't name the procedure they came from.

For the wasteland/gastown token routes the event is emitted at mint time, since the elevation is exercised in the worker where this app emits nothing at all.

Two emits are placed before their DB lookup so an admin probing a non-existent organization is still attributed, rather than vanishing into the 404.

#5124's properties are preserved: audit logging still never denies a legitimate admin action (all three entry points are non-throwing), identity remains the primary key with ip recorded but not depended on, and no token, header, cookie or secret is ever included.

Notes for review

  • Duplicate events per request are expected and not deduped. An admin-console call can emit an admin_guard from adminProcedure and a kilo_admin_elevation from ensureOrganizationAccess. Both are true; deduping would need per-request state.
  • resolveScopedConfig's personal branch is deliberately not treated as an elevation. It already requires owner_id === ctx.user.id, so is_admin only gates a staff-only feature on the caller's own config — no customer data is reached. Emitting there would add false positives to the elevation stream.
  • Batch org resolution records breadth, not IDs. Inlining hundreds of UUIDs would bloat the log line past what the drain keeps.
  • Token-mint correlation is kiloUserId + timestamp within the token lifetime, not exact. generateApiToken has no jti; adding one only pays off once the downstream workers log it, which is cross-repo.
  • The mcp-gateway helpers now take ctx instead of userId + isGlobalAdmin. That's what makes the bypass auditable in one place, and it removes the isGlobalAdmin: ctx.user.is_admin repetition across 12 call sites that made an unaudited bypass easy to add.
  • The docblock in admin-access-log.ts now states the coverage limit explicitly and names the funnel new bypasses must use, so the next reader isn't misled by the feat(auth): audit-log all admin access (choke-point, IP-independent) #5124 title.

#5124 gated admin_access on adminOnly:true and adminProcedure, which
covers the /admin console but misses the is_admin escape hatch on
regular procedures — the path a Kilo employee actually takes to read a
customer organization's data. Those paths emitted nothing.

Adds kind:"kilo_admin_elevation" alongside the existing
kind:"admin_guard", plus reason/target, and funnels the bypasses through
elevateViaKiloAdmin/recordKiloAdminElevation so the choke point is
structural rather than something each site has to remember.
Comment thread apps/web/src/lib/admin/admin-access-log.ts
Comment thread apps/web/src/routers/organizations/utils.ts Outdated
Comment thread apps/web/src/lib/wasteland/server-resolve.ts
@kilo-code-bot

kilo-code-bot Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

Incremental review of the async conversion and REST-attribution fallback in the elevation audit helpers found no new issues — all call sites are awaited and the never-throw guarantee is preserved; one previously flagged sink error-handling warning and one null-attribution suggestion remain open from the prior review.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/admin/admin-access-log.ts 141 Non-Error throw values are swallowed silently by the sink failure handler

SUGGESTION

File Line Issue
apps/web/src/lib/wasteland/server-resolve.ts 34 recordKiloAdminElevationForRequest in a non-request context will emit with null route/method/ip
Files Reviewed (9 files in incremental diff)
  • apps/web/src/lib/admin/admin-access-log.test.ts
  • apps/web/src/lib/admin/admin-access-log.ts - 1 issue (unchanged hunk, still open)
  • apps/web/src/lib/security-agent/router/shared-handlers.ts
  • apps/web/src/lib/trpc/init.test.ts
  • apps/web/src/routers/cli-sessions-v2-router.ts
  • apps/web/src/routers/code-indexing/code-indexing-router.ts
  • apps/web/src/routers/mcp-gateway-router.ts
  • apps/web/src/routers/organizations/utils.test.ts
  • apps/web/src/routers/organizations/utils.ts

Carried from previous review (unchanged file, verified still present): apps/web/src/lib/wasteland/server-resolve.ts - 1 issue

Fix these issues in Kilo Cloud

Previous Review Summary (commit 4b5f116)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 4b5f116)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The audit-logging elevation mechanism is well-structured and covers the intended bypasses, but there is a minor robustness issue in the sink error handling and two observability gaps worth noting.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/admin/admin-access-log.ts 141 Non-Error throw values are swallowed silently by the sink failure handler

SUGGESTION

File Line Issue
apps/web/src/routers/organizations/utils.ts 118 elevateViaKiloAdmin call allocates and emits even when uniqueIds is empty
apps/web/src/lib/wasteland/server-resolve.ts 34 recordKiloAdminElevationForRequest in a non-request context will emit with null route/method/ip
Files Reviewed (16 files)
  • apps/web/src/app/api/gastown/token/route.ts
  • apps/web/src/app/api/wasteland/token/route.ts
  • apps/web/src/lib/admin/admin-access-log.test.ts
  • apps/web/src/lib/admin/admin-access-log.ts - 1 issue
  • apps/web/src/lib/organizations/organization-auth.ts
  • apps/web/src/lib/security-agent/router/shared-handlers.ts
  • apps/web/src/lib/trpc/init.test.ts
  • apps/web/src/lib/trpc/init.ts
  • apps/web/src/lib/user/server.test.ts
  • apps/web/src/lib/user/server.ts
  • apps/web/src/lib/wasteland/server-resolve.ts - 1 issue
  • apps/web/src/routers/cli-sessions-v2-router.ts
  • apps/web/src/routers/code-indexing/code-indexing-router.ts
  • apps/web/src/routers/mcp-gateway-router.ts
  • apps/web/src/routers/organizations/utils.test.ts
  • apps/web/src/routers/organizations/utils.ts - 1 issue

Fix these issues in Kilo Cloud


Reviewed by kimi-k3 · Input: 56.3K · Output: 12.3K · Cached: 464.4K

Review guidance: REVIEW.md from base branch main

`recordKiloAdminElevation` hard-coded `surface: 'trpc'` and read request
metadata only from a tRPC context. But `ensureOrganizationAccess`,
`getOrganizationsAccessRoles`, and `ensureOrganizationAccessAndFetchOrg` are
also called from REST route handlers that hand-roll a `{ user }` context —
`/api/cloud-agent/sessions/stream-ticket`, `/api/auto-routing/settings`,
`/api/code-indexing/enabled`, the integration connect/callback handlers, and
the org integration reinstall routes.

An admin elevation on those paths was therefore emitted as a tRPC request with
null route/IP and, worse, `authVia: 'session'` — a fabricated value on the one
field documented as the token-compromise discriminator. A stolen admin bearer
token reading an arbitrary customer organization over REST looked like a
web-console session.

Make the two elevation emitters async and dispatch on the presence of
`ctx.trpcPath`: tRPC contexts are attributed from the context as before,
anything else delegates to the existing `recordKiloAdminElevationForRequest`,
which derives surface/authVia/route/IP from the request headers. Keeping one
emitter name means a future `is_admin` bypass is attributed correctly on either
transport by default, rather than depending on the author picking the right
variant.

`tokenSource` stays null for REST callers, which pass a bare `{ user }`;
`authVia: 'token'` still fires, and null is already a documented value for that
field. Noted inline so it is not mistaken for an oversight.
@RSO
RSO merged commit e7afdfc into main Aug 7, 2026
18 of 20 checks passed
@RSO
RSO deleted the admin-access-follow-up-1 branch August 7, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants