Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5823c39
feat(worker-utils): add worker admin capability contract
pandemicsyn Nov 2, 2026
944eee6
feat(gastown): read worker admin capabilities and harden legacy claims
pandemicsyn Nov 2, 2026
43ffebf
feat(wasteland): read worker admin capabilities and harden legacy claims
pandemicsyn Nov 2, 2026
87a53ae
feat(admin): mint and introspect scoped worker admin capabilities
pandemicsyn Nov 2, 2026
c7e1e0f
refactor(admin): drop dead isAdmin delegation path and classify new s…
pandemicsyn Nov 2, 2026
65eabfa
feat(admin): issue per-procedure capability for worker support calls
pandemicsyn Nov 2, 2026
53df3d9
Merge remote-tracking branch 'origin/feat/admin-assurance-inventory' …
pandemicsyn Nov 2, 2026
b34e4e1
fix(admin): report why a hardened legacy claim was rejected
pandemicsyn Nov 2, 2026
41fc635
fix(admin): reject invalid capability headers on workers
pandemicsyn Nov 2, 2026
515695a
feat(admin): emit tRPC worker admin telemetry
pandemicsyn Nov 2, 2026
58f522a
refactor(admin): clamp capability ttl to the browser bound
pandemicsyn Nov 2, 2026
a212f39
fix(worker-utils): make capability module portable for all typecheck …
pandemicsyn Nov 2, 2026
0b873a8
fix(gastown): bind admin bypass to the named town and gate capability…
pandemicsyn Nov 2, 2026
3b60e94
fix(wasteland): align worker admin telemetry and cover ownership deci…
pandemicsyn Nov 2, 2026
22f1d21
fix(admin): require a session step-up before minting worker capabilities
pandemicsyn Nov 2, 2026
e0ee250
test(gastown): exercise real procedures and audit only used bypasses
pandemicsyn Nov 2, 2026
b17f9cf
test(wasteland): cover real admin procedures and align telemetry
pandemicsyn Nov 2, 2026
1a4441a
fix(admin): reuse scoped browser capabilities and refuse guard user d…
pandemicsyn Nov 2, 2026
68a7616
chore(workers): widen generated admin env types
pandemicsyn Nov 2, 2026
b29f4b7
test(wasteland): cover owner-only access gate
pandemicsyn Nov 2, 2026
0db16a6
test(wasteland): type the org member fixture
pandemicsyn Nov 2, 2026
5b4d76b
refactor(worker-utils): split the client-safe admin action allowlist
pandemicsyn Nov 2, 2026
f0d4839
fix(gastown): restore admin parity and audit town denials once
pandemicsyn Nov 2, 2026
703a6cb
test(wasteland): cover service-scope rejection
pandemicsyn Nov 2, 2026
ec160d8
fix(admin): filter capability actions and widen concurrent requests
pandemicsyn Nov 2, 2026
dfaff1d
fix(admin): scope town auth by action target and bound caches
pandemicsyn Nov 3, 2026
4e07d97
fix(gastown): bind user-target town routes to the named owner
pandemicsyn Nov 3, 2026
d544c53
fix(gastown): bind user capabilities only after ordinary ownership
pandemicsyn Nov 3, 2026
3122785
fix(gastown): remove the pre-ownership user-target gate
pandemicsyn Nov 3, 2026
0c10c41
test(admin): prove capability expiry re-mint and audit denied user ta…
pandemicsyn Nov 3, 2026
dedbd2c
Merge remote-tracking branch 'origin/feat/admin-mfa-step-up' into fea…
pandemicsyn Nov 3, 2026
44710f8
Merge remote-tracking branch 'origin/feat/admin-mfa-step-up' into fea…
pandemicsyn Nov 4, 2026
e7296c2
Merge remote-tracking branch 'origin/feat/admin-mfa-step-up' into fea…
pandemicsyn Nov 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ ordinary local startup does not require this credential.
- `CLOUD_AGENT_RESOURCE_TOKENS_ENABLED` - Default-off family gate for Cloud Agent Next request control and workflow control tokens; requires the master and exact `true`. [SERVER]
- `GASTOWN_RESOURCE_TOKENS_ENABLED` - Default-off family gate for Gastown control tokens; requires the master and exact `true`. This is not a safe-activation declaration: known ingest-audience and live-token-transport blockers remain deferred. [SERVER]
- `WASTELAND_RESOURCE_TOKENS_ENABLED` - Default-off family gate for Wasteland control tokens; requires the master and exact `true`. [SERVER]
- `WORKER_ADMIN_LEGACY_CLAIM_ISSUANCE` - `on` by default. Controls whether `/api/gastown/token`, `/api/wasteland/token`, Wasteland server resolution, and the admin Gastown REST client still put `isAdmin` on the ordinary Worker identity token. Set to `off` only during the legacy drain; Workers independently cap any legacy `isAdmin` claim at one hour and reject dateless claims. [SERVER]
- `WORKER_ADMIN_CAPABILITY_ISSUANCE` - Default off; requires exact `on`. Enables minting short-lived ES256 Worker admin capabilities from a browser session endpoint. Must stay off until session-bound MFA step-up (PR2) is live; the interim actor adapter always fails closed. [SERVER]
- `WORKER_ADMIN_CAPABILITY_PRIVATE_JWK` - `[SECRET]` ES256 private JWK (with `kid`) used to sign Worker admin capabilities. Never commit a private key; rotate by publishing a new `kid` before signing with it. [SECRET]
- `WORKER_ADMIN_CAPABILITY_PUBLIC_KEYS` - Worker-side JSON JWKS (`{"keys":[...]}`) of public capability keys, including retired keys during rotation. [SERVER]
- `LEGACY_ADMIN_CLAIM_MODE` - Worker-side `honor` (default), `observe`, or `reject`. `honor` keeps legacy `isAdmin` support access; `observe` grants but emits `observe_would_deny`; `reject` stops granting legacy admin. Applies to both Gastown and Wasteland. [SERVER]
- `CHAT_RESOURCE_TOKENS_ENABLED` - Default-off family gate for fresh chat resource issuance; requires the master and exact `true`. Validated modern devices retain chat/event-service/notifications issuance after rollback, capped by one hour and parent expiry. [SERVER]
- `DELEGATED_RESOURCE_TOKENS_ENABLED` - Default-off family gate for explicit API, gateway, and attribution delegation, including the organization user-token resource route; requires the master and exact `true`. Disabled explicit delegation remains unavailable. [SERVER]
- `WORKFLOW_GATEWAY_RESOURCE_TOKENS_ENABLED` - Default-off family gate for server workflow gateway tokens; requires the master and exact `true`. [SERVER]
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"form-data": "4.0.6",
"google-auth-library": "10.6.2",
"jotai": "2.18.1",
"jose": "catalog:",
"jotai-minidb": "0.0.8",
"js-yaml": "4.3.2",
"jsonwebtoken": "catalog:",
Expand Down
84 changes: 84 additions & 0 deletions apps/web/src/app/api/gastown/token/route.real-guard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
jest.mock('@kilocode/web-shared/lib/user/server', () => ({
getUserFromAuth: jest.fn(),
getUserFromSessionForCredentialIssuance: jest.fn(),
}));
jest.mock('@kilocode/web-shared/lib/drizzle', () => ({
db: { query: { kilocode_users: { findFirst: jest.fn() } } },
}));
jest.mock('@/lib/gastown/feature-flags', () => ({
isGastownEnabled: jest.fn(async () => true),
}));
jest.mock('@kilocode/web-shared/lib/organizations/organizations', () => ({
getUserOrgMemberships: jest.fn(async () => []),
}));
jest.mock('@kilocode/web-shared/lib/tokens', () => ({
generateApiToken: jest.fn(() => 'identity-token'),
}));
jest.mock('@kilocode/web-shared/lib/admin/admin-access-log', () => ({
recordKiloAdminElevationForRequest: jest.fn(async () => undefined),
serviceTarget: (id: string) => `service:${id}`,
}));
jest.mock('@kilocode/web-shared/lib/config.server', () => ({
isWorkerAdminLegacyClaimIssuanceEnabled: jest.fn(() => false),
isWorkerAdminCapabilityIssuanceEnabled: jest.fn(() => true),
}));
jest.mock('@/lib/worker-admin/capability-issuer', () => {
const actual = jest.requireActual('@/lib/worker-admin/capability-issuer');
return {
...actual,
issueWorkerAdminCapability: jest.fn(),
};
});

