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
639 changes: 0 additions & 639 deletions src/security-module-coverage.test.ts

This file was deleted.

89 changes: 89 additions & 0 deletions src/services/credentials/anthropic-credential-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
jest.mock('../../logger', () => ({
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
}));

jest.mock('../../env-utils', () => ({
getLowerCaseProcessEnvValue: jest.fn(),
getConfigEnvValue: jest.fn(),
}));

import { buildAnthropicCredentialEnv } from './anthropic-credential-env';
import type { WrapperConfig } from '../../types';
import { getLowerCaseProcessEnvValue } from '../../env-utils';

const mockGetLowerCaseProcessEnvValue = getLowerCaseProcessEnvValue as jest.MockedFunction<
typeof getLowerCaseProcessEnvValue
>;

const baseConfig = {} as WrapperConfig;
const proxyIp = '172.30.0.30';

describe('buildAnthropicCredentialEnv', () => {
beforeEach(() => {
mockGetLowerCaseProcessEnvValue.mockReturnValue('');
});

afterEach(() => {
jest.clearAllMocks();
});

it('returns empty object when no anthropic credentials configured', () => {
const result = buildAnthropicCredentialEnv({ config: baseConfig, proxyIp });
expect(result).toEqual({});
});

it('returns env additions when anthropicApiKey is set', () => {
const config = { ...baseConfig, anthropicApiKey: 'sk-ant-real-key' } as WrapperConfig;
const result = buildAnthropicCredentialEnv({ config, proxyIp });
expect(result.ANTHROPIC_BASE_URL).toBe(`http://${proxyIp}:10001`);
// Placeholder must pass Claude Code's sk-ant- prefix validation
expect(result.ANTHROPIC_AUTH_TOKEN).toMatch(/^sk-ant-/);
expect(result.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh');
});

it('returns env additions when auth type is github-oidc with anthropic provider', () => {
mockGetLowerCaseProcessEnvValue.mockImplementation((key: string) => {
if (key === 'AWF_AUTH_TYPE') return 'github-oidc';
if (key === 'AWF_AUTH_PROVIDER') return 'anthropic';
return '';
});
const result = buildAnthropicCredentialEnv({ config: baseConfig, proxyIp });
expect(result.ANTHROPIC_BASE_URL).toContain(proxyIp);
expect(result.ANTHROPIC_AUTH_TOKEN).toBeDefined();
});

it('returns empty object when auth type is github-oidc but provider is not anthropic', () => {
mockGetLowerCaseProcessEnvValue.mockImplementation((key: string) => {
if (key === 'AWF_AUTH_TYPE') return 'github-oidc';
if (key === 'AWF_AUTH_PROVIDER') return 'openai';
return '';
});
const result = buildAnthropicCredentialEnv({ config: baseConfig, proxyIp });
expect(result).toEqual({});
});

it('ANTHROPIC_AUTH_TOKEN placeholder has sk-ant- prefix for Claude Code key-format validation', () => {
const config = { ...baseConfig, anthropicApiKey: 'sk-ant-test' } as WrapperConfig;
const result = buildAnthropicCredentialEnv({ config, proxyIp });
expect(result.ANTHROPIC_AUTH_TOKEN).toMatch(/^sk-ant-/);
});

it('real anthropicApiKey is NOT present in agent env (credential isolation)', () => {
const realKey = 'sk-ant-real-secret-key';
const config = { ...baseConfig, anthropicApiKey: realKey } as WrapperConfig;
const result = buildAnthropicCredentialEnv({ config, proxyIp });
expect(Object.values(result)).not.toContain(realKey);
});

it('ANTHROPIC_API_KEY is NOT set in returned env (excluded for credential isolation)', () => {
const config = { ...baseConfig, anthropicApiKey: 'sk-ant-test' } as WrapperConfig;
const result = buildAnthropicCredentialEnv({ config, proxyIp });
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
});

it('routes to Anthropic port 10001 specifically', () => {
const config = { ...baseConfig, anthropicApiKey: 'sk-ant-test' } as WrapperConfig;
const result = buildAnthropicCredentialEnv({ config, proxyIp });
expect(result.ANTHROPIC_BASE_URL).toMatch(/:10001$/);
});
});
118 changes: 118 additions & 0 deletions src/services/credentials/copilot-credential-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
jest.mock('../../logger', () => ({
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
}));

jest.mock('../../env-utils', () => ({
getLowerCaseProcessEnvValue: jest.fn(),
getConfigEnvValue: jest.fn(),
}));

import { buildCopilotCredentialEnv } from './copilot-credential-env';
import type { WrapperConfig } from '../../types';
import { getConfigEnvValue } from '../../env-utils';
import { COPILOT_PLACEHOLDER_TOKEN } from '../../constants/placeholders';

const mockGetConfigEnvValue = getConfigEnvValue as jest.MockedFunction<typeof getConfigEnvValue>;

const baseConfig = {} as WrapperConfig;
const proxyIp = '172.30.0.30';

describe('buildCopilotCredentialEnv', () => {
beforeEach(() => {
mockGetConfigEnvValue.mockReturnValue(undefined);
});

afterEach(() => {
jest.clearAllMocks();
});

it('returns empty object when no copilot credentials configured', () => {
const result = buildCopilotCredentialEnv({ config: baseConfig, proxyIp });
expect(result).toEqual({});
});

it('returns env additions when copilotGithubToken is set', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_API_URL).toBe(`http://${proxyIp}:10002`);
expect(result.COPILOT_OFFLINE).toBe('true');
expect(result.COPILOT_TOKEN).toBe(COPILOT_PLACEHOLDER_TOKEN);
expect(result.COPILOT_GITHUB_TOKEN).toBe(COPILOT_PLACEHOLDER_TOKEN);
});

it('returns env additions when copilotProviderApiKey is set', () => {
const config = { ...baseConfig, copilotProviderApiKey: 'provider-key' } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_API_URL).toBeDefined();
expect(result.COPILOT_PROVIDER_API_KEY).toBe(COPILOT_PLACEHOLDER_TOKEN);
});

