Skip to content

feat(auth): make revocation and account deletion real (w4c) - #5337

Merged
iscekic merged 26 commits into
mainfrom
audit-w4c-revocation-9496
Aug 18, 2026
Merged

feat(auth): make revocation and account deletion real (w4c)#5337
iscekic merged 26 commits into
mainfrom
audit-w4c-revocation-9496

Conversation

@iscekic

@iscekic iscekic commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

What changed for the user

  • A blocked or removed account now loses access on every service path at the same time: Session Ingest, Cloud Agent streams, the CLI, and the iOS app. A cached session or a stale credential no longer keeps authorizing.
  • A rotated API token now stops working everywhere immediately.
  • Deleting your account now requires an emailed confirmation code, actually removes the account, and signs you out.
  • Removing an organization member now immediately closes their live viewer and stream sessions.
  • On iOS, bearer tokens are stored so they never follow a device restore or backup.

How it works, for the maintainer

Kilo bearer verification now checks the account's current API-token pepper and blocked state. A rotated pepper or a blocked account fails even when a cached existence check would pass, so revocation takes effect immediately. A secret-store or database failure throws instead of returning null, so callers map an outage to a retryable 503 rather than a 401.

Files
  • packages/worker-utils/src/kilo-token-auth.ts — adds findKiloUserPepper and verifyKiloBearerAgainstCurrentPepper.

Session Ingest now verifies the current pepper and mints one-use web tickets. The /api/user/web upgrade consumes an opaque ticket before any JWT path runs, and a dependency failure maps to 503. The ticket lives in a new Durable Object and is consumed exactly once.

Files
  • services/session-ingest/src/middleware/kilo-jwt-auth.ts — verifies against the current pepper; adds the one-use web-ticket branch that runs before the JWT path.
  • services/session-ingest/src/dos/connection-ticket-do.ts — new Durable Object: mint stores { userId, expiresAt } and arms an alarm; consume deletes atomically and returns the userId exactly once.
  • services/session-ingest/src/routes/api.ts — adds POST /api/user/web-ticket (opaque UUID, 60 s expiry); /api/user/web forwards the authenticated kiloUserId.
  • services/session-ingest/src/index.ts — exports ConnectionTicketDO.
  • services/session-ingest/wrangler.jsonc — adds the CONNECTION_TICKET_DO binding and a v3 SQLite migration.
  • services/session-ingest/wrangler.test.jsonc — adds the CONNECTION_TICKET_DO binding and a v2 test migration.

Session Ingest rechecks membership on subscribe and command, and closes viewer sockets on member removal. A removed member gets a SESSION_ACCESS_DENIED envelope and their live sockets close immediately. The invalidate endpoint now also closes viewer sockets.

Files
  • services/session-ingest/src/dos/UserConnectionDO.ts — carries kiloUserId on the web attachment; rechecks membership via resolveAccessibleKiloSession on both subscribe and command; adds closeViewerSockets().
  • services/session-ingest/src/app.ts/internal/session-access/invalidate also calls closeViewerSockets().

Cloud Agent Next tRPC auth now enforces the current pepper and active-account state. validateKiloToken wraps the shared verifier and passes the Hyperdrive connection string. A blocked or pepper-rotated account fails tRPC auth.

Files
  • services/cloud-agent-next/src/validate-kilo-token.ts — new validateKiloToken wrapper around the shared verifier.
  • services/cloud-agent-next/src/middleware/auth.ts — uses validateKiloToken instead of the previous token check.

Cloud Agent stream and terminal tickets are now audience-scoped and single-use, and member removal closes live streams. The worker /stream and /terminal routes validate the audience, recheck session access, and consume a ticket nonce exactly once before upgrading, so a replayed or cross-purpose ticket is rejected; the direct Durable Object /stream validates audience only. Removing an organization member also closes every live stream socket for that member through a new internal endpoint.