import {
getUserFromAuth,
getUserFromSessionForCredentialIssuance,
} from '@kilocode/web-shared/lib/user/server';
import { db } from '@kilocode/web-shared/lib/drizzle';
import { issueWorkerAdminCapability } from '@/lib/worker-admin/capability-issuer';
import { POST } from './route';

const user = { id: 'user-1', is_admin: true, api_token_pepper: 'pepper-1' };
const body = JSON.stringify({ adminCapability: { target: { kind: 'town', id: 'town-1' } } });

function request(headers: Record<string, string>): Request {
return new Request('https://app.example.test/api/gastown/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body,
});
}

beforeEach(() => {
jest.clearAllMocks();
jest.mocked(getUserFromAuth).mockResolvedValue({ user, tokenSource: 'session' } as never);
});

describe('gastown token route with the real actor guard', () => {
it('refuses a bearer credential before reading the session', async () => {
const response = await POST(request({ Authorization: 'Bearer personal-api-key' }));
expect(response.status).toBe(401);
await expect(response.json()).resolves.toMatchObject({ code: 'session_required' });
expect(getUserFromSessionForCredentialIssuance).not.toHaveBeenCalled();
expect(issueWorkerAdminCapability).not.toHaveBeenCalled();
});

it('refuses a session without fresh step-up', async () => {
jest
.mocked(getUserFromSessionForCredentialIssuance)
.mockResolvedValue({ user: { id: 'user-1' } } as never);
jest.mocked(db.query.kilocode_users.findFirst).mockResolvedValue({
id: 'user-1',
is_admin: true,
is_super_admin: false,
api_token_pepper: 'pepper-1',
blocked_at: null,
google_user_email: 'admin@example.com',
} as never);

const response = await POST(request({}));
expect(response.status).toBe(403);
await expect(response.json()).resolves.toMatchObject({ code: 'step_up_required' });
expect(issueWorkerAdminCapability).not.toHaveBeenCalled();
});
});
158 changes: 158 additions & 0 deletions apps/web/src/app/api/gastown/token/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
jest.mock('@kilocode/web-shared/lib/user/server', () => ({
getUserFromAuth: jest.fn(),
}));
jest.mock('@/lib/gastown/feature-flags', () => ({
isGastownEnabled: jest.fn(async () => true),
}));
jest.mock('@kilocode/web-shared/lib/organizations/organizations', () => ({
getUserOrgMemberships: jest.fn(async () => []),
}));
jest.mock('@kilocode/web-shared/lib/tokens', () => ({
generateApiToken: jest.fn(() => 'identity-token'),
}));
jest.mock('@kilocode/web-shared/lib/admin/admin-access-log', () => ({
recordKiloAdminElevationForRequest: jest.fn(async () => undefined),
serviceTarget: (id: string) => `service:${id}`,
}));
jest.mock('@kilocode/web-shared/lib/config.server', () => ({
isWorkerAdminLegacyClaimIssuanceEnabled: jest.fn(() => true),
isWorkerAdminCapabilityIssuanceEnabled: jest.fn(() => true),
INTERNAL_API_SECRET: 'internal-secret',
}));
jest.mock('@/lib/worker-admin/actor-guard', () => ({
requireWorkerAdminActor: jest.fn(),
WorkerAdminActorError: class WorkerAdminActorError extends Error {
constructor(
public status: number,
public code: string,
message: string
) {
super(message);
}
},
}));
jest.mock('@/lib/worker-admin/capability-issuer', () => ({
AdminCapabilityRequestSchema: jest.requireActual('@/lib/worker-admin/capability-issuer')
.AdminCapabilityRequestSchema,
WorkerAdminCapabilityIssuanceError: class WorkerAdminCapabilityIssuanceError extends Error {},
issueWorkerAdminCapability: jest.fn(),
}));

