Skip to content

feat(gastown): admin impersonation — allow admins to view any town#1469

Merged
jrf0110 merged 2 commits intomainfrom
1467-admin-town-impersonation
Mar 24, 2026
Merged

feat(gastown): admin impersonation — allow admins to view any town#1469
jrf0110 merged 2 commits intomainfrom
1467-admin-town-impersonation

Conversation

@jrf0110
Copy link
Contributor

@jrf0110 jrf0110 commented Mar 24, 2026

Summary

  • Add admin bypass to the gastown tRPC ownership verification layer (resolveTownOwnership, verifyTownOwnership, verifyRigOwnership) so Kilo admins can access any town's routes — beads, agents, convoys, terminal, settings, mayor chat — without being the town owner
  • Block admins from destructive actions (delete town/rig) and config modifications on towns they don't own; mask secrets in town config for admin viewers
  • Add admin audit logging middleware (adminAuditMiddleware) that writes admin.town_access events to Cloudflare Analytics Engine with admin user ID, town ID, and route
  • Add checkAdminAccess tRPC query and AdminViewingBanner component that shows "Viewing as admin — this town belongs to user@..." when an admin views another user's town
  • Force settings page into read-only mode for admin viewers; allow admins to bypass the gastown-access PostHog feature flag
  • Add "View Town UI" buttons to the admin panel's TownInspectorDashboard and UserAdminGastown components

Verification

  • TypeScript typecheck (pnpm typecheck)
  • Gastown worker unit tests (pnpm test in cloudflare-gastown/)
  • Lint (pnpm lint)
  • Build (pnpm build)
  • New unit tests for admin audit middleware and town-auth admin bypass pass

Visual Changes

image

Reviewer Notes

  • The existing townAuthMiddleware and townOwnershipMiddleware already had admin bypass (if (c.get('kiloIsAdmin')) return next()). The gap was in the tRPC layer where resolveTownOwnership/verifyTownOwnership/verifyRigOwnership did not check isAdmin. This PR adds a new 'admin' ownership type that propagates through the tRPC flow.
  • Admin access to a town they don't own returns { type: 'admin' } from resolveTownOwnership. Callers check for this type to block destructive mutations while allowing read access and mayor chat.
  • The checkAdminAccess tRPC query is cheap — it only hits the TownDO config when the user is an admin, and returns immediately for non-admins.
  • Audit events are written to the same Cloudflare Analytics Engine dataset as existing gastown events, using the existing writeEvent utility. No new tables or migrations required.

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 24, 2026

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
cloudflare-gastown/src/trpc/router.ts 599 deleteBead and deleteAgent still let admin viewers delete another town's rig-scoped resources once townId is supplied.
cloudflare-gastown/src/trpc/router.ts 888 The new masking helper still reveals any secret or env var whose value is 4 characters or shorter.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
src/app/api/gastown/token/route.ts 30 This route still calls isGastownEnabled(user.id) without the new admin override, so the browser token mint can still 403 even after the page-level gating was updated for admins.
Files Reviewed (5 files)
  • cloudflare-gastown/src/trpc/router.ts - 2 issues
  • cloudflare-gastown/test/unit/admin-access.test.ts
  • src/app/admin/components/UserAdmin/UserAdminGastown.tsx
  • src/components/gastown/AdminViewingBanner.tsx
  • src/lib/gastown/types/router.d.ts

Fix these issues in Kilo Cloud


Reviewed by gpt-5.4-20260305 · 799,147 tokens

Add admin impersonation support for the gastown worker so Kilo admins
can navigate to any town URL for support, debugging, and monitoring.

Key changes:
- Add admin bypass to tRPC ownership verification (resolveTownOwnership,
  verifyTownOwnership, verifyRigOwnership) so admins can access any town
- Block admins from destructive actions (deleteTown, deleteRig) and
  config modifications on towns they don't own
- Mask secrets in town config for admin viewers (same as non-owner org members)
- Add admin audit logging middleware that writes admin town access events
  to Cloudflare Analytics Engine
- Add checkAdminAccess tRPC query so the frontend can detect admin viewing
- Add AdminViewingBanner component shown on town overview and settings pages
- Force read-only mode on settings page when admin is viewing another user's town
- Allow admins to bypass the gastown-access feature flag
- Add View Town UI button to admin panel (TownInspectorDashboard and UserAdminGastown)
- Add unit tests for admin audit middleware and town-auth admin bypass

Closes #1467
@jrf0110 jrf0110 force-pushed the 1467-admin-town-impersonation branch from 66a8d3a to b7a9393 Compare March 24, 2026 14:39
- Fix verifyRigOwnership for admins: add optional townIdHint parameter
  that resolves the real owner from TownDO config, so admin viewers can
  access rig-scoped queries (listBeads, listAgents, getRig, etc.)
- Fix checkAdminAccess to return ownerOrgId for org-owned towns; update
  AdminViewingBanner to display org ownership context instead of null
- Fix resolveTownOwnership catch block: no longer returns admin bypass
  for deleted/invalid towns — getTownConfig failure always throws NOT_FOUND
- Block refreshContainerToken for admin viewers (same guard as updateTownConfig)
@jrf0110 jrf0110 force-pushed the 1467-admin-town-impersonation branch from 477fb65 to 9728410 Compare March 24, 2026 15:35
)
.mutation(async ({ ctx, input }) => {
const rig = await verifyRigOwnership(ctx.env, ctx.userId, input.rigId, ctx.orgMemberships);
const rig = await verifyRigOwnership(ctx.env, ctx, input.rigId, input.townId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Admin viewers can still delete rig-scoped resources

verifyRigOwnership() now resolves another owner's rig whenever an admin supplies townId, so this mutation lets admin viewers delete beads in towns they do not own. The same authorization hole is still present in deleteAgent below, which means the impersonation flow is not actually restricting destructive actions to read-only support access.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think thats the point


// Admins see masked secrets (read-only access, no secret exposure)
if (ownership.type === 'admin') {
const mask = (s?: string) => (s ? '****' + s.slice(-4) : undefined);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Short secrets are still exposed by the new masking helper

mask() always appends the last four characters, so any secret or env var with length <= 4 is revealed in full ("abc" -> "****abc"). This PR is trying to guarantee that admin viewers never see another town's secrets, but short values still leak through both this helper and the env_vars masking below.

@jrf0110 jrf0110 merged commit 1d9c8b8 into main Mar 24, 2026
19 checks passed
@jrf0110 jrf0110 deleted the 1467-admin-town-impersonation branch March 24, 2026 16:18
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