Files
  • services/cloud-agent-next/src/auth.ts — adds STREAM_TICKET_AUDIENCE and TERMINAL_TICKET_AUDIENCE; validateStreamTicket requires an expectedAudience; the legacy wrapper path re-verifies through verifyKiloToken.
  • services/cloud-agent-next/src/persistence/StreamTicketNonceDO.ts — new one-time nonce consumer; consumeStreamTicketNonce is the single consume call site.
  • services/cloud-agent-next/src/server.ts — validates audience and consumes the nonce on /stream and /terminal; adds POST /internal/streams/close.
  • services/cloud-agent-next/src/persistence/CloudAgentSession.ts — passes STREAM_TICKET_AUDIENCE to validateStreamTicket; adds closeOrgStreams(organizationId).
  • services/cloud-agent-next/src/types.ts — declares the STREAM_TICKET_NONCE_DO binding.
  • services/cloud-agent-next/src/index.ts — exports StreamTicketNonceDO.
  • services/cloud-agent-next/wrangler.jsonc — adds the STREAM_TICKET_NONCE_DO binding and a v8 SQLite migration.
  • services/cloud-agent-next/wrangler.test.jsonc — adds the STREAM_TICKET_NONCE_DO binding and a v8 migration.

The active-sessions getToken procedure is now a mutation that mints a one-use web ticket. It posts to Session Ingest /api/user/web-ticket with an internal service token and returns { token, expiresAt }. A missing worker URL or a bad mint response fails fast.

Files
  • apps/web/src/routers/active-sessions-router.tsgetToken becomes a mutation returning { token, expiresAt }.

Account deletion now requires an emailed confirmation code and performs real GDPR removal. requestAccountDeletionChallenge mints and emails a sign-in code with a 1-hour cooldown; requestAccountDeletion rechecks the soft-delete precondition, reserves the code, runs removal, and consumes the code only after removal succeeds. A failed removal releases the code so the caller is not stranded.

Files
  • apps/web/src/routers/user-router.ts — adds requestAccountDeletionChallenge and requestAccountDeletion.

The web app signs stream tickets with an audience. signStreamTicket sets the JWT audience to stream or terminal based on purpose, and embeds a nonce.

Files
  • apps/web/src/lib/cloud-agent/stream-ticket.ts — sets audience and nonce on signStreamTicket.

Member removal now closes the removed member's live Cloud Agent streams, and group-policy admission reads the primary database. closeCloudAgentOrgStreams posts to the internal close endpoint, and removal calls it best-effort. Group-policy admission reads the primary db instead of the replica when no transaction is passed.

Files
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts — adds closeCloudAgentOrgStreams.
  • apps/web/src/lib/organizations/organizations.ts — calls closeCloudAgentOrgStreams after member removal.
  • apps/web/src/lib/organizations/organization-group-policy-context.server.ts — reads the primary db instead of readDb.

The code-review stream view fetches a fresh stream ticket, and the Cloud Agent provider mints the ingest ticket as a mutation. fetchStreamTicket validates { ticket, expiresAt }; CodeReviewStreamView uses it. CloudAgentProvider switches getToken to .mutate().

Files
  • apps/web/src/components/code-reviews/fetch-stream-ticket.ts — new fetchStreamTicket.
  • apps/web/src/components/code-reviews/CodeReviewStreamView.tsx — uses fetchStreamTicket.
  • apps/web/src/components/cloud-agent-next/CloudAgentProvider.tsxgetToken calls .mutate().

iOS bearer tokens are stored with WHEN_UNLOCKED_THIS_DEVICE_ONLY. This keeps credentials out of iCloud and iTunes backups so they never follow a device restore. Every bearer-token write and delete pins the option.

Files
  • apps/mobile/src/lib/auth/credentials.ts — adds IOS_BEARER_SECURE_STORE_OPTIONS and pins every bearer write and delete to it.
  • apps/mobile/src/lib/auth/auth-context.tsx — uses the same options on sign-out deletion.

The mobile profile screen gains a Delete Account flow. useDeleteAccount requests a challenge, submits the code, and signs out on success. The profile screen renders the confirm, code-entry, and terminal states.

Files
  • apps/mobile/src/components/use-delete-account.ts — new deletion hook.
  • apps/mobile/src/components/profile-screen.tsx — adds the Delete Account flow.

Mobile mints the ingest ticket as a mutation and returns { ticket, expiresAt }. mobile-session-manager returns { ticket, expiresAt } from getTicket; user-web-connection-provider switches getToken to .mutate().

