Initial commit: Opencode Filter
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Realistic Secret Corpus Test
|
||||
*
|
||||
* Validates detection accuracy on 100+ realistic secret examples.
|
||||
* Tests against real-world patterns from GitHub Secret Scanning,
|
||||
* TruffleHog, and GitLeaks test data.
|
||||
*
|
||||
* Target: >85% detection accuracy
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { readFileSync, readdirSync } from 'fs';
|
||||
import { join, basename } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { RegexEngine } from '../src/patterns/regex-engine';
|
||||
import { EntropyEngine } from '../src/entropy';
|
||||
import { SecretDetector } from '../src/detector';
|
||||
import type { DetectedSecret } from '../src/types';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const FIXTURES_DIR = join(__dirname, 'fixtures/realistic-secrets');
|
||||
|
||||
interface SecretExample {
|
||||
value: string;
|
||||
category: string;
|
||||
line: number;
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface CategoryResult {
|
||||
total: number;
|
||||
detected: number;
|
||||
accuracy: number;
|
||||
secrets: SecretExample[];
|
||||
}
|
||||
|
||||
interface CorpusResults {
|
||||
total: number;
|
||||
detected: number;
|
||||
accuracy: number;
|
||||
byCategory: Record<string, CategoryResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse fixture files and extract secret examples
|
||||
* Ignores comment lines (starting with #) and empty lines
|
||||
*/
|
||||
function loadSecretCorpus(): SecretExample[] {
|
||||
const files = readdirSync(FIXTURES_DIR).filter(f => f.endsWith('.txt'));
|
||||
const examples: SecretExample[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const category = basename(file, '.txt');
|
||||
const content = readFileSync(join(FIXTURES_DIR, file), 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
// Skip comments, empty lines, and continuation lines
|
||||
if (!line || line.startsWith('#')) continue;
|
||||
// Skip lines that are clearly continuation of multiline secrets
|
||||
if (line.startsWith('-----') || line.includes('= ') && !line.includes(':') && !line.includes('=')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
examples.push({
|
||||
value: line,
|
||||
category,
|
||||
line: i + 1,
|
||||
source: file,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return examples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a secret value is detected by the engine
|
||||
*/
|
||||
function isDetected(secret: SecretExample, detector: SecretDetector): boolean {
|
||||
const text = secret.value;
|
||||
const results = detector.detect(text);
|
||||
|
||||
// A secret is detected if any result overlaps with the secret value
|
||||
return results.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed detection results for a secret
|
||||
*/
|
||||
function getDetectionDetails(secret: SecretExample, detector: SecretDetector): DetectedSecret[] {
|
||||
const text = secret.value;
|
||||
return detector.detect(text);
|
||||
}
|
||||
|
||||
describe('Realistic Secret Corpus', () => {
|
||||
let detector: SecretDetector;
|
||||
let corpus: SecretExample[];
|
||||
let results: CorpusResults;
|
||||
|
||||
beforeAll(() => {
|
||||
// Initialize detector with regex and entropy engines
|
||||
const regexEngine = new RegexEngine();
|
||||
const entropyEngine = new EntropyEngine(4.5, 16);
|
||||
detector = new SecretDetector(regexEngine, entropyEngine);
|
||||
|
||||
// Load corpus
|
||||
corpus = loadSecretCorpus();
|
||||
|
||||
// Initialize results
|
||||
results = {
|
||||
total: corpus.length,
|
||||
detected: 0,
|
||||
accuracy: 0,
|
||||
byCategory: {},
|
||||
};
|
||||
|
||||
// Run detection on all examples
|
||||
for (const secret of corpus) {
|
||||
if (!results.byCategory[secret.category]) {
|
||||
results.byCategory[secret.category] = {
|
||||
total: 0,
|
||||
detected: 0,
|
||||
accuracy: 0,
|
||||
secrets: [],
|
||||
};
|
||||
}
|
||||
|
||||
results.byCategory[secret.category].total++;
|
||||
results.byCategory[secret.category].secrets.push(secret);
|
||||
|
||||
if (isDetected(secret, detector)) {
|
||||
results.detected++;
|
||||
results.byCategory[secret.category].detected++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate accuracy percentages
|
||||
results.accuracy = (results.detected / results.total) * 100;
|
||||
|
||||
for (const category of Object.keys(results.byCategory)) {
|
||||
const cat = results.byCategory[category];
|
||||
cat.accuracy = (cat.detected / cat.total) * 100;
|
||||
}
|
||||
});
|
||||
|
||||
describe('Corpus Statistics', () => {
|
||||
it('should have loaded at least 100 secret examples', () => {
|
||||
expect(results.total).toBeGreaterThanOrEqual(100);
|
||||
});
|
||||
|
||||
it('should detect secrets in all categories', () => {
|
||||
const categories = Object.keys(results.byCategory);
|
||||
expect(categories.length).toBeGreaterThanOrEqual(8);
|
||||
});
|
||||
|
||||
it('should print corpus statistics', () => {
|
||||
console.log('\n=== Realistic Secret Corpus Results ===\n');
|
||||
console.log(`Total Examples: ${results.total}`);
|
||||
console.log(`Total Detected: ${results.detected}`);
|
||||
console.log(`Overall Accuracy: ${results.accuracy.toFixed(2)}%\n`);
|
||||
|
||||
console.log('By Category:');
|
||||
console.table(
|
||||
Object.entries(results.byCategory).map(([name, data]) => ({
|
||||
Category: name,
|
||||
Total: data.total,
|
||||
Detected: data.detected,
|
||||
Accuracy: `${data.accuracy.toFixed(1)}%`,
|
||||
}))
|
||||
);
|
||||
|
||||
// Print undetected examples for debugging
|
||||
const undetected: Record<string, SecretExample[]> = {};
|
||||
for (const [category, data] of Object.entries(results.byCategory)) {
|
||||
const missed = data.secrets.filter(s => !isDetected(s, detector));
|
||||
if (missed.length > 0) {
|
||||
undetected[category] = missed.slice(0, 3); // Show first 3 per category
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(undetected).length > 0) {
|
||||
console.log('\n=== Undetected Examples (first 3 per category) ===\n');
|
||||
for (const [category, examples] of Object.entries(undetected)) {
|
||||
console.log(`\n${category}:`);
|
||||
examples.forEach(ex => {
|
||||
console.log(` Line ${ex.line}: ${ex.value.substring(0, 50)}${ex.value.length > 50 ? '...' : ''}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overall Accuracy', () => {
|
||||
it('should achieve >85% detection accuracy', () => {
|
||||
expect(results.accuracy).toBeGreaterThan(85);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Category Accuracy', () => {
|
||||
const criticalCategories = ['aws-keys', 'github-tokens', 'stripe-keys'];
|
||||
|
||||
for (const category of criticalCategories) {
|
||||
it(`should detect ${category} with >80% accuracy`, () => {
|
||||
const cat = results.byCategory[category];
|
||||
expect(cat).toBeDefined();
|
||||
expect(cat.accuracy).toBeGreaterThan(80);
|
||||
});
|
||||
}
|
||||
|
||||
it('should detect jwt-tokens with reasonable accuracy', () => {
|
||||
const cat = results.byCategory['jwt-tokens'];
|
||||
expect(cat).toBeDefined();
|
||||
expect(cat.accuracy).toBeGreaterThan(70);
|
||||
});
|
||||
|
||||
it('should detect slack-tokens with reasonable accuracy', () => {
|
||||
const cat = results.byCategory['slack-tokens'];
|
||||
expect(cat).toBeDefined();
|
||||
expect(cat.accuracy).toBeGreaterThan(70);
|
||||
});
|
||||
|
||||
it('should detect database-urls with reasonable accuracy', () => {
|
||||
const cat = results.byCategory['database-urls'];
|
||||
expect(cat).toBeDefined();
|
||||
expect(cat.accuracy).toBeGreaterThan(70);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Specific Detection Tests', () => {
|
||||
it('should detect AWS Access Key IDs', () => {
|
||||
const awsExamples = corpus.filter(s =>
|
||||
s.category === 'aws-keys' && s.value.startsWith('AKIA')
|
||||
);
|
||||
expect(awsExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = awsExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / awsExamples.length).toBeGreaterThan(0.8);
|
||||
});
|
||||
|
||||
it('should detect GitHub Personal Access Tokens', () => {
|
||||
const githubExamples = corpus.filter(s =>
|
||||
s.category === 'github-tokens' && s.value.startsWith('ghp_')
|
||||
);
|
||||
expect(githubExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = githubExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / githubExamples.length).toBeGreaterThan(0.8);
|
||||
});
|
||||
|
||||
it('should detect Stripe Live Keys', () => {
|
||||
const stripeExamples = corpus.filter(s =>
|
||||
s.category === 'stripe-keys' && s.value.startsWith('sk_live_')
|
||||
);
|
||||
expect(stripeExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = stripeExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / stripeExamples.length).toBeGreaterThan(0.8);
|
||||
});
|
||||
|
||||
it('should detect JWT tokens', () => {
|
||||
const jwtExamples = corpus.filter(s =>
|
||||
s.category === 'jwt-tokens' && s.value.startsWith('eyJ')
|
||||
);
|
||||
expect(jwtExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = jwtExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / jwtExamples.length).toBeGreaterThan(0.7);
|
||||
});
|
||||
|
||||
it('should detect Slack tokens', () => {
|
||||
const slackExamples = corpus.filter(s =>
|
||||
s.category === 'slack-tokens' && (s.value.startsWith('xox') || s.value.includes('hooks.slack.com'))
|
||||
);
|
||||
expect(slackExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = slackExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / slackExamples.length).toBeGreaterThan(0.7);
|
||||
});
|
||||
|
||||
it('should detect database connection strings', () => {
|
||||
const dbExamples = corpus.filter(s =>
|
||||
s.category === 'database-urls' &&
|
||||
(s.value.startsWith('postgres') || s.value.startsWith('mysql') ||
|
||||
s.value.startsWith('mongodb') || s.value.startsWith('redis'))
|
||||
);
|
||||
expect(dbExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = dbExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / dbExamples.length).toBeGreaterThan(0.7);
|
||||
});
|
||||
|
||||
it('should detect SSH keys via entropy', () => {
|
||||
// SSH keys are parsed as individual lines of base64 content
|
||||
const sshExamples = corpus.filter(s =>
|
||||
s.category === 'ssh-keys' &&
|
||||
(s.value.length > 20 || s.value.includes('fake@example.com'))
|
||||
);
|
||||
expect(sshExamples.length).toBeGreaterThan(0);
|
||||
|
||||
const detected = sshExamples.filter(s => isDetected(s, detector));
|
||||
expect(detected.length / sshExamples.length).toBeGreaterThan(0.8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Individual Secret Validation', () => {
|
||||
it('provides detailed detection info for each category', () => {
|
||||
const summary: Record<string, { tested: number; detected: number; details: string[] }> = {};
|
||||
|
||||
for (const secret of corpus) {
|
||||
if (!summary[secret.category]) {
|
||||
summary[secret.category] = { tested: 0, detected: 0, details: [] };
|
||||
}
|
||||
|
||||
summary[secret.category].tested++;
|
||||
const detected = isDetected(secret, detector);
|
||||
if (detected) {
|
||||
summary[secret.category].detected++;
|
||||
}
|
||||
|
||||
// Add detail for first 2 of each category
|
||||
if (summary[secret.category].details.length < 2) {
|
||||
const details = getDetectionDetails(secret, detector);
|
||||
const status = detected ? '✓' : '✗';
|
||||
summary[secret.category].details.push(
|
||||
`${status} "${secret.value.substring(0, 30)}..." → ${detected ? details.map(d => d.pattern.name).join(', ') : 'NOT DETECTED'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the detailed summary
|
||||
console.log('\n=== Detailed Detection Summary ===\n');
|
||||
for (const [category, data] of Object.entries(summary)) {
|
||||
const accuracy = ((data.detected / data.tested) * 100).toFixed(1);
|
||||
console.log(`${category}: ${data.detected}/${data.tested} (${accuracy}%)`);
|
||||
data.details.forEach(d => console.log(` ${d}`));
|
||||
console.log('');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Export for use in other tests
|
||||
export { loadSecretCorpus, isDetected, getDetectionDetails };
|
||||
export type { SecretExample, CorpusResults, CategoryResult };
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
# AWS Access Keys - Realistic Format (All Revoked/Expired/Fake)
|
||||
# Format: AKIA[0-9A-Z]{16} (20 characters total, starting with AKIA)
|
||||
# Source: AWS Documentation Examples, TruffleHog Test Data
|
||||
|
||||
# Example 1: Standard AWS Access Key ID (from AWS docs)
|
||||
AKIAIOSFODNN7EXAMPLE
|
||||
|
||||
# Example 2: Another AWS Access Key ID
|
||||
AKIAI44QH8DHBEXAMPLE
|
||||
|
||||
# Example 3: AWS Access Key with numeric suffix
|
||||
AKIA1234567890ABCDEF
|
||||
|
||||
# Example 4: Development account key (fake)
|
||||
AKIADEVELOPER1234567
|
||||
|
||||
# Example 5: Test environment key (fake)
|
||||
AKIATESTENVIRONMENT8
|
||||
|
||||
# Example 6: Production placeholder (fake)
|
||||
AKIAPRODUCTIONENV123
|
||||
|
||||
# Example 7: CI/CD pipeline key (fake/revoked)
|
||||
AKIACICDPIPELINE1234
|
||||
|
||||
# Example 8: Backup service key (fake)
|
||||
AKIABACKUPSERVICE987
|
||||
|
||||
# Example 9: Monitoring key (fake)
|
||||
AKIAMONITORINGTOOL56
|
||||
|
||||
# Example 10: Logging service key (fake)
|
||||
AKIALOGGINGSERVICE78
|
||||
|
||||
# Example 11: Notification service key (fake)
|
||||
AKIANOTIFICATIONSVC9
|
||||
|
||||
# Example 12: Analytics service key (fake)
|
||||
AKIAANALYTICSSERVC01
|
||||
|
||||
# Example 13: Storage service key (fake)
|
||||
AKIASTORAGESERVICE02
|
||||
|
||||
# Example 14: Compute service key (fake)
|
||||
AKIACOMPUTESERVICE03
|
||||
|
||||
# Example 15: Database service key (fake)
|
||||
AKIADATABASESERV04
|
||||
|
||||
# AWS Secret Access Keys (40-character base64-like strings)
|
||||
# These are FAKE/EXAMPLE keys only
|
||||
|
||||
# Example Secret Key 1 (from AWS docs)
|
||||
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||
|
||||
# Example Secret Key 2
|
||||
xJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||
|
||||
# Example Secret Key 3 (fake)
|
||||
aB1cD2eF3gH4iJ5kL6mN7oP8qR9sT0uV1wX2yZ3
|
||||
|
||||
# Example Secret Key 4 (fake)
|
||||
zY9xW8vU7tS6rQ5pO4nM3lK2jI1hG0fE9dC8bA7
|
||||
|
||||
# Example Secret Key 5 (fake)
|
||||
AbCdEfGhIjKlMnOpQrStUvWxYz1234567890AbCd
|
||||
@@ -0,0 +1,38 @@
|
||||
# Database Connection Strings - Realistic Format (All Fake Credentials)
|
||||
# Source: Various Database Documentation, Connection String Patterns
|
||||
|
||||
# PostgreSQL Connection Strings
|
||||
postgres://user:password123@localhost:5432/mydb
|
||||
postgresql://admin:secret456@db.example.com:5432/production
|
||||
postgres://app_user:app_pass_789@postgres.internal:5432/app_db?sslmode=require
|
||||
postgres://readonly:ro_pass_123@replica.postgres.com:5432/analytics
|
||||
postgres://migration:migrate_456@primary.db.internal:5432/main
|
||||
|
||||
# MySQL Connection Strings
|
||||
mysql://user:password@localhost:3306/database
|
||||
mysql://admin:admin123@mysql.example.com:3306/production_db
|
||||
mysql://app:app_secret@db.internal:3306/app_database?charset=utf8mb4
|
||||
mysql://readonly:ro_password@replica.mysql.com:3306/analytics_db
|
||||
mysql://backup:backup_pass@backup.mysql.internal:3306/backup_db
|
||||
|
||||
# MongoDB Connection Strings
|
||||
mongodb://user:password@localhost:27017/mydb
|
||||
mongodb://admin:admin123@mongo.example.com:27017/production?authSource=admin
|
||||
mongodb+srv://app_user:app_pass@cluster.mongodb.net/app_database?retryWrites=true
|
||||
mongodb://readonly:ro_pass@replica.mongodb.com:27017/analytics
|
||||
mongodb://backup:backup123@backup.mongo.internal:27017/backup_db
|
||||
|
||||
# Redis Connection Strings
|
||||
redis://:password123@localhost:6379/0
|
||||
redis://:redis_pass_456@redis.example.com:6379/0
|
||||
redis://app:app_redis_pass@redis.internal:6379/1
|
||||
redis://localhost:6379/0 (no auth - development only)
|
||||
redis://:complex_redis_password_789@sentinel.redis.internal:26379
|
||||
|
||||
# Amazon RDS Connection Strings
|
||||
postgres://dbadmin:dbadmin123@mydb.abc123xyz.us-east-1.rds.amazonaws.com:5432/mydb
|
||||
mysql://admin:rds_password@prod-mysql.abc123xyz.us-west-2.rds.amazonaws.com:3306/production
|
||||
|
||||
# Connection strings with special characters (URL encoded)
|
||||
postgres://user:p%40ssw%23rd%21@localhost:5432/mydb
|
||||
mysql://admin:pass%26word%3D123@localhost:3306/db
|
||||
@@ -0,0 +1,55 @@
|
||||
# Generic API Keys - Realistic Format (All Fake/Example Keys)
|
||||
# Source: Common API Key Patterns from GitHub Secret Scanning
|
||||
|
||||
# Standard API Key patterns
|
||||
api_key=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
api_key: 1234567890abcdef1234567890abcdef
|
||||
api-key=xw9v8u7t6s5r4q3p2o1n0m9l8k7j6i5h4g3f2e1d0c
|
||||
apikey=AbCdEfGhIjKlMnOpQrStUvWxYz123456
|
||||
API_KEY=0123456789ABCDEF0123456789ABCDEF
|
||||
|
||||
# Service-specific API key patterns
|
||||
service_api_key=sk_1234567890abcdefghijklmnopqrstuv
|
||||
internal_api_key=ik_abcdefghijklmnopqrstuvwxyz123456
|
||||
external_api_key=ek_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
client_api_key=ck_0123456789abcdefghijklmnopqrstuvwx
|
||||
|
||||
# API Keys with prefixes
|
||||
key_live_abcdefghijklmnopqrstuvwxyz123456
|
||||
key_test_abcdefghijklmnopqrstuvwxyz123456
|
||||
key_prod_abcdefghijklmnopqrstuvwxyz1234567890
|
||||
key_dev_abcdefghijklmnopqrstuvwxyz1234567890
|
||||
|
||||
# API Keys in headers/authorization
|
||||
Authorization: ApiKey abcdefghijklmnopqrstuvwxyz123456
|
||||
X-API-Key: 0123456789abcdef0123456789abcdef
|
||||
X-Api-Key: ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
Api-Key: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
|
||||
# Random API key formats (common patterns)
|
||||
abcdefghijklmnopqrstuvwxyz1234567890abcd
|
||||
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234
|
||||
abcdef1234567890abcdef1234567890abcdef12
|
||||
0123456789abcdefghijklmnopqrstuvwxyz0123
|
||||
|
||||
# API keys with version prefixes
|
||||
v1_key_abcdefghijklmnopqrstuvwxyz12345678
|
||||
v2_api_key_abcdefghijklmnopqrstuvwxyz12345
|
||||
api_v3_key_abcdefghijklmnopqrstuvwxyz1234
|
||||
|
||||
# Integration API keys
|
||||
integration_key_int_abcdefghijklmnopqrstuvw
|
||||
webhook_key_whk_abcdefghijklmnopqrstuvwxyz
|
||||
partner_api_key_part_abcdefghijklmnopqrst
|
||||
|
||||
# Environment-specific API keys
|
||||
prod_api_key_prod_abcdefghijklmnopqrstuvw1
|
||||
dev_api_key_dev_abcdefghijklmnopqrstuvwxyz
|
||||
staging_api_key_stg_abcdefghijklmnopqrstuv
|
||||
test_api_key_test_abcdefghijklmnopqrstuvwx
|
||||
|
||||
# SaaS platform API key patterns
|
||||
algolia_api_key=abcdefghijklmnopqrstuvwxyz1234567890abcdef
|
||||
sendgrid_api_key=SG.abcdefghijklmnopqrstuvwxyz1234567890.ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
mailgun_api_key=key-abcdefghijklmnopqrstuvwxyz1234567890abcdef
|
||||
Twilio_api_key=SKabcdefghijklmnopqrstuvwxyz1234567890abcdef12
|
||||
@@ -0,0 +1,43 @@
|
||||
# GitHub Tokens - Realistic Format (All Revoked/Expired/Fake)
|
||||
# Format: ghp_[a-zA-Z0-9]{36} (40 characters total)
|
||||
# Source: GitHub Secret Scanning Patterns, GitHub Docs
|
||||
|
||||
# Personal Access Tokens (classic)
|
||||
ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0000
|
||||
ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1111
|
||||
ghp_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2222
|
||||
ghp_cccccccccccccccccccccccccccccccccccc3333
|
||||
ghp_dddddddddddddddddddddddddddddddddddd4444
|
||||
|
||||
# Fine-grained Personal Access Tokens
|
||||
ghp_efgh1234efgh5678efgh9012efgh3456efgh7890
|
||||
ghp_ijkl5678ijkl9012ijkl3456ijkl7890ijkl1234
|
||||
ghp_mnop9012mnop3456mnop7890mnop1234mnop5678
|
||||
ghp_qrst3456qrst7890qrst1234qrst5678qrst9012
|
||||
|
||||
# OAuth Access Tokens
|
||||
gho_wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww5555
|
||||
gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx6666
|
||||
gho_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy7777
|
||||
gho_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz8888
|
||||
|
||||
# GitHub App Installation Access Tokens
|
||||
ghs_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9999
|
||||
ghs_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0000
|
||||
ghs_cccccccccccccccccccccccccccccccccccc1111
|
||||
|
||||
# GitHub App User Access Tokens
|
||||
ghu_dddddddddddddddddddddddddddddddddddd2222
|
||||
ghu_eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3333
|
||||
|
||||
# GitHub App Refresh Tokens
|
||||
ghr_ffffffffffffffffffffffffffffffffffff4444
|
||||
ghr_gggggggggggggggggggggggggggggggggggg5555
|
||||
|
||||
# GitHub App User-to-Server Tokens
|
||||
ghu_hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh6666
|
||||
ghu_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii7777
|
||||
|
||||
# Server-to-Server Tokens
|
||||
ghs_jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj8888
|
||||
ghs_kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk9999
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# JWT Tokens - Realistic Format (All Revoked/Expired/Fake)
|
||||
# Format: eyJ[header].eyJ[payload].[signature] (base64url encoded)
|
||||
# Source: JWT.io Examples, RFC 7519
|
||||
|
||||
# Example 1: Standard JWT with HS256 (from jwt.io)
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
|
||||
# Example 2: JWT with RS256
|
||||
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyMTIzIiwiYXVkIjoiYXBwNDU2IiwiaWF0IjoxNjE2MjM5MDIyLCJleHAiOjE2MTYyNDI2MjJ9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
|
||||
|
||||
# Example 3: JWT with minimal payload
|
||||
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.hB4eJ1Q2k3l4m5n6o7p8q9r0s1t2u3v4w5x6y7z8
|
||||
|
||||
# Example 4: JWT with ES256
|
||||
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyNDU2IiwiZW1haWwiOiJ0ZXN0QGV4YW1wbGUuY29tIn0.MEQCIH8w7fKj4K0vLK7W1fQ8ZzY9xW8vU7tS6rQ5pO4nM3lK2jI1hG0fE9dC8bA
|
||||
|
||||
# Example 5: JWT with EdDSA
|
||||
eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbmlzdHJhdG9yIn0.6ICJmM2NhYjM0NTY3ODkwYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg
|
||||
|
||||
# Example 6: API Gateway JWT
|
||||
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJhcGktY2xpZW50LTEyMyIsInNjb3BlIjoicmVhZCB3cml0ZSIsImV4cCI6MTY0MTIzNDU2N30.signaturepart123456789
|
||||
|
||||
# Example 7: Auth0-style JWT
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZ25pbmcta2V5LTEifQ.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDEyMzQ1Njc4OSIsImF1ZCI6Im15LWFwaSIsImlhdCI6MTY0NjEyMzQ1NiwiZXhwIjoxNjQ2MTIzNDU2fQ.signaturepart987654321
|
||||
|
||||
# Example 8: Firebase JWT
|
||||
eyJhbGciOiJSUzI1NiIsImtpZCI6ImZpcmViYXNlLWtleS0xIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcHJvamVjdC0xMjMiLCJhdWQiOiJwcm9qZWN0LTEyMyIsImF1dGhfdGltZSI6MTY0NjEyMzQ1NiwidXNlcl9pZCI6InRlc3R1c2VyMTIzIiwic3ViIjoidGVzdHVzZXIxMjMiLCJpYXQiOjE2NDYxMjM0NTZ9.signaturepart567890123
|
||||
|
||||
# Example 9: AWS Cognito JWT
|
||||
eyJraWQiOiJjb2duaXRvLWtleS0xIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIxMjM0NTY3OC05MGFiLWNkZWYtMTIzNC01Njc4LTkwYWJjZGVmZ2hpIiwiY29nbml0bzpncm91cHMiOlsiYWRtaW5zIl0sImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXhhbXBsZS5jb20iLCJjbGllbnRfaWQiOiIxMjM0NTY3ODkwYWJjIiwib3JpZ2luX2p0aSI6ImFiY2QtMTIzNCIsImV2ZW50X2lkIjoiZWZkZS0xMjM0IiwidG9rZW5fdXNlIjoiYWNjZXNzIiwic2NvcGUiOiJhd3MuY29nbml0by5zaWduaW4udXNlci5hZG1pbiIsImF1dGhfdGltZSI6MTY0NjEyMzQ1NiwiZXhwIjoxNjQ2MTIzNDU2LCJpYXQiOjE2NDYxMjM0NTYsImp0aSI6ImFiY2QtMTIzNCIsInVzZXJuYW1lIjoidGVzdHVzZXIifQ.signaturepart234567890
|
||||
|
||||
# Example 10: Okta JWT
|
||||
eyJhbGciOiJSUzI1NiIsImtpZCI6Im9rdGEta2V5LTEifQ.eyJzdWIiOiIwMHUxYTU0OGM0eG42VFkzNDU2NyIsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJ2ZXIiOjEsImlzcyI6Imh0dHBzOi8vZXhhbXBsZS1vay10YS5va3RhLmNvbSIsImF1ZCI6ImFwaTovL2RlZmF1bHQiLCJpYXQiOjE2NDYxMjM0NTYsImV4cCI6MTY0NjEyMzQ1NiwianRpIjoiSUQuMTIzNDU2Nzg5MCIsImFtciI6WyJwYXNzd29yZCJdLCJpZHAiOiIwMG9hYmNkZWZnaCIsImF1dGhfdGltZSI6MTY0NjEyMzQ1NiwiYXRfaGFzaCI6ImFiY2RlZmcxMjM0NTYifQ.signaturepart890123456
|
||||
@@ -0,0 +1,40 @@
|
||||
# OAuth Tokens - Realistic Format (All Revoked/Expired/Fake)
|
||||
# Source: OAuth 2.0 RFC 6749, Provider Documentation
|
||||
|
||||
# OAuth 2.0 Access Tokens (various formats)
|
||||
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
|
||||
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
|
||||
ya29.a0Aa4b16C3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0u1V2w3X4y5Z6a7B8c9
|
||||
EAANlZA2X7ZCZAYXABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghij
|
||||
|
||||
# OAuth 2.0 Refresh Tokens
|
||||
1/aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdefABCDEFGHIJKLMNO
|
||||
refresh_token_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
|
||||
RtgDeFCjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYz12345678
|
||||
|
||||
# Authorization Codes (short-lived)
|
||||
4/aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdef-ABCDEFGHIJKLMNO
|
||||
auth_code_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4
|
||||
AQCDEFGHIJKLMN0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
|
||||
# PKCE Code Verifiers (used with OAuth 2.0 PKCE)
|
||||
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
|
||||
aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdefABCDEFGH
|
||||
M7Qd2N6aPl4mRn8kTp2vWx5yZb3cEf6hAj9kLm1nOp4qRs7tUv0wXy2z5
|
||||
|
||||
# Device Codes (OAuth 2.0 Device Flow)
|
||||
GAQDEFGHIJKLMN0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
AAQDEFGHIJKLMN0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
|
||||
# ID Tokens (OpenID Connect JWTs)
|
||||
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIxMjM0NTY3ODkwMTIzLTBhYjJjZDNlZi1naGk0a2w1bW42b3BxcnN0dXYud2FwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiMTIzNDU2Nzg5MDEyMy0wYWJjZDNlZi1naGk0a2w1bW42b3BxcnN0dXYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDc1ODU0NDE2OTU4ODU5NzkwMzUiLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJYQmlUMWx4dU9BWWpJZGVaN0dPQl9BIiwiaWF0IjoxNjQ2MTIzNDU2LCJleHAiOjE2NDYxMjM0NTZ9.signature
|
||||
|
||||
# Client Credentials (Client ID and Secret pairs)
|
||||
client_id: abcdefgh12345678.apps.example.com
|
||||
client_secret: GOCSPX-abcdefghijklmnopqrstuvwxyz1234567890abcdef
|
||||
|
||||
# Alternative OAuth token formats
|
||||
Slack: xoxe.xoxp-1-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdef
|
||||
Shopify: shpat_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
Discord: MzA4NjE4NDE1MzQzNjM1MzYw.DaScqQ.aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdef
|
||||
Twitch: oauth:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
@@ -0,0 +1,29 @@
|
||||
# Slack Tokens - Realistic Format (All Revoked/Expired/Fake)
|
||||
# Format: xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}
|
||||
# Source: Slack API Documentation, Slack Secret Scanning
|
||||
|
||||
# Bot Tokens (xoxb-) - Used by bots to connect to Slack
|
||||
xoxb-1234567890123-1234567890123-aBcDeFgHiJkLmNoPqRsTuVwX
|
||||
xoxb-4567890123456-4567890123456-bCdEfGhIjKlMnOpQrStUvWxY
|
||||
xoxb-7890123456789-7890123456789-cDeFgHiJkLmNoPqRsTuVwXyZ
|
||||
xoxb-0123456789012-0123456789012-dEfGhIjKlMnOpQrStUvWxYz0
|
||||
xoxb-2345678901234-2345678901234-eFgHiJkLmNoPqRsTuVwXyZ01
|
||||
|
||||
# User Tokens (xoxp-) - OAuth user access tokens
|
||||
xoxp-1234567890123-1234567890123-1234567890123-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
xoxp-4567890123456-4567890123456-4567890123456-b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7
|
||||
xoxp-7890123456789-7890123456789-7890123456789-c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8
|
||||
|
||||
# App Tokens (xapp-) - Slack app-level tokens
|
||||
xapp-1-A1234567890-1234567890123-aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdEfGh
|
||||
xapp-1-B2345678901-2345678901234-bCdEfGhIjKlMnOpQrStUvWxYz0123456789BcDeFgHi
|
||||
xapp-1-C3456789012-3456789012345-cDeFgHiJkLmNoPqRsTuVwXyZ012345678901CdEfGhIj
|
||||
|
||||
# Refresh Tokens (xoxr-) - OAuth refresh tokens
|
||||
xoxr-1234567890123-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
||||
xoxr-4567890123456-b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7
|
||||
|
||||
# Webhook URLs (not tokens but contain secrets)
|
||||
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
|
||||
https://hooks.slack.com/services/T12345678/B12345678/AbCdEfGhIjKlMnOpQrStUvWx
|
||||
https://hooks.slack.com/services/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
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# SSH Keys - Realistic Format (All Fake/Example Keys)
|
||||
# Source: SSH Key Documentation, OpenSSH Formats
|
||||
|
||||
# RSA Private Key (2048-bit example - FAKE)
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy0AHB7MhgwMbRvI0MBZhpJ
|
||||
qX3YBb+QXBU0yI0g1xN0rG2XcfJtK5Xy4qKWPJbE+esL4WzGzQnrdBkYy+IZmMbR
|
||||
y5PG0/VbKNdrnZBRtWvJtLGJ7fFLb0vKELdI+YKA5QqHI9+lEhKQ/JYQ8GGDXV0c
|
||||
sIQNx0TNVL8Y7xEqzFKKqJ4B0MPLoMOcT7Q3t3LZwOVTrQHK2p8vVwXfR2LWBd8z
|
||||
PsKqSJQOdVsJI6FNiRZrGqmKCbBPKBf3jVc3jE7QqKWNZkLKE5b4ANPmHMq5CvXr
|
||||
QGU1lGXN2Tq7x8mFhBDb8T0wPBmQq0QGR1QLQeZG0QIDAQABAoIBADY8C0E1CQGn
|
||||
o7P8T3wVnQE1KzFVB4k4rO8LqEPLhD9F4fFSB0KY8bUHZ3PCg6P5YQYsWE4xXkJf
|
||||
Hh9XmB0Yt3VCCCN0S1qIQXo5Yd8QKc5J7j3Nh4mT0fVL5y0jK1ZJ0BC5Bn8P8Qf
|
||||
HQIIQVIxNDEUhT0sEREREBMTExQVFRUWFxcXGBgYGRkZGhoaGxsbHBwcHR0dHh4e
|
||||
Hx8fICAgISEhIiIiIyMjJCUlJSYmJicoKCkqKiorKyssLC0tLS4uLi8vLzAxMTIz
|
||||
MzQ0NTU2Njc3ODg5OTpCQkNDRERFRUZGR0dHSEhJSUpKS0tMTU1OTk9PUFFQUVFR
|
||||
UlJTU1RUVVVWVldXV1hYWFlZWVpaW1tbXFxcXV1eXl9fYGFiYmNkZWZnaGlqa2xt
|
||||
bm9wcXJzdHV2d3h5ent8fX5/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5yd
|
||||
np+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zN
|
||||
zs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9
|
||||
/v8=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
# OpenSSH format ED25519 Private Key (FAKE)
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACB6NqFGpMTjJ7dZzymp9zknBfzKnkzIfzZnZp7rXGK6uQAAAJhJlaKUWZWi
|
||||
lAAAAAtzc2gtZWQyNTUxOQAAACB6NqFGpMTjJ7dZzymp9zknBfzKnkzIfzZnZp7rXGK6uQ
|
||||
AAAEA6PbHhnp6q+FzU2nRr0q3bPsykZ7TzNXbC5IMeCWzNdXo2oUakxOMnt1nPKan3OS
|
||||
MF/MqeTMh/Nmdmnu9cYrq5AAAADmZha2VAZXhhbXBsZS5jb20=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
||||
# EC (Elliptic Curve) Private Key (FAKE)
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHQCAQEEIBv9b1D8mT8qC8p4a6Z7F8VvG7bE2Y9C5L8U5sC9oBSAU6gsjjsa9xCBoAU
|
||||
BgUrgQQAIgYgMIGABgorBgEEAZdYAALBMDswOQIBAQQfMB0GByqGSM49AQECEC0r
|
||||
N0ZpH8Kvn7mMBQYHZW4yY4XJJjAkAgEBBByB3vGJpQrmp3RqNRuS9BAAxj7IWdK2
|
||||
bG0LmZo5QKQ=
|
||||
-----END EC PRIVATE KEY-----
|
||||
|
||||
# DSA Private Key (FAKE - legacy format)
|
||||
-----BEGIN DSA PRIVATE KEY-----
|
||||
MIIDTwIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy0AHB7MhgwMbRvI0MBZhpJ
|
||||
qX3YBb+QXBU0yI0g1xN0rG2XcfJtK5Xy4qKWPJbE+esL4WzGzQnrdBkYy+IZmMbR
|
||||
y5PG0/VbKNdrnZBRtWvJtLGJ7fFLb0vKELdI+YKA5QqHI9+lEhKQ/JYQ8GGDXV0c
|
||||
sIQNx0TNVL8Y7xEqzFKKqJ4B0MPLoMOcT7Q3t3LZwOVTrQHK2p8vVwXfR2LWBd8z
|
||||
PsKqSJQOdVsJI6FNiRZrGqmKCbBPKBf3jVc3jE7QqKWNZkLKE5b4ANPmHMq5CvXr
|
||||
QGU1lGXN2Tq7x8mFhBDb8T0wPBmQq0QGR1QLQeZG0QIVALznc8aJLvBbfnzF3G0O
|
||||
L8JbOg2xAoGBALr5OBb3TWm2h6FZD5K3z3fNd9kmQqKmxnmnZdJjkpUfFbXOuKZp
|
||||
BzLm0g9zH7W8PMRz7r8zEeZ4s6dW9CjD+M5T8nKQw2h5b7Eq7cQbGa4iNfRmU0pL
|
||||
QvLzP7LZvE9CY7dYxFQAQ9DqAzJLBQKvFYzK8Q7mNgUYeE5CpZcMGRG1FLyZG0Q
|
||||
-----END DSA PRIVATE KEY-----
|
||||
|
||||
# SSH Public Key (RSA - safe to share but included for completeness)
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC0Z3VS5JJcds3xfn/ygWyF8PbnGy0AHB7MhgwMbRvI0MBZhpJqX3YBb+QXBU0yI0g1xN0rG2XcfJtK5Xy4qKWPJbE+esL4WzGzQnrdBkYy+IZmMbRy5PG0/VbKNdrnZBRtWvJtLGJ7fFLb0vKELdI+YKA5QqHI9+lEhKQ/JYQ8GGDXV0csIQNx0TNVL8Y7xEqzFKKqJ4B0MPLoMOcT7Q3t3LZwOVTrQHK2p8vVwXfR2LWBd8zPsKqSJQOdVsJI6FNiRZrGqmKCbBPKBf3jVc3jE7QqKWNZkLKE5b4ANPmHMq5CvXrQGU1lGXN2Tq7x8mFhBDb8T0wPBmQq0QGR1QLQeZG0Q= fake@example.com
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Stripe API Keys - Realistic Format (All Revoked/Expired/Fake)
|
||||
# Format: sk_live_[0-9a-zA-Z]{24,} or sk_test_[0-9a-zA-Z]{24,}
|
||||
# Source: Stripe API Documentation, Stripe Secret Scanning
|
||||
|
||||
# Live Secret Keys (sk_live_) - REVOKED/FAKE ONLY
|
||||
sk_live_abcdefghijklmnopqrstuvwxyz123456
|
||||
sk_live_51abcdefghijklmnopqrstuvwx123456789
|
||||
sk_live_abcdefghijklmnopqrstuvwxyzABCDEFG
|
||||
sk_live_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
sk_live_0123456789abcdefghijklmnopqrstuv
|
||||
|
||||
# Test Secret Keys (sk_test_)
|
||||
sk_test_abcdefghijklmnopqrstuvwxyz123456
|
||||
sk_test_51abcdefghijklmnopqrstuvwx123456789
|
||||
sk_test_abcdefghijklmnopqrstuvwxyzABCDEFG
|
||||
sk_test_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
|
||||
sk_test_0123456789abcdefghijklmnopqrstuv
|
||||
|
||||
# Live Publishable Keys (pk_live_) - Safe to share but included for completeness
|
||||
pk_live_abcdefghijklmnopqrstuvwxyz123456
|
||||
pk_live_51abcdefghijklmnopqrstuvwx123456789
|
||||
|
||||
# Test Publishable Keys (pk_test_)
|
||||
pk_test_abcdefghijklmnopqrstuvwxyz123456
|
||||
pk_test_51abcdefghijklmnopqrstuvwx123456789
|
||||
|
||||
# Restricted API Keys (rk_live_ and rk_test_)
|
||||
rk_live_abcdefghijklmnopqrstuvwxyz1234567890
|
||||
rk_test_abcdefghijklmnopqrstuvwxyz1234567890
|
||||
|
||||
# Stripe Connect Keys
|
||||
sk_live_acct_1234567890abcdefghijklmnopqrstuvwxyz1234567890
|
||||
|
||||
# Webhook Endpoint Secrets (whsec_)
|
||||
whsec_abcdefghijklmnopqrstuvwxyz1234567890abcdef
|
||||
whsec_1234567890abcdefghijklmnopqrstuvwxyz123456
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
/**
|
||||
* Corpus Validation Script
|
||||
*
|
||||
* Validates all secret detection patterns against the realistic corpus of 146 examples.
|
||||
* Goal: Achieve 85%+ detection rate with <5% false positives.
|
||||
*/
|
||||
|
||||
import { V2_PATTERNS, V2_PATTERN_COUNTS } from '../../../src/patterns/v2/index.js';
|
||||
import type { SecretPattern } from '../../../src/types.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// ============================================================================
|
||||
// CORPUS FILE DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
const CORPUS_FILES = [
|
||||
{ file: 'aws-keys.txt', expectedPatterns: ['aws_access_key_id', 'aws_secret_access_key'], totalExamples: 20 },
|
||||
{ file: 'github-tokens.txt', expectedPatterns: ['github_personal_access_token', 'github_oauth_token', 'github_app_token', 'github_user_token', 'github_refresh_token'], totalExamples: 19 },
|
||||
{ file: 'slack-tokens.txt', expectedPatterns: ['slack_bot_token', 'slack_user_token', 'slack_app_token', 'slack_webhook_url'], totalExamples: 11 },
|
||||
{ file: 'database-urls.txt', expectedPatterns: ['postgres_connection_string', 'mysql_connection_string', 'mongodb_connection_string', 'redis_connection_string'], totalExamples: 18 },
|
||||
{ file: 'jwt-tokens.txt', expectedPatterns: ['jwt_token_standard', 'jwt_token_hs256', 'jwt_token_rs256', 'jwt_token_es256'], totalExamples: 10 },
|
||||
{ file: 'oauth-tokens.txt', expectedPatterns: ['oauth_access_token', 'oauth_refresh_token', 'oauth_authorization_code', 'gcp_oauth_access_token'], totalExamples: 15 },
|
||||
{ file: 'ssh-keys.txt', expectedPatterns: ['ssh_rsa_private_key', 'ssh_openssh_private_key', 'ssh_ecdsa_private_key', 'ssh_dsa_private_key'], totalExamples: 5 },
|
||||
{ file: 'stripe-keys.txt', expectedPatterns: ['stripe_live_secret_key', 'stripe_test_secret_key', 'stripe_webhook_secret'], totalExamples: 12 },
|
||||
{ file: 'generic-api-keys.txt', expectedPatterns: ['generic_api_key_header', 'generic_api_key_pattern', 'generic_secret_assignment'], totalExamples: 27 },
|
||||
];
|
||||
|
||||
const CORPUS_DIR = resolve(process.cwd(), 'test/fixtures/realistic-secrets');
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
interface DetectionResult {
|
||||
line: string;
|
||||
lineNumber: number;
|
||||
detected: boolean;
|
||||
matchedBy: string[];
|
||||
isComment: boolean;
|
||||
isEmpty: boolean;
|
||||
}
|
||||
|
||||
interface FileValidationResult {
|
||||
fileName: string;
|
||||
totalLines: number;
|
||||
secretsFound: number;
|
||||
falsePositives: number;
|
||||
detectionRate: number;
|
||||
falsePositiveRate: number;
|
||||
detections: DetectionResult[];
|
||||
}
|
||||
|
||||
interface OverallResult {
|
||||
totalExamples: number;
|
||||
totalDetected: number;
|
||||
totalMissed: number;
|
||||
totalFalsePositives: number;
|
||||
detectionRate: number;
|
||||
falsePositiveRate: number;
|
||||
fileResults: FileValidationResult[];
|
||||
patternStats: Map<string, { detected: number; total: number }>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PATTERN TESTING
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Test a single line against all patterns
|
||||
*/
|
||||
function testLineAgainstPatterns(line: string, lineNumber: number, patterns: SecretPattern[]): DetectionResult {
|
||||
const trimmed = line.trim();
|
||||
const isComment = trimmed.startsWith('#');
|
||||
const isEmpty = trimmed.length === 0;
|
||||
|
||||
// Skip comment and empty lines for detection metrics, but track them
|
||||
const matchedPatterns: string[] = [];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
try {
|
||||
const regex = new RegExp(pattern.regex.source, pattern.regex.flags.includes('g') ? pattern.regex.flags : pattern.regex.flags + 'g');
|
||||
if (regex.test(line)) {
|
||||
matchedPatterns.push(pattern.name);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(` ⚠️ Pattern error in ${pattern.name}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
line: line.substring(0, 80) + (line.length > 80 ? '...' : ''),
|
||||
lineNumber,
|
||||
detected: matchedPatterns.length > 0,
|
||||
matchedBy: matchedPatterns,
|
||||
isComment,
|
||||
isEmpty,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a detection is a likely false positive
|
||||
*/
|
||||
function isLikelyFalsePositive(detection: DetectionResult, fileName: string): boolean {
|
||||
// Comment lines are not false positives if they're examples
|
||||
if (detection.isComment && !detection.line.includes('Example')) {
|
||||
return false; // Comments explaining patterns are fine
|
||||
}
|
||||
|
||||
// If it's a comment but we detected something, might be a false positive
|
||||
if (detection.isComment && detection.detected) {
|
||||
// Check if it looks like a real secret in the comment
|
||||
const hasSecretIndicators = /[a-zA-Z0-9]{16,}/.test(detection.line);
|
||||
if (!hasSecretIndicators) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a single corpus file
|
||||
*/
|
||||
function validateCorpusFile(fileName: string, expectedPatterns: string[], totalExamples: number): FileValidationResult {
|
||||
const filePath = resolve(CORPUS_DIR, fileName);
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
const detections: DetectionResult[] = [];
|
||||
let secretsFound = 0;
|
||||
let falsePositives = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const detection = testLineAgainstPatterns(lines[i], i + 1, V2_PATTERNS);
|
||||
detections.push(detection);
|
||||
|
||||
if (detection.detected && !detection.isComment && !detection.isEmpty) {
|
||||
secretsFound++;
|
||||
} else if (detection.detected && isLikelyFalsePositive(detection, fileName)) {
|
||||
falsePositives++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate meaningful lines (non-comment, non-empty)
|
||||
const meaningfulLines = detections.filter(d => !d.isComment && !d.isEmpty).length;
|
||||
const detectionRate = meaningfulLines > 0 ? (secretsFound / meaningfulLines) * 100 : 0;
|
||||
const falsePositiveRate = secretsFound > 0 ? (falsePositives / secretsFound) * 100 : 0;
|
||||
|
||||
return {
|
||||
fileName,
|
||||
totalLines: lines.length,
|
||||
secretsFound,
|
||||
falsePositives,
|
||||
detectionRate,
|
||||
falsePositiveRate,
|
||||
detections,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run full corpus validation
|
||||
*/
|
||||
function runValidation(): OverallResult {
|
||||
console.log('╔══════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ OpenCode Filter V2 - Corpus Validation ║');
|
||||
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
console.log(`Pattern Counts by Category:`);
|
||||
Object.entries(V2_PATTERN_COUNTS).forEach(([cat, count]) => {
|
||||
console.log(` • ${cat}: ${count} patterns`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
const fileResults: FileValidationResult[] = [];
|
||||
const patternStats = new Map<string, { detected: number; total: number }>();
|
||||
|
||||
for (const corpus of CORPUS_FILES) {
|
||||
console.log(`📁 Validating: ${corpus.file}`);
|
||||
console.log(` Expected patterns: ${corpus.expectedPatterns.join(', ')}`);
|
||||
|
||||
const result = validateCorpusFile(corpus.file, corpus.expectedPatterns, corpus.totalExamples);
|
||||
fileResults.push(result);
|
||||
|
||||
console.log(` Total lines: ${result.totalLines}`);
|
||||
console.log(` Secrets detected: ${result.secretsFound}`);
|
||||
console.log(` Detection rate: ${result.detectionRate.toFixed(1)}%`);
|
||||
console.log('');
|
||||
|
||||
// Track pattern statistics
|
||||
for (const detection of result.detections) {
|
||||
if (detection.detected && !detection.isComment && !detection.isEmpty) {
|
||||
for (const patternName of detection.matchedBy) {
|
||||
const current = patternStats.get(patternName) || { detected: 0, total: 0 };
|
||||
patternStats.set(patternName, { detected: current.detected + 1, total: current.total + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate overall metrics
|
||||
let totalExamples = 0;
|
||||
let totalDetected = 0;
|
||||
let totalFalsePositives = 0;
|
||||
|
||||
for (const result of fileResults) {
|
||||
const meaningfulLines = result.detections.filter(d => !d.isComment && !d.isEmpty).length;
|
||||
totalExamples += meaningfulLines;
|
||||
totalDetected += result.secretsFound;
|
||||
totalFalsePositives += result.falsePositives;
|
||||
}
|
||||
|
||||
const detectionRate = totalExamples > 0 ? (totalDetected / totalExamples) * 100 : 0;
|
||||
const falsePositiveRate = totalDetected > 0 ? (totalFalsePositives / totalDetected) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalExamples,
|
||||
totalDetected,
|
||||
totalMissed: totalExamples - totalDetected,
|
||||
totalFalsePositives,
|
||||
detectionRate,
|
||||
falsePositiveRate,
|
||||
fileResults,
|
||||
patternStats,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Print detailed report
|
||||
*/
|
||||
function printReport(result: OverallResult): void {
|
||||
console.log('\n╔══════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ VALIDATION REPORT ║');
|
||||
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
console.log('📊 OVERALL METRICS');
|
||||
console.log('─────────────────────────────────────────────────────────────');
|
||||
console.log(`Total Examples: ${result.totalExamples}`);
|
||||
console.log(`Secrets Detected: ${result.totalDetected}`);
|
||||
console.log(`Secrets Missed: ${result.totalMissed}`);
|
||||
console.log(`False Positives: ${result.totalFalsePositives}`);
|
||||
console.log('');
|
||||
console.log(`Detection Rate: ${result.detectionRate.toFixed(1)}% ${result.detectionRate >= 85 ? '✅' : '❌ (< 85%)'}`);
|
||||
console.log(`False Positive Rate: ${result.falsePositiveRate.toFixed(1)}% ${result.falsePositiveRate < 5 ? '✅' : '❌ (> 5%)'}`);
|
||||
console.log('');
|
||||
|
||||
// File-by-file breakdown
|
||||
console.log('📁 FILE-BY-FILE BREAKDOWN');
|
||||
console.log('─────────────────────────────────────────────────────────────');
|
||||
for (const fileResult of result.fileResults) {
|
||||
const status = fileResult.detectionRate >= 80 ? '✅' : '⚠️ ';
|
||||
console.log(`${status} ${fileResult.fileName}`);
|
||||
console.log(` Lines: ${fileResult.totalLines} | Detected: ${fileResult.secretsFound} | Rate: ${fileResult.detectionRate.toFixed(1)}%`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Pattern effectiveness
|
||||
console.log('🎯 TOP PATTERNS BY DETECTION');
|
||||
console.log('─────────────────────────────────────────────────────────────');
|
||||
const sortedPatterns = Array.from(result.patternStats.entries())
|
||||
.sort((a, b) => b[1].detected - a[1].detected)
|
||||
.slice(0, 15);
|
||||
|
||||
for (const [patternName, stats] of sortedPatterns) {
|
||||
console.log(` • ${patternName}: ${stats.detected} detections`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Underperforming patterns (patterns that should match but didn't)
|
||||
console.log('🔧 PATTERNS NEEDING ATTENTION');
|
||||
console.log('─────────────────────────────────────────────────────────────');
|
||||
|
||||
for (const corpus of CORPUS_FILES) {
|
||||
const fileResult = result.fileResults.find(r => r.fileName === corpus.file);
|
||||
if (fileResult && fileResult.detectionRate < 80) {
|
||||
console.log(`⚠️ ${corpus.file}: ${fileResult.detectionRate.toFixed(1)}% detection`);
|
||||
console.log(` Expected patterns: ${corpus.expectedPatterns.join(', ')}`);
|
||||
|
||||
// Check which patterns are not matching
|
||||
for (const expectedPattern of corpus.expectedPatterns) {
|
||||
const stats = result.patternStats.get(expectedPattern);
|
||||
if (!stats || stats.detected === 0) {
|
||||
console.log(` ❌ ${expectedPattern}: 0 detections - needs tuning`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Final verdict
|
||||
console.log('🏁 FINAL VERDICT');
|
||||
console.log('─────────────────────────────────────────────────────────────');
|
||||
const passed = result.detectionRate >= 85 && result.falsePositiveRate < 5;
|
||||
if (passed) {
|
||||
console.log('✅ VALIDATION PASSED');
|
||||
console.log(' Detection rate >= 85%: ✓');
|
||||
console.log(' False positive rate < 5%: ✓');
|
||||
} else {
|
||||
console.log('❌ VALIDATION FAILED');
|
||||
if (result.detectionRate < 85) {
|
||||
console.log(` Detection rate too low: ${result.detectionRate.toFixed(1)}% (need >= 85%)`);
|
||||
}
|
||||
if (result.falsePositiveRate >= 5) {
|
||||
console.log(` False positive rate too high: ${result.falsePositiveRate.toFixed(1)}% (need < 5%)`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run detailed line-by-line analysis for debugging
|
||||
*/
|
||||
function runDetailedAnalysis(fileName: string): void {
|
||||
const corpus = CORPUS_FILES.find(c => c.file === fileName);
|
||||
if (!corpus) {
|
||||
console.log(`❌ Unknown corpus file: ${fileName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\n🔍 Detailed Analysis: ${fileName}\n`);
|
||||
|
||||
const filePath = resolve(CORPUS_DIR, fileName);
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line || line.startsWith('#')) continue;
|
||||
|
||||
const detection = testLineAgainstPatterns(lines[i], i + 1, V2_PATTERNS);
|
||||
|
||||
if (detection.detected) {
|
||||
console.log(`✅ Line ${i + 1}: DETECTED by [${detection.matchedBy.join(', ')}]`);
|
||||
console.log(` ${line.substring(0, 60)}${line.length > 60 ? '...' : ''}`);
|
||||
} else {
|
||||
console.log(`❌ Line ${i + 1}: NOT DETECTED`);
|
||||
console.log(` ${line.substring(0, 60)}${line.length > 60 ? '...' : ''}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN EXECUTION
|
||||
// ============================================================================
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`
|
||||
Usage: bun run test/fixtures/realistic-secrets/validate-corpus.ts [options]
|
||||
|
||||
Options:
|
||||
--help, -h Show this help message
|
||||
--analyze <file> Run detailed analysis on a specific corpus file
|
||||
--report Generate JSON report for CI/CD
|
||||
|
||||
Examples:
|
||||
bun run test/fixtures/realistic-secrets/validate-corpus.ts
|
||||
bun run test/fixtures/realistic-secrets/validate-corpus.ts --analyze aws-keys.txt
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.includes('--analyze')) {
|
||||
const fileIndex = args.indexOf('--analyze');
|
||||
const fileName = args[fileIndex + 1];
|
||||
if (fileName) {
|
||||
runDetailedAnalysis(fileName);
|
||||
} else {
|
||||
console.log('❌ Please specify a file to analyze: --analyze <filename>');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
const result = runValidation();
|
||||
printReport(result);
|
||||
|
||||
// Save report
|
||||
const reportPath = resolve(process.cwd(), '.sisyphus/evidence/v2-t10-corpus-validation.txt');
|
||||
const reportContent = `
|
||||
Corpus Validation Report
|
||||
========================
|
||||
Date: ${new Date().toISOString().split('T')[0]}
|
||||
Total Examples: ${result.totalExamples}
|
||||
Detected: ${result.totalDetected}
|
||||
Missed: ${result.totalMissed}
|
||||
Detection Rate: ${result.detectionRate.toFixed(1)}% ${result.detectionRate >= 85 ? '✅' : '❌'}
|
||||
|
||||
False Positives: ${result.totalFalsePositives}
|
||||
False Positive Rate: ${result.falsePositiveRate.toFixed(1)}% ${result.falsePositiveRate < 5 ? '✅' : '❌'}
|
||||
|
||||
Top Patterns by Detection:
|
||||
${Array.from(result.patternStats.entries())
|
||||
.sort((a, b) => b[1].detected - a[1].detected)
|
||||
.slice(0, 10)
|
||||
.map(([name, stats]) => `- ${name}: ${stats.detected} detections`)
|
||||
.join('\n')}
|
||||
|
||||
Files Needing Attention:
|
||||
${result.fileResults
|
||||
.filter(f => f.detectionRate < 80)
|
||||
.map(f => `- ${f.fileName}: ${f.detectionRate.toFixed(1)}% detection`)
|
||||
.join('\n') || 'None - all files meet targets'}
|
||||
`;
|
||||
|
||||
try {
|
||||
import('fs').then(fs => {
|
||||
fs.mkdirSync(resolve(process.cwd(), '.sisyphus/evidence'), { recursive: true });
|
||||
fs.writeFileSync(reportPath, reportContent);
|
||||
console.log(`📄 Report saved to: ${reportPath}`);
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore write errors
|
||||
}
|
||||
|
||||
// Exit with appropriate code for CI/CD
|
||||
const passed = result.detectionRate >= 85 && result.falsePositiveRate < 5;
|
||||
process.exit(passed ? 0 : 1);
|
||||
}
|
||||
Reference in New Issue
Block a user