it('returns env additions when copilotProviderBaseUrl is set', () => {
const config = { ...baseConfig, copilotProviderBaseUrl: 'https://openrouter.ai/api' } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_API_URL).toBeDefined();
// Agent always sees the sidecar URL, never the real provider URL
expect(result.COPILOT_PROVIDER_BASE_URL).toBe(`http://${proxyIp}:10002`);
});

it('does NOT set COPILOT_GITHUB_TOKEN placeholder when only providerApiKey is given', () => {
const config = { ...baseConfig, copilotProviderApiKey: 'provider-key' } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_GITHUB_TOKEN).toBeUndefined();
});

it('does NOT set COPILOT_PROVIDER_API_KEY placeholder when no provider key given', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_PROVIDER_API_KEY).toBeUndefined();
});

it('sets COPILOT_PROVIDER_WIRE_API=responses for gpt-5 model', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
mockGetConfigEnvValue.mockImplementation((_: unknown, key: string) =>
key === 'COPILOT_MODEL' ? 'gpt-5' : undefined
);
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_PROVIDER_WIRE_API).toBe('responses');
});

it('sets COPILOT_PROVIDER_WIRE_API=responses for o3 model', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
mockGetConfigEnvValue.mockImplementation((_: unknown, key: string) =>
key === 'COPILOT_MODEL' ? 'o3-mini' : undefined
);
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_PROVIDER_WIRE_API).toBe('responses');
});

it('sets COPILOT_PROVIDER_WIRE_API=responses for openai/gpt-5 prefixed model', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
mockGetConfigEnvValue.mockImplementation((_: unknown, key: string) =>
key === 'COPILOT_MODEL' ? 'openai/gpt-5' : undefined
);
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_PROVIDER_WIRE_API).toBe('responses');
});

it('does not set COPILOT_PROVIDER_WIRE_API for non-gpt5/o3 models', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
mockGetConfigEnvValue.mockImplementation((_: unknown, key: string) =>
key === 'COPILOT_MODEL' ? 'claude-3-5-sonnet' : undefined
);
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_PROVIDER_WIRE_API).toBeUndefined();
});

it('real copilotGithubToken is NOT present in agent env (credential isolation)', () => {
const realToken = 'ghu_real_secret_token';
const config = { ...baseConfig, copilotGithubToken: realToken } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(Object.values(result)).not.toContain(realToken);
});

it('routes to Copilot port 10002 specifically', () => {
const config = { ...baseConfig, copilotGithubToken: 'ghu_token' } as WrapperConfig;
const result = buildCopilotCredentialEnv({ config, proxyIp });
expect(result.COPILOT_API_URL).toMatch(/:10002$/);
});
});
45 changes: 45 additions & 0 deletions src/services/credentials/gemini-credential-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
jest.mock('../../logger', () => ({
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
}));

import { buildGeminiCredentialEnv } from './gemini-credential-env';
import type { WrapperConfig } from '../../types';

const baseConfig = {} as WrapperConfig;
const proxyIp = '172.30.0.30';