Files
  • apps/mobile/src/components/agents/mobile-session-manager.tsgetTicket returns { ticket, expiresAt }.
  • apps/mobile/src/components/agents/user-web-connection-provider.tsxgetToken calls .mutate().

The extension mints the ingest ticket as a mutation and returns { ticket, expiresAt }. extension-agent-session-manager returns { ticket, expiresAt } from getTicket; agents-provider switches getToken to .mutate().

Files
  • apps/extension/src/shared/extension-agent-session-manager.tsgetTicket returns { ticket, expiresAt }.
  • apps/extension/entrypoints/sidepanel/agents-provider.tsxgetToken calls .mutate().

The SDK sends the one-use ticket under the ticket query parameter and refreshes it on reconnect. user-web-connection tracks hasEverOpened and refreshes auth only before a reconnect, so the initial connect does not double-mint. base-connection adds a skipAuthRefresh flag so the post-refresh reconnect does not refresh again.

Files
  • packages/cloud-agent-sdk/src/user-web-connection.ts — sends the ticket under the ticket param; adds hasEverOpened and shouldRefreshAuthBeforeConnect.
  • packages/cloud-agent-sdk/src/base-connection.ts — adds skipAuthRefresh to connectInternal.

A seed fixture creates an owner-plus-member organization pair for the member-revocation E2E scenario.

Files
  • dev/seed/app/w4c-org-pair.ts — new seed.

Tests: 30 test files changed (9 new), covering the shared pepper verification, the one-use web ticket, the nonce replay rejection, the member-removal socket close, and the mobile deletion flow.
Generated: 2 worker-configuration.d.ts files regenerated (session-ingest and cloud-agent-next).


Verification

  • E2E (bot-e2e, iOS only) exercises cross-service revocation and the mobile deletion flow.

Visual Changes

The mobile Profile screen gains a Delete Account flow (confirm, enter emailed code, terminal success).

Step Screenshot
Confirm confirm
Enter code enter-code
Terminal success terminal-success

Reviewer Notes

  • The nonce consume lives only on the worker /stream and /terminal paths; the direct Durable Object /stream validates audience only (recorded decision — replay is guaranteed on the public worker path).
  • activeSessions.getToken is now a mutation because a one-use ticket must be minted on every connect.
  • No human steps are needed: no new secret, migration, or flag.

E2E: bot-e2e

iscekic added 13 commits August 18, 2026 01:22
P2-D-24b: getOrganizationGroupPolicyContext without tx opened readDb.
The openrouter gateway admission path consumes that membership read.
Read the primary (db) instead, keeping repeatable-read read-only.
P2-D-23a: bearer writes and sign-out deletes used no keychain class.
Use SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY so the tokens are not
in iCloud or iTunes backup and do not migrate to a new device.
…lient

P2-GH-54c: mobile and extension getTicket parsed only { ticket } and returned
a string, so the SDK set expiresAt to undefined. Return { ticket, expiresAt }
and fail when expiresAt is missing. CodeReviewStreamView keeps a string
handoff to websocket-manager.
… on removal

P2-D-22: handleWebCommand rechecks current org membership before forwarding.
Member removal closes the member's live UserConnection web sockets and Cloud
Agent stream sockets via closeViewerSockets and closeOrgStreams.
P1-D-34a/b: requestAccountDeletionChallenge emails a sign-in code and returns
devCode in non-production. requestAccountDeletion reauthenticates with the
code, checks preconditions, then calls performGdprRemoval. Mobile keeps the
challenge on precondition and invalid-code errors so the user can resubmit.
Fix tsgo errors (body cast, NODE_ENV replaceProperty, nonce-DO binding,
holder pattern, requireActual cast, fetch mock typing) and oxlint
no-floating-promises / require-await in the slice test files.
The extension getTicket now requires expiresAt; the e2e fixture mock omitted it and would fail Cloud Agent connect.
P3-D-08ba requires stream and terminal replay rejection; /stream had a test but /terminal did not.
@iscekic iscekic self-assigned this Aug 18, 2026
Creates an organization with an owner and a member for the S4 socket-close scenario.
@iscekic iscekic changed the title feat(auth): make revocation and account deletion real feat(auth): make revocation and account deletion real (w4c) Aug 18, 2026
…9496

