Repository navigation
feat(admin): close worker admin support access with scoped capabilities #9616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
+5,228
−394
Open
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
5823c39
feat(worker-utils): add worker admin capability contract
pandemicsyn 944eee6
feat(gastown): read worker admin capabilities and harden legacy claims
pandemicsyn 43ffebf
feat(wasteland): read worker admin capabilities and harden legacy claims
pandemicsyn 87a53ae
feat(admin): mint and introspect scoped worker admin capabilities
pandemicsyn c7e1e0f
refactor(admin): drop dead isAdmin delegation path and classify new s…
pandemicsyn 65eabfa
feat(admin): issue per-procedure capability for worker support calls
pandemicsyn 53df3d9
Merge remote-tracking branch 'origin/feat/admin-assurance-inventory' …
pandemicsyn b34e4e1
fix(admin): report why a hardened legacy claim was rejected
pandemicsyn 41fc635
fix(admin): reject invalid capability headers on workers
pandemicsyn 515695a
feat(admin): emit tRPC worker admin telemetry
pandemicsyn 58f522a
refactor(admin): clamp capability ttl to the browser bound
pandemicsyn a212f39
fix(worker-utils): make capability module portable for all typecheck …
pandemicsyn 0b873a8
fix(gastown): bind admin bypass to the named town and gate capability…
pandemicsyn 3b60e94
fix(wasteland): align worker admin telemetry and cover ownership deci…
pandemicsyn 22f1d21
fix(admin): require a session step-up before minting worker capabilities
pandemicsyn e0ee250
test(gastown): exercise real procedures and audit only used bypasses
pandemicsyn b17f9cf
test(wasteland): cover real admin procedures and align telemetry
pandemicsyn 1a4441a
fix(admin): reuse scoped browser capabilities and refuse guard user d…
pandemicsyn 68a7616
chore(workers): widen generated admin env types
pandemicsyn b29f4b7
test(wasteland): cover owner-only access gate
pandemicsyn 0db16a6
test(wasteland): type the org member fixture
pandemicsyn 5b4d76b
refactor(worker-utils): split the client-safe admin action allowlist
pandemicsyn f0d4839
fix(gastown): restore admin parity and audit town denials once
pandemicsyn 703a6cb
test(wasteland): cover service-scope rejection
pandemicsyn ec160d8
fix(admin): filter capability actions and widen concurrent requests
pandemicsyn dfaff1d
fix(admin): scope town auth by action target and bound caches
pandemicsyn 4e07d97
fix(gastown): bind user-target town routes to the named owner
pandemicsyn d544c53
fix(gastown): bind user capabilities only after ordinary ownership
pandemicsyn 3122785
fix(gastown): remove the pre-ownership user-target gate
pandemicsyn 0c10c41
test(admin): prove capability expiry re-mint and audit denied user ta…
pandemicsyn dedbd2c
Merge remote-tracking branch 'origin/feat/admin-mfa-step-up' into fea…
pandemicsyn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| 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 { 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(); | ||
| }); | ||
|
|
||
| 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(); | ||
| }); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.