describe('buildGeminiCredentialEnv', () => {
it('returns empty object when geminiApiKey is not set', () => {
const result = buildGeminiCredentialEnv({ config: baseConfig, proxyIp });
expect(result).toEqual({});
});

it('returns env additions when geminiApiKey is set', () => {
const config = { ...baseConfig, geminiApiKey: 'AIza-test-key' } as WrapperConfig;
const result = buildGeminiCredentialEnv({ config, proxyIp });
expect(result.GOOGLE_GEMINI_BASE_URL).toBe(`http://${proxyIp}:10003`);
expect(result.GEMINI_API_BASE_URL).toBe(`http://${proxyIp}:10003`);
expect(result.GEMINI_API_KEY).toBe('gemini-api-key-placeholder-for-credential-isolation');
});

it('real geminiApiKey is NOT present in agent env (credential isolation)', () => {
const realKey = 'AIza-real-secret-key';
const config = { ...baseConfig, geminiApiKey: realKey } as WrapperConfig;
const result = buildGeminiCredentialEnv({ config, proxyIp });
expect(Object.values(result)).not.toContain(realKey);
});

it('sets both GOOGLE_GEMINI_BASE_URL and GEMINI_API_BASE_URL for backward compatibility', () => {
const config = { ...baseConfig, geminiApiKey: 'AIza-test' } as WrapperConfig;
const result = buildGeminiCredentialEnv({ config, proxyIp });
expect(result.GOOGLE_GEMINI_BASE_URL).toBeDefined();
expect(result.GEMINI_API_BASE_URL).toBeDefined();
expect(result.GOOGLE_GEMINI_BASE_URL).toBe(result.GEMINI_API_BASE_URL);
});

it('routes to Gemini port 10003 specifically', () => {
const config = { ...baseConfig, geminiApiKey: 'AIza-test' } as WrapperConfig;
const result = buildGeminiCredentialEnv({ config, proxyIp });
expect(result.GOOGLE_GEMINI_BASE_URL).toMatch(/:10003$/);
});
});
44 changes: 44 additions & 0 deletions src/services/credentials/openai-credential-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
jest.mock('../../logger', () => ({
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
}));

import { buildOpenAiCredentialEnv } from './openai-credential-env';
import type { WrapperConfig } from '../../types';

const baseConfig = {} as WrapperConfig;
const proxyIp = '172.30.0.30';

describe('buildOpenAiCredentialEnv', () => {
it('returns empty object when openaiApiKey is not set', () => {
const result = buildOpenAiCredentialEnv({ config: baseConfig, proxyIp });
expect(result).toEqual({});
});

it('returns env additions when openaiApiKey is set', () => {
const config = { ...baseConfig, openaiApiKey: 'sk-real-key' } as WrapperConfig;
const result = buildOpenAiCredentialEnv({ config, proxyIp });
expect(result.OPENAI_BASE_URL).toBe(`http://${proxyIp}:10000`);
expect(result.OPENAI_API_KEY).toBe('sk-placeholder-for-api-proxy');
expect(result.CODEX_API_KEY).toBe('sk-placeholder-for-api-proxy');
});

it('real openaiApiKey is NOT present in agent env (credential isolation)', () => {
const realKey = 'sk-real-secret-key';
const config = { ...baseConfig, openaiApiKey: realKey } as WrapperConfig;
const result = buildOpenAiCredentialEnv({ config, proxyIp });
expect(Object.values(result)).not.toContain(realKey);
});

it('sets both OPENAI_API_KEY and CODEX_API_KEY for Codex WebSocket auth support', () => {
const config = { ...baseConfig, openaiApiKey: 'sk-test' } as WrapperConfig;
const result = buildOpenAiCredentialEnv({ config, proxyIp });
expect(result.OPENAI_API_KEY).toBeDefined();
expect(result.CODEX_API_KEY).toBeDefined();
});

it('routes to OpenAI port 10000 specifically', () => {
const config = { ...baseConfig, openaiApiKey: 'sk-test' } as WrapperConfig;
const result = buildOpenAiCredentialEnv({ config, proxyIp });
expect(result.OPENAI_BASE_URL).toMatch(/:10000$/);
});
});
36 changes: 36 additions & 0 deletions src/services/credentials/vertex-credential-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
jest.mock('../../logger', () => ({
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
}));

import { buildVertexCredentialEnv } from './vertex-credential-env';
import type { WrapperConfig } from '../../types';

const baseConfig = {} as WrapperConfig;
const proxyIp = '172.30.0.30';

describe('buildVertexCredentialEnv', () => {
it('returns empty object when googleApiKey is not set', () => {
const result = buildVertexCredentialEnv({ config: baseConfig, proxyIp });
expect(result).toEqual({});
});

it('returns env additions when googleApiKey is set', () => {
const config = { ...baseConfig, googleApiKey: 'AIza-test-key' } as WrapperConfig;
const result = buildVertexCredentialEnv({ config, proxyIp });
expect(result.GOOGLE_VERTEX_BASE_URL).toBe(`http://${proxyIp}:10004`);
expect(result.GOOGLE_API_KEY).toBe('google-api-key-placeholder-for-credential-isolation');
});

it('real googleApiKey is NOT present in agent env (credential isolation)', () => {
const realKey = 'AIza-real-vertex-secret-key';
const config = { ...baseConfig, googleApiKey: realKey } as WrapperConfig;
const result = buildVertexCredentialEnv({ config, proxyIp });
expect(Object.values(result)).not.toContain(realKey);
});

it('routes to Vertex AI port 10004 specifically', () => {
const config = { ...baseConfig, googleApiKey: 'AIza-test' } as WrapperConfig;
const result = buildVertexCredentialEnv({ config, proxyIp });
expect(result.GOOGLE_VERTEX_BASE_URL).toMatch(/:10004$/);
});
});
Loading
Loading