# Conflicts:
#	services/session-ingest/src/index.test.ts
#	services/session-ingest/worker-configuration.d.ts
connectionTicketConsumeResponseSchema is only used via z.infer to derive a type, never safeParse'd. Replace it with a plain type.
validateKiloToken imports verifyKiloBearerAgainstCurrentPepper, which transitively imports pg. auth.ts is imported by CloudAgentSession, which the integration-test worker imports, so pg leaked into the Workers runtime and failed to load. Move validateKiloToken into its own module so auth.ts stays pg-free.
…9496

# Conflicts:
#	apps/web/src/routers/active-sessions-router.test.ts
#	apps/web/src/routers/active-sessions-router.ts
#	packages/worker-utils/src/kilo-token-auth.ts
#	services/session-ingest/src/middleware/kilo-jwt-auth.test.ts
#	services/session-ingest/src/middleware/kilo-jwt-auth.ts
@kilo-code-bot

kilo-code-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental change is a test-only branded-type cast in the mobile session-manager fixture; prior revocation and deletion findings remain resolved.

Files Reviewed (1 file)
  • apps/mobile/src/components/agents/mobile-session-manager.test.ts
Previous Review Summaries (4 snapshots, latest commit 151820c)

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

Previous review (commit 151820c)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental fixes consume the deletion code only after GDPR removal and map auth dependency outages to 503; both prior findings are resolved.

Files Reviewed (6 files)
  • apps/web/src/routers/user-router.ts
  • apps/web/src/routers/user-router.test.ts
  • packages/worker-utils/src/kilo-token-auth.ts
  • packages/worker-utils/src/kilo-token-auth.test.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.test.ts

Previous review (commit 54c4c1f)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

Account deletion still burns the confirmation code before performGdprRemoval succeeds, so a transient GDPR failure locks the user out for the 1-hour cooldown.

Overview

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

WARNING

File Line Issue
apps/web/src/routers/user-router.ts 978 Code is consumed before performGdprRemoval succeeds
Files Reviewed (71 files)
  • apps/web/src/routers/user-router.ts - 1 issue
  • apps/extension/entrypoints/sidepanel/agents-provider.tsx
  • apps/extension/src/shared/extension-agent-session-manager.ts
  • apps/extension/src/shared/extension-agent-session-manager.test.ts
  • apps/extension/tests/e2e/agents-fixture.ts
  • apps/mobile/src/components/agents/mobile-session-manager.ts
  • apps/mobile/src/components/agents/mobile-session-manager.test.ts
  • apps/mobile/src/components/agents/user-web-connection-provider.tsx
  • apps/mobile/src/components/agents/user-web-connection-provider.test.ts
  • apps/mobile/src/components/agents/user-web-connection-provider.mounted.test.tsx
  • apps/mobile/src/components/profile-screen.tsx
  • apps/mobile/src/components/profile-screen.mounted.test.tsx
  • apps/mobile/src/components/use-delete-account.ts
  • apps/mobile/src/lib/auth/auth-context.tsx
  • apps/mobile/src/lib/auth/auth-context.test.ts
  • apps/mobile/src/lib/auth/auth-context.test.tsx
  • apps/mobile/src/lib/auth/credentials.ts
  • apps/mobile/src/lib/auth/credentials.test.ts
  • apps/mobile/src/lib/trpc.test.ts
  • apps/web/src/components/cloud-agent-next/CloudAgentProvider.tsx
  • apps/web/src/components/code-reviews/CodeReviewStreamView.tsx
  • apps/web/src/components/code-reviews/fetch-stream-ticket.ts
  • apps/web/src/components/code-reviews/fetch-stream-ticket.test.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.test.ts
  • apps/web/src/lib/cloud-agent/stream-ticket.ts
  • apps/web/src/lib/organizations/organization-group-policy-context.server.ts
  • apps/web/src/lib/organizations/organization-group-policy-context.server.test.ts
  • apps/web/src/lib/organizations/organizations.ts
  • apps/web/src/lib/organizations/organizations.test.ts
  • apps/web/src/routers/active-sessions-router.ts
  • apps/web/src/routers/active-sessions-router.test.ts
  • apps/web/src/routers/user-router.test.ts
  • dev/seed/app/w4c-org-pair.ts
  • packages/cloud-agent-sdk/src/base-connection.ts
  • packages/cloud-agent-sdk/src/user-web-connection.ts
  • packages/cloud-agent-sdk/src/user-web-connection.test.ts
  • packages/worker-utils/src/kilo-token-auth.ts
  • packages/worker-utils/src/kilo-token-auth.test.ts
  • services/cloud-agent-next/src/auth.ts
  • services/cloud-agent-next/src/auth.test.ts
  • services/cloud-agent-next/src/index.ts
  • services/cloud-agent-next/src/middleware/auth.ts
  • services/cloud-agent-next/src/middleware/auth.test.ts
  • services/cloud-agent-next/src/persistence/CloudAgentSession.ts
  • services/cloud-agent-next/src/persistence/CloudAgentSession-stream-ticket.test.ts
  • services/cloud-agent-next/src/persistence/StreamTicketNonceDO.ts
  • services/cloud-agent-next/src/router.test.ts
  • services/cloud-agent-next/src/server.ts
  • services/cloud-agent-next/src/server.test.ts
  • services/cloud-agent-next/src/server-stream-ticket.test.ts
  • services/cloud-agent-next/src/types.ts
  • services/cloud-agent-next/src/validate-kilo-token.ts
  • services/cloud-agent-next/src/validate-kilo-token.test.ts
  • services/cloud-agent-next/worker-configuration.d.ts
  • services/cloud-agent-next/wrangler.jsonc
  • services/cloud-agent-next/wrangler.test.jsonc
  • services/session-ingest/src/app.ts
  • services/session-ingest/src/dos/UserConnectionDO.ts
  • services/session-ingest/src/dos/UserConnectionDO.test.ts
  • services/session-ingest/src/dos/connection-ticket-do.ts
  • services/session-ingest/src/dos/connection-ticket-do.test.ts
  • services/session-ingest/src/index.ts
  • services/session-ingest/src/index.test.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.test.ts
  • services/session-ingest/src/routes/api.ts
  • services/session-ingest/src/routes/api.test.ts
  • services/session-ingest/worker-configuration.d.ts
  • services/session-ingest/wrangler.jsonc
  • services/session-ingest/wrangler.test.jsonc

