thuban 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { execFileSync } = require('child_process');
7
+ const SecretScanner = require('./secret-scanner');
8
+
9
+ function createTempRepo() {
10
+ const rootPath = fs.mkdtempSync(path.join(os.tmpdir(), 'thuban-secret-scanner-'));
11
+ execFileSync('git', ['init'], { cwd: rootPath, stdio: 'ignore' });
12
+ execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: rootPath, stdio: 'ignore' });
13
+ execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: rootPath, stdio: 'ignore' });
14
+ return rootPath;
15
+ }
16
+
17
+ test('detects hardcoded secrets, .env exposure, and history leaks', async () => {
18
+ const rootPath = createTempRepo();
19
+
20
+ fs.writeFileSync(path.join(rootPath, '.gitignore'), 'node_modules/\n');
21
+ fs.writeFileSync(
22
+ path.join(rootPath, 'config.js'),
23
+ [
24
+ 'const stripe = "sk_live_1234567890abcdefghijklmnop";',
25
+ 'const password = "SuperSecret123!";',
26
+ 'const token = "Bearer abcdefghijklmnopqrstuvwxyz123456";',
27
+ 'const db = "postgres://appuser:dbpassword@localhost:5432/app";'
28
+ ].join('\n')
29
+ );
30
+ fs.writeFileSync(path.join(rootPath, '.env'), 'API_KEY=AIzaSyA123456789012345678901234567890123\n');
31
+
32
+ execFileSync('git', ['add', '.'], { cwd: rootPath, stdio: 'ignore' });
33
+ execFileSync('git', ['commit', '-m', 'initial'], { cwd: rootPath, stdio: 'ignore' });
34
+
35
+ fs.writeFileSync(path.join(rootPath, 'removed.js'), 'const oldKey = "ghp_abcdefghijklmnopqrstuvwxyz123456";\n');
36
+ execFileSync('git', ['add', 'removed.js'], { cwd: rootPath, stdio: 'ignore' });
37
+ execFileSync('git', ['commit', '-m', 'add leaked token'], { cwd: rootPath, stdio: 'ignore' });
38
+ fs.unlinkSync(path.join(rootPath, 'removed.js'));
39
+ execFileSync('git', ['add', '-A'], { cwd: rootPath, stdio: 'ignore' });
40
+ execFileSync('git', ['commit', '-m', 'remove leaked token'], { cwd: rootPath, stdio: 'ignore' });
41
+
42
+ const scanner = new SecretScanner({ rootPath });
43
+ const result = await scanner.scanAll();
44
+
45
+ assert.ok(result.issues.some((issue) => issue.type === 'Stripe Secret Key'));
46
+ assert.ok(result.issues.some((issue) => issue.type === 'Hardcoded Password or Secret Assignment'));
47
+ assert.ok(result.issues.some((issue) => issue.type === 'Database Connection String with Password'));
48
+ assert.ok(result.issues.some((issue) => issue.type === '.env File Present'));
49
+ assert.ok(result.issues.some((issue) => issue.type.includes('Git History')));
50
+ assert.ok(result.summary.bySeverity.critical >= 1);
51
+ });
52
+
53
+ test('does not flag gitignored env files as exposure issues', async () => {
54
+ const rootPath = createTempRepo();
55
+
56
+ fs.writeFileSync(path.join(rootPath, '.gitignore'), '.env\n');
57
+ fs.writeFileSync(path.join(rootPath, '.env'), 'TOKEN=example-value\n');
58
+
59
+ const scanner = new SecretScanner({ rootPath, includeGitHistory: false });
60
+ const issues = await scanner.scanFile(path.join(rootPath, '.env'));
61
+
62
+ assert.equal(issues.some((issue) => issue.type === '.env File Present'), false);
63
+ });
64
+
65
+ test('ignores placeholders, examples, tests, and virtualenv noise while catching modern keys', async () => {
66
+ const rootPath = createTempRepo();
67
+
68
+ fs.writeFileSync(path.join(rootPath, '.gitignore'), '.env.local\n');
69
+ fs.mkdirSync(path.join(rootPath, 'apps', 'api', 'tests'), { recursive: true });
70
+ fs.mkdirSync(path.join(rootPath, 'apps', 'web'), { recursive: true });
71
+ fs.mkdirSync(path.join(rootPath, '.venv', 'Lib', 'site-packages'), { recursive: true });
72
+
73
+ fs.writeFileSync(
74
+ path.join(rootPath, 'apps', 'api', 'tests', 'auth.test.js'),
75
+ 'const header = "Bearer local-dev-token";\n'
76
+ );
77
+ fs.writeFileSync(
78
+ path.join(rootPath, 'apps', 'web', '.env.example'),
79
+ 'CLERK_SECRET_KEY=sk_test_replace_me\nNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_replace_me\n'
80
+ );
81
+ fs.writeFileSync(
82
+ path.join(rootPath, '.venv', 'Lib', 'site-packages', 'pkg.py'),
83
+ 'TOKEN = "hf_94wBhPGp6KrrTH3KDchhKpRxZwd6dmHWLL"\n'
84
+ );
85
+ fs.writeFileSync(
86
+ path.join(rootPath, '.env.local'),
87
+ [
88
+ 'OPENAI_API_KEY=sk-proj-abcdefghijklmnopqrstuvwxyzABCDE123456789',
89
+ 'CARTESIA_API_KEY=sk_car_LbvdTSQmrEYgi5KCHN3C6m',
90
+ 'LIVEKIT_API_SECRET=M06JDMQfH0ZsfzFYmxgu1OnqhiSOHQJfH8jcn4kkcgmA'
91
+ ].join('\n')
92
+ );
93
+
94
+ const scanner = new SecretScanner({ rootPath, includeGitHistory: false });
95
+ const result = await scanner.scanAll();
96
+
97
+ assert.ok(result.issues.some((issue) => issue.type === 'OpenAI Project Key'));
98
+ assert.ok(result.issues.some((issue) => issue.type === 'Cartesia API Key'));
99
+ assert.ok(result.issues.some((issue) => issue.type === 'LiveKit API Secret'));
100
+ assert.equal(result.issues.some((issue) => issue.code.includes('local-dev-token')), false);
101
+ assert.equal(result.issues.some((issue) => issue.type === '.env File Present' && issue.file.includes('.env.example')), false);
102
+ assert.equal(result.issues.some((issue) => issue.file.includes('.venv')), false);
103
+ });