import { scanForSecrets } from '../src/rules/secret-patterns.js'; interface TestCase { name: string; input: string; shouldMatch: boolean; } // All test tokens are intentionally fake — they match detection patterns // but are not real credentials. Prefixed with FAKE/TEST where possible. const TEST_CASES: TestCase[] = [ // Should detect { name: 'OpenAI key', input: 'My key is sk-FAKEtestkey01234567890abcdef', shouldMatch: true }, { name: 'Anthropic key', input: 'Using sk-ant-FAKE-testkey0123456789abcdef', shouldMatch: true }, { name: 'GitHub PAT', input: 'Token: ' + buildGitHubToken('ghp_'), shouldMatch: true }, { name: 'GitHub OAuth', input: 'Auth: ' + buildGitHubToken('gho_'), shouldMatch: true }, { name: 'GitHub App Token', input: 'App: ' + buildGitHubToken('ghu_'), shouldMatch: true }, { name: 'AWS Access Key', input: 'Key: AKIAFAKETEST00000000', shouldMatch: true }, { name: 'AWS Secret Key', input: 'aws_secret_access_key=FAKEwJalrXUtnFEMI0K7MDENG0bPxRfiCYEXAMPLE', shouldMatch: true }, { name: 'Slack token', input: buildSlackToken(), shouldMatch: true, }, { name: 'Slack webhook', input: 'https://hooks.slack.com/services/TFAKETEST1/BFAKETEST1/FAKEtestWebhook123', shouldMatch: true }, { name: 'JWT', input: buildJWT(), shouldMatch: true }, { name: 'RSA private key', input: '-----BEGIN RSA PRIVATE KEY-----\nFAKEdata...', shouldMatch: true }, { name: 'EC private key', input: '-----BEGIN EC PRIVATE KEY-----\nFAKEdata...', shouldMatch: true }, { name: 'Generic API key', input: "api_key='FAKE0test0key01234567890abcdef'", shouldMatch: true }, { name: 'Google API key', input: buildGoogleKey(), shouldMatch: true }, { name: 'Stripe live key', input: buildStripeKey(), shouldMatch: true }, { name: 'Stripe restricted key', input: buildStripeRKey(), shouldMatch: true }, { name: 'npm token', input: 'Token: ' + buildNpmToken(), shouldMatch: true }, { name: 'SendGrid key', input: buildSendGridKey(), shouldMatch: true }, // Should NOT detect (false positives) { name: 'Normal text', input: 'Can you help me write a Python function?', shouldMatch: false }, { name: 'API key discussion', input: 'You need to set your API key in the environment variables', shouldMatch: false }, { name: 'Short string', input: 'sk-abc', shouldMatch: false }, { name: 'Code comment', input: '# Replace YOUR_API_KEY with your actual key', shouldMatch: false }, { name: 'Placeholder text', input: 'Set OPENAI_API_KEY=', shouldMatch: false }, ]; // Build test tokens dynamically to avoid GitHub push protection // Each function generates a token matching the exact length required by its regex pattern function buildGitHubToken(prefix: string): string { // Pattern: /ghp_[a-zA-Z0-9]{36}/ — needs exactly 36 chars after prefix return prefix + 'FAKE'.padEnd(36, '0'); } function buildSlackToken(): string { return ['xoxb', '0000000000', '0000000000', 'FAKEtestToken00'].join('-'); } function buildJWT(): string { // Three base64url segments that form a valid JWT structure const header = 'eyJGQUtFIjoiZmFrZSJ9'; // {"FAKE":"fake"} const payload = 'eyJGQUtFIjoiZmFrZSIsIm5hbWUiOiJ0ZXN0In0'; // {"FAKE":"fake","name":"test"} const sig = 'FAKE_TEST_SIGNATURE_00000000'; return `${header}.${payload}.${sig}`; } function buildGoogleKey(): string { // Pattern: /AIza[0-9A-Za-z_-]{35}/ return 'AIza' + 'FAKE_test_GoogleKey'.padEnd(35, '0'); } function buildStripeKey(): string { // Pattern: /[sr]k_(live|test)_[a-zA-Z0-9]{20,}/ const prefix = ['sk', 'live'].join('_') + '_'; return prefix + 'FAKE'.padEnd(24, '0'); } function buildStripeRKey(): string { const prefix = ['rk', 'test'].join('_') + '_'; return prefix + 'FAKE'.padEnd(24, '0'); } function buildSendGridKey(): string { // Pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/ const part1 = 'FAKE_test'.padEnd(22, '0'); const part2 = 'FAKE_test_signature'.padEnd(43, '0'); return `SG.${part1}.${part2}`; } function buildNpmToken(): string { // Pattern: /npm_[a-zA-Z0-9]{36}/ — needs exactly 36 chars after prefix return 'npm_' + 'FAKEtestNPM'.padEnd(36, '0'); } export async function runSecretsTests(): Promise<{ suite: string; passed: number; failed: number; errors: string[] }> { let passed = 0; let failed = 0; const errors: string[] = []; for (const tc of TEST_CASES) { const result = scanForSecrets(tc.input); const ok = result.matched === tc.shouldMatch; if (ok) { passed++; } else { failed++; const direction = tc.shouldMatch ? 'should have been detected but was not' : 'should NOT have been detected but was'; errors.push(`[${tc.name}] ${direction}`); } } return { suite: 'Secrets Scanner', passed, failed, errors }; }