Fix these issues in Kilo Cloud

Previous review (commit 412d878)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

Account deletion reauth skips expiry and attempt limits, and one-use ingest tickets are replayed on unexpected reconnect.

Overview

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

CRITICAL

File Line Issue
apps/web/src/routers/user-router.ts 951 consumeSignInCode skips expiry and the 5-attempt budget
packages/cloud-agent-sdk/src/user-web-connection.ts 419 One-use ingest ticket is reused on unexpected 1006 reconnect

WARNING

File Line Issue
apps/web/src/routers/user-router.ts 951 Code is consumed before performGdprRemoval succeeds
apps/web/src/routers/user-router.ts 917 Production provider_not_configured stamps cooldown and returns success
services/cloud-agent-next/src/persistence/StreamTicketNonceDO.ts 12 Nonce DOs never expire or delete storage
Files Reviewed (70 files)
  • apps/web/src/routers/user-router.ts - 3 issues
  • packages/cloud-agent-sdk/src/user-web-connection.ts - 1 issue
  • services/cloud-agent-next/src/persistence/StreamTicketNonceDO.ts - 1 issue
  • apps/extension/entrypoints/sidepanel/agents-provider.tsx
  • apps/extension/src/shared/extension-agent-session-manager.ts
  • apps/extension/src/shared/extension-agent-session-manager.test.ts
  • apps/extension/tests/e2e/agents-fixture.ts
  • apps/mobile/src/components/agents/mobile-session-manager.ts
  • apps/mobile/src/components/agents/mobile-session-manager.test.ts
  • apps/mobile/src/components/agents/user-web-connection-provider.tsx
  • apps/mobile/src/components/agents/user-web-connection-provider.test.ts
  • apps/mobile/src/components/agents/user-web-connection-provider.mounted.test.tsx
  • apps/mobile/src/components/profile-screen.tsx
  • apps/mobile/src/components/profile-screen.mounted.test.tsx
  • apps/mobile/src/components/use-delete-account.ts
  • apps/mobile/src/lib/auth/auth-context.tsx
  • apps/mobile/src/lib/auth/auth-context.test.ts
  • apps/mobile/src/lib/auth/auth-context.test.tsx
  • apps/mobile/src/lib/auth/credentials.ts
  • apps/mobile/src/lib/auth/credentials.test.ts
  • apps/mobile/src/lib/trpc.test.ts
  • apps/web/src/components/cloud-agent-next/CloudAgentProvider.tsx
  • apps/web/src/components/code-reviews/CodeReviewStreamView.tsx
  • apps/web/src/components/code-reviews/fetch-stream-ticket.ts
  • apps/web/src/components/code-reviews/fetch-stream-ticket.test.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.test.ts
  • apps/web/src/lib/cloud-agent/stream-ticket.ts
  • apps/web/src/lib/organizations/organization-group-policy-context.server.ts
  • apps/web/src/lib/organizations/organization-group-policy-context.server.test.ts
  • apps/web/src/lib/organizations/organizations.ts
  • apps/web/src/lib/organizations/organizations.test.ts
  • apps/web/src/routers/active-sessions-router.ts
  • apps/web/src/routers/active-sessions-router.test.ts
  • apps/web/src/routers/user-router.test.ts
  • dev/seed/app/w4c-org-pair.ts
  • packages/cloud-agent-sdk/src/user-web-connection.test.ts
  • packages/worker-utils/src/kilo-token-auth.ts
  • packages/worker-utils/src/kilo-token-auth.test.ts
  • services/cloud-agent-next/src/auth.ts
  • services/cloud-agent-next/src/auth.test.ts
  • services/cloud-agent-next/src/index.ts
  • services/cloud-agent-next/src/middleware/auth.ts
  • services/cloud-agent-next/src/middleware/auth.test.ts
  • services/cloud-agent-next/src/persistence/CloudAgentSession.ts
  • services/cloud-agent-next/src/persistence/CloudAgentSession-stream-ticket.test.ts
  • services/cloud-agent-next/src/router.test.ts
  • services/cloud-agent-next/src/server.ts
  • services/cloud-agent-next/src/server.test.ts
  • services/cloud-agent-next/src/server-stream-ticket.test.ts
  • services/cloud-agent-next/src/types.ts
  • services/cloud-agent-next/src/validate-kilo-token.ts
  • services/cloud-agent-next/src/validate-kilo-token.test.ts
  • services/cloud-agent-next/worker-configuration.d.ts
  • services/cloud-agent-next/wrangler.jsonc
  • services/cloud-agent-next/wrangler.test.jsonc
  • services/session-ingest/src/app.ts
  • services/session-ingest/src/dos/UserConnectionDO.ts
  • services/session-ingest/src/dos/UserConnectionDO.test.ts
  • services/session-ingest/src/dos/connection-ticket-do.ts
  • services/session-ingest/src/dos/connection-ticket-do.test.ts
  • services/session-ingest/src/index.ts
  • services/session-ingest/src/index.test.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.test.ts
  • services/session-ingest/src/routes/api.ts
  • services/session-ingest/src/routes/api.test.ts
  • services/session-ingest/worker-configuration.d.ts
  • services/session-ingest/wrangler.jsonc
  • services/session-ingest/wrangler.test.jsonc