import { getUserFromAuth } from '@kilocode/web-shared/lib/user/server';
import { generateApiToken } from '@kilocode/web-shared/lib/tokens';
import { recordKiloAdminElevationForRequest } from '@kilocode/web-shared/lib/admin/admin-access-log';
import { isWorkerAdminLegacyClaimIssuanceEnabled } from '@kilocode/web-shared/lib/config.server';
import { requireWorkerAdminActor, WorkerAdminActorError } from '@/lib/worker-admin/actor-guard';
import { issueWorkerAdminCapability } from '@/lib/worker-admin/capability-issuer';
import { POST } from './route';

const user = { id: 'user-1', is_admin: true, api_token_pepper: 'pepper-1' };

function request(body?: unknown): Request {
return new Request('https://app.example.test/api/gastown/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: body === undefined ? null : JSON.stringify(body),
});
}

beforeEach(() => {
jest.clearAllMocks();
jest.mocked(getUserFromAuth).mockResolvedValue({ user, tokenSource: 'session' } as never);
jest.mocked(isWorkerAdminLegacyClaimIssuanceEnabled).mockReturnValue(true);
});

describe('POST /api/gastown/token', () => {
it('mints the ordinary identity token with the legacy admin claim by default', async () => {
const response = await POST(request());
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({ token: 'identity-token' });
expect(generateApiToken).toHaveBeenCalledWith(
user,
expect.objectContaining({ isAdmin: true, gastownAccess: true }),
{ expiresIn: 3600 }
);
expect(issueWorkerAdminCapability).not.toHaveBeenCalled();
expect(recordKiloAdminElevationForRequest).toHaveBeenCalledWith(
expect.objectContaining({ reason: 'service_token_mint' })
);
});

it('does not record a legacy elevation when a capability body is sent', async () => {
jest.mocked(requireWorkerAdminActor).mockResolvedValue({
actor: { assurance: { userId: 'user-1' } },
sessionBinding: { apiTokenPepper: 'pepper-1', webSessionPepper: null },
} as never);
jest.mocked(issueWorkerAdminCapability).mockResolvedValue({
capability: 'capability-token',
capabilityExpiresAt: '2026-01-01T00:05:00.000Z',
jti: 'jti-1',
actions: ['trpc:gastown.getTown'],
});
await POST(request({ adminCapability: { target: { kind: 'town', id: 'town-1' } } }));
expect(recordKiloAdminElevationForRequest).not.toHaveBeenCalledWith(
expect.objectContaining({ reason: 'service_token_mint' })
);
});

it('stops issuing the legacy admin claim when the flag is off', async () => {
jest.mocked(isWorkerAdminLegacyClaimIssuanceEnabled).mockReturnValue(false);
await POST(request());
expect(generateApiToken).toHaveBeenCalledWith(
user,
expect.not.objectContaining({ isAdmin: true }),
{ expiresIn: 3600 }
);
});

it('mints a capability only after the interim actor guard passes', async () => {
jest
.mocked(requireWorkerAdminActor)
.mockRejectedValue(
new WorkerAdminActorError(403, 'step_up_required', 'Fresh multi-factor step-up is required')
);
const response = await POST(
request({ adminCapability: { target: { kind: 'town', id: 'town-1' } } })
);
expect(response.status).toBe(403);
await expect(response.json()).resolves.toMatchObject({ code: 'step_up_required' });
expect(issueWorkerAdminCapability).not.toHaveBeenCalled();
});

it('returns the capability once the guard passes', async () => {
jest.mocked(requireWorkerAdminActor).mockResolvedValue({
actor: { assurance: { userId: 'user-1' } },
sessionBinding: { apiTokenPepper: 'pepper-1', webSessionPepper: null },
} as never);
jest.mocked(issueWorkerAdminCapability).mockResolvedValue({
capability: 'capability-token',
capabilityExpiresAt: '2026-01-01T00:05:00.000Z',
jti: 'jti-1',
actions: ['trpc:gastown.getTown'],
});
const response = await POST(
request({ adminCapability: { target: { kind: 'town', id: 'town-1' } } })
);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({
capability: 'capability-token',
capabilityExpiresAt: '2026-01-01T00:05:00.000Z',
});
});

it('ignores an unrelated JSON body like it did before capabilities', async () => {
const response = await POST(request({}));
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({ token: 'identity-token' });
expect(requireWorkerAdminActor).not.toHaveBeenCalled();
expect(recordKiloAdminElevationForRequest).toHaveBeenCalledWith(
expect.objectContaining({ reason: 'service_token_mint' })
);
});

it('rejects a malformed capability request', async () => {
const response = await POST(request({ adminCapability: { target: { kind: 'nope' } } }));
expect(response.status).toBe(400);
expect(requireWorkerAdminActor).not.toHaveBeenCalled();
});
});
Loading
Loading