Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ Manage shared web env var additions and rotations with `pnpm web:env set <VARIAB
- `INTERNAL_API_SECRET` - Shared secret for internal API calls between services; used in `apps/web/src/lib/kiloclaw/cli-runs.test.ts`, `kiloclaw-router.test.ts`, dev seed scripts, and other service routers. `[SECRET]`
- `SUPPORT_API_SECRET` - Shared bearer token for Customer Support Automation (CSA) internal API calls. Cloud uses it to authorize CSA → Cloud `apps/web/src/app/api/internal/support/` and Cloud → CSA `POST /api/internal/cloud/users/gdpr-scrub`. A CSA compromise can also call Cloud deletion and Cloud can scrub CSA-local PII. Leak can look up any email and enqueue deletion for non-admin, non-bot, non-live-subscription customers; access disable is deferred to worker preflight and pending requests can be cancelled. Keep production values off preview deployments; rotate Cloud and CSA together. `[SECRET]`
- `BOUNDED_INTERNAL_SERVICE_TOKENS_ENABLED` - Set to exact `true` to enable modern, purpose-labelled internal assertions at the Phase 5.1 bounded Git broker, export, deletion, and Session Ingest callsites. Unset or any other value retains their existing legacy token formats. Enable only after compatible readers, including the dedicated GitHub disconnect audience, are deployed; generic human/control/runtime signers are not affected. [SERVER]
- `NATIVE_RESOURCE_TOKENS_ENABLED` - Set to exact `true` to permit fresh native adoption of separate one-hour API/gateway access tokens only for clients explicitly requesting `api-gateway-v1`, provided `SHARED_RESOURCE_TOKENS_ENABLED` is also exact `true`. Default-off. Unsupported clients keep legacy responses. Turning this flag off affects subsequent native issuance/refreshes only; active modern device credentials can continue receiving bounded control tokens while their owned device session and current user pepper remain valid. [SERVER]
- `SHARED_RESOURCE_TOKENS_ENABLED` - Master default-off readiness gate. Fresh modern producer issuance requires both this flag and its family flag below to be exact `true`; unset or any other value disables adoption. Native adoption separately requires `NATIVE_RESOURCE_TOKENS_ENABLED` and this master, independent of producer families; old CLI negotiation is unchanged. These flags do not revoke existing credentials. Valid modern device access credentials retain bounded control issuance after rollback, with current owned-session, pepper, and requested organization membership validation and a one-hour/parent-expiry cap. Chat likewise retains bounded three-audience issuance for validated modern devices; modern credentials never fall back to broad legacy tokens. Persisted modern workload renewal does not use adoption gates. Separately deployed readers/producers must be verified before activation. [SERVER]
- `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]
- `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, attribution, and HTML-deploy 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]
- `BENCHMARK_RESOURCE_TOKENS_ENABLED` - Default-off family gate for benchmark resource tokens; requires the master and exact `true`. [SERVER]
- `RUNTIME_ISOLATION_ENABLED` - Cloud Agent Worker rollout control for new modern control-plane sessions and worktree destinations. Exact `true` permits adoption; default/unset/other values reject it before durable work. Legacy attachments keep directory-shared Kilo runtimes. Persisted modern runtime authorization continues selecting per-session isolation after rollback, and the connected wrapper must advertise the isolation capability. Keep off during the automatic deployment wave and enable only after compatible Worker and wrapper versions are healthy. Foreground expiry recovery retains the same session identity and requires acknowledged idle transport retirement; this flag does not establish complete real-provider smoke coverage. See `docs/token-issuance-policy.md`, Phase 5.2 merge, automatic deployment, and activation. [SERVER]
- `CALLBACK_TOKEN_SECRET` - Secret for signing callback tokens. Required for local development. `[SECRET]`
- `INTERNAL_SECRET` - Alias/fallback for `INTERNAL_API_SECRET`; used in KiloClaw E2E scripts (`services/kiloclaw/e2e/`). `[SECRET]`

Expand Down
128 changes: 128 additions & 0 deletions apps/web/src/lib/auth/resource-delegation.servicecontrol.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { describe, expect, test } from '@jest/globals';
import jwt from 'jsonwebtoken';

const shared = { enabled: true, family: '' };
beforeEach(() => {
shared.enabled = true;
shared.family = '';
});
jest.mock('@/lib/config.server', () => ({
NEXTAUTH_SECRET: 'service-control-test-secret',
isResourceTokenIssuanceEnabled: (family: string) =>
shared.enabled && (!shared.family || shared.family === family),
}));
jest.mock('@/lib/user/server', () => ({ getUserFromSessionForCredentialIssuance: jest.fn() }));

import { generateCloudAgentWorkflowToken, generateWorkflowGatewayToken } from '@/lib/tokens';
import { defineTestUser } from '@/tests/helpers/user.helper';

describe('workflow service control tokens', () => {
test('uses bounded modern gateway workflow owner claims', () => {
const user = defineTestUser({ api_token_pepper: 'workflow-pepper' });
const token = generateWorkflowGatewayToken(user, {
organizationId: 'organization-id',
tokenSource: 'reviewer',
});
const claims = jwt.verify(token, 'service-control-test-secret') as jwt.JwtPayload;

expect(claims).toMatchObject({
aud: 'kilo-gateway',
kiloUserId: user.id,
apiTokenPepper: 'workflow-pepper',
organizationId: 'organization-id',
tokenSource: 'reviewer',
tokenPurpose: 'delegated-workload',
credentialExchange: false,
});
expect(claims).not.toHaveProperty('runtimeAdmission');
expect(claims.exp! - claims.iat!).toBe(60 * 60);
});

test('uses bounded modern automation admission', () => {
const user = defineTestUser({ api_token_pepper: 'workflow-pepper' });
const token = generateCloudAgentWorkflowToken(user, {
expiresIn: 300,
tokenSource: 'reviewer',
botId: 'reviewer',
});
const claims = jwt.verify(token, 'service-control-test-secret') as jwt.JwtPayload;
expect(claims).toMatchObject({
aud: 'cloud-agent-next',
tokenPurpose: 'internal-service',
credentialExchange: false,
runtimeAdmission: {
source: 'automation',
authorizationUserId: user.id,
authorizationPepper: 'workflow-pepper',
},
});
expect(claims.exp! - claims.iat!).toBe(300);
});

test('caps modern workflow admission to one hour', () => {
const user = defineTestUser({ api_token_pepper: 'workflow-pepper' });
const token = generateCloudAgentWorkflowToken(user, {
expiresIn: 5 * 365 * 24 * 60 * 60,
tokenSource: 'reviewer',
});
const claims = jwt.decode(token) as jwt.JwtPayload;

expect(claims.exp! - claims.iat!).toBe(60 * 60);
});

test('requires an authorization pepper for modern workflow admission', () => {
const user = defineTestUser({ api_token_pepper: 'workflow-pepper' });
const authorizationUser = defineTestUser({ api_token_pepper: null });

expect(() =>
generateCloudAgentWorkflowToken(user, {
expiresIn: 300,
tokenSource: 'reviewer',
authorizationUser,
})
).toThrow('current authorization pepper');
});

test('preserves the legacy workflow token shape when shared issuance is disabled', () => {
shared.enabled = false;
const user = defineTestUser({ api_token_pepper: 'workflow-pepper' });
const token = generateCloudAgentWorkflowToken(user, {
expiresIn: 300,
tokenSource: 'reviewer',
botId: 'reviewer',
});
const claims = jwt.verify(token, 'service-control-test-secret') as jwt.JwtPayload;
expect(claims).toMatchObject({
kiloUserId: user.id,
tokenSource: 'reviewer',
botId: 'reviewer',
});
expect(claims).not.toHaveProperty('tokenPurpose');
expect(claims.exp! - claims.iat!).toBe(300);
});
});

test.each(['cloud-agent-next', 'workflow-gateway'])(
'only enables the selected workflow family %s',
family => {
shared.family = family;
const user = defineTestUser({ api_token_pepper: 'workflow-pepper' });
const cloud = jwt.decode(
generateCloudAgentWorkflowToken(user, {
expiresIn: 300,
tokenSource: 'reviewer',
})
) as jwt.JwtPayload;
const gateway = jwt.decode(
generateWorkflowGatewayToken(user, {
tokenSource: 'reviewer',
})
) as jwt.JwtPayload;
expect(cloud.tokenPurpose).toBe(family === 'cloud-agent-next' ? 'internal-service' : undefined);
expect(gateway.tokenPurpose).toBe(
family === 'workflow-gateway' ? 'delegated-workload' : undefined
);
expect(cloud.exp! - cloud.iat!).toBe(300);
if (family === 'cloud-agent-next') expect(gateway).not.toHaveProperty('aud');
}
);
Loading
Loading