Fix these issues in Kilo Cloud

Previous review (commit ac56293)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

Account deletion reauth skips expiry and attempt limits, and one-use ingest tickets are replayed on unexpected reconnect.

Overview

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

CRITICAL

File Line Issue
apps/web/src/routers/user-router.ts 951 consumeSignInCode skips expiry and the 5-attempt budget
packages/cloud-agent-sdk/src/user-web-connection.ts 419 One-use ingest ticket is reused on unexpected 1006 reconnect

WARNING

File Line Issue
apps/web/src/routers/user-router.ts 951 Code is consumed before performGdprRemoval succeeds
apps/web/src/routers/user-router.ts 917 Production provider_not_configured stamps cooldown and returns success
services/cloud-agent-next/src/persistence/StreamTicketNonceDO.ts 12 Nonce DOs never expire or delete storage
Files Reviewed (70 files)
  • apps/web/src/routers/user-router.ts - 3 issues
  • packages/cloud-agent-sdk/src/user-web-connection.ts - 1 issue
  • services/cloud-agent-next/src/persistence/StreamTicketNonceDO.ts - 1 issue
  • apps/extension/entrypoints/sidepanel/agents-provider.tsx
  • apps/extension/src/shared/extension-agent-session-manager.ts
  • apps/extension/src/shared/extension-agent-session-manager.test.ts
  • apps/extension/tests/e2e/agents-fixture.ts
  • apps/mobile/src/components/agents/mobile-session-manager.ts
  • apps/mobile/src/components/agents/mobile-session-manager.test.ts
  • apps/mobile/src/components/agents/user-web-connection-provider.tsx
  • apps/mobile/src/components/agents/user-web-connection-provider.test.ts
  • apps/mobile/src/components/agents/user-web-connection-provider.mounted.test.tsx
  • apps/mobile/src/components/profile-screen.tsx
  • apps/mobile/src/components/profile-screen.mounted.test.tsx
  • apps/mobile/src/components/use-delete-account.ts
  • apps/mobile/src/lib/auth/auth-context.tsx
  • apps/mobile/src/lib/auth/auth-context.test.ts
  • apps/mobile/src/lib/auth/auth-context.test.tsx
  • apps/mobile/src/lib/auth/credentials.ts
  • apps/mobile/src/lib/auth/credentials.test.ts
  • apps/mobile/src/lib/trpc.test.ts
  • apps/web/src/components/cloud-agent-next/CloudAgentProvider.tsx
  • apps/web/src/components/code-reviews/CodeReviewStreamView.tsx
  • apps/web/src/components/code-reviews/fetch-stream-ticket.ts
  • apps/web/src/components/code-reviews/fetch-stream-ticket.test.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.ts
  • apps/web/src/lib/cloud-agent-next/cloud-agent-client.test.ts
  • apps/web/src/lib/cloud-agent/stream-ticket.ts
  • apps/web/src/lib/organizations/organization-group-policy-context.server.ts
  • apps/web/src/lib/organizations/organization-group-policy-context.server.test.ts
  • apps/web/src/lib/organizations/organizations.ts
  • apps/web/src/lib/organizations/organizations.test.ts
  • apps/web/src/routers/active-sessions-router.ts
  • apps/web/src/routers/active-sessions-router.test.ts
  • apps/web/src/routers/user-router.test.ts
  • dev/seed/app/w4c-org-pair.ts
  • packages/cloud-agent-sdk/src/user-web-connection.test.ts
  • packages/worker-utils/src/kilo-token-auth.ts
  • packages/worker-utils/src/kilo-token-auth.test.ts
  • services/cloud-agent-next/src/auth.ts
  • services/cloud-agent-next/src/auth.test.ts
  • services/cloud-agent-next/src/index.ts
  • services/cloud-agent-next/src/middleware/auth.ts
  • services/cloud-agent-next/src/middleware/auth.test.ts
  • services/cloud-agent-next/src/persistence/CloudAgentSession.ts
  • services/cloud-agent-next/src/persistence/CloudAgentSession-stream-ticket.test.ts
  • services/cloud-agent-next/src/router.test.ts
  • services/cloud-agent-next/src/server.ts
  • services/cloud-agent-next/src/server.test.ts
  • services/cloud-agent-next/src/server-stream-ticket.test.ts
  • services/cloud-agent-next/src/types.ts
  • services/cloud-agent-next/src/validate-kilo-token.ts
  • services/cloud-agent-next/src/validate-kilo-token.test.ts
  • services/cloud-agent-next/worker-configuration.d.ts
  • services/cloud-agent-next/wrangler.jsonc
  • services/cloud-agent-next/wrangler.test.jsonc
  • services/session-ingest/src/app.ts
  • services/session-ingest/src/dos/UserConnectionDO.ts
  • services/session-ingest/src/dos/UserConnectionDO.test.ts
  • services/session-ingest/src/dos/connection-ticket-do.ts
  • services/session-ingest/src/dos/connection-ticket-do.test.ts
  • services/session-ingest/src/index.ts
  • services/session-ingest/src/index.test.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.ts
  • services/session-ingest/src/middleware/kilo-jwt-auth.test.ts
  • services/session-ingest/src/routes/api.ts
  • services/session-ingest/src/routes/api.test.ts
  • services/session-ingest/worker-configuration.d.ts
  • services/session-ingest/wrangler.jsonc
  • services/session-ingest/wrangler.test.jsonc

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 116.3K · Output: 5.1K · Cached: 304.4K

Review guidance: REVIEW.md from base branch main

Reserve the sign-in code before consuming it so an expired (>10 min) code
and the 5-attempt budget are enforced, matching the normal sign-in flow.
Treat production provider_not_configured as a send failure instead of
stamping a cooldown and returning a codeless success.
The ingest ticket is consumed once at the /api/user/web upgrade. Refresh it
before a reconnect so a non-auth-failure close (1006) does not retry with an
already-consumed ticket and loop forever. hasEverOpened scopes the refresh to
reconnects so the initial connect does not double-mint.
Set an alarm on nonce consume and delete the used flag in alarm() so nonce
DOs do not accumulate storage forever. The flag only needs to persist until
the ticket JWT expires, after which validateStreamTicket rejects the replay.
@iscekic

iscekic commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Rejected the remaining WARNING (user-router.ts:978 — code consumed before performGdprRemoval succeeds). This is a recorded, accepted race. The plan (Slice 7 step 2) directs consume-before-remove and accepts that a transient GDPR failure after consume uses the cooldown/Resend path. The precondition check assertUserCanBeSoftDeleted runs before consume, so the common case is caught. No new evidence; no code change.

@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 18, 2026

@pandemicsyn pandemicsyn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

Comment thread apps/web/src/routers/user-router.ts Outdated
Comment thread services/session-ingest/src/middleware/kilo-jwt-auth.ts Outdated
A failed performGdprRemoval burned the confirmation code, and the 1 h
request cooldown then blocked a new one. Release the reservation on
failure and consume the code after removal succeeds, so the client retry
with the same challenge works.
verifyKiloBearerAgainstCurrentPepper swallowed secret-store and database
failures into null, so Session Ingest answered an outage with 'Invalid or
expired token'. Let dependency errors propagate and map them to a
retryable 503; null now means an invalid or revoked credential only.
@iscekic
iscekic enabled auto-merge (squash) August 18, 2026 18:38
@iscekic
iscekic merged commit 06ebb35 into main Aug 18, 2026
49 checks passed
@iscekic
iscekic deleted the audit-w4c-revocation-9496 branch August 18, 2026 18:43
iscekic added a commit that referenced this pull request Aug 18, 2026
PR #5337 gave the mutation a required input, so builds already in the
stores get a 400 on the delete-account flow.

Make the input optional and keep the old support-ticket path for a
call without one: it emails the user and support, stamps the cooldown,
and deletes nothing, which is exactly what those builds tell the user
happened. A call with a challenge still reauthenticates and performs the
GDPR removal.

The branch carries a TODO for its removal.
iscekic added a commit that referenced this pull request Aug 18, 2026
* fix(active-sessions): restore getToken as a query for shipped clients

PR #5337 changed activeSessions.getToken from a query to a mutation.
Every installed client calls it as a query, so tRPC rejects the call:
405 on a plain GET, and 400 "Cannot mix procedure types in call" on any
batch that pairs it with user.getMe. The mobile app then shows
"Could not load your account".

Shipped app builds and installed extensions cannot be updated in step
with the server, so the procedure type must stay a query. The one-use
web-ticket body is unchanged.

* feat(active-sessions): add createWebTicket mutation, keep getToken query

Minting is not idempotent, so createWebTicket is a mutation and is the
path forward. Web, mobile and extension now call it.

getToken stays a query for clients that are already installed, with a
TODO for its removal. Shipped app builds and installed extensions cannot
update in step with the server, and tRPC answers a query-shaped call to
a mutation with 405, or fails the whole batch with 400 "Cannot mix
procedure types in call" when it sits beside a query.

Both procedures share one mintWebTicket handler.

* fix(user): accept requestAccountDeletion without a challenge again

PR #5337 gave the mutation a required input, so builds already in the
stores get a 400 on the delete-account flow.

Make the input optional and keep the old support-ticket path for a
call without one: it emails the user and support, stamps the cooldown,
and deletes nothing, which is exactly what those builds tell the user
happened. A call with a challenge still reauthenticates and performs the
GDPR removal.

The branch carries a TODO for its removal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants