@capgo/capgo-sec 1.0.4

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,235 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { allRules, ruleCount } from '../src/rules/index';
3
+ import { secretsRules } from '../src/rules/secrets';
4
+ import { storageRules } from '../src/rules/storage';
5
+ import { networkRules } from '../src/rules/network';
6
+ import { capacitorRules } from '../src/rules/capacitor';
7
+ import { androidRules } from '../src/rules/android';
8
+ import { iosRules } from '../src/rules/ios';
9
+ import { authenticationRules } from '../src/rules/authentication';
10
+ import { webviewRules } from '../src/rules/webview';
11
+ import { cryptographyRules } from '../src/rules/cryptography';
12
+ import { loggingRules, debugRules } from '../src/rules/logging';
13
+
14
+ describe('Security Rules', () => {
15
+ test('should have correct total rule count', () => {
16
+ expect(ruleCount).toBeGreaterThan(60);
17
+ expect(allRules.length).toBe(ruleCount);
18
+ });
19
+
20
+ test('all rules should have required properties', () => {
21
+ for (const rule of allRules) {
22
+ expect(rule.id).toBeDefined();
23
+ expect(rule.name).toBeDefined();
24
+ expect(rule.description).toBeDefined();
25
+ expect(rule.severity).toBeDefined();
26
+ expect(rule.category).toBeDefined();
27
+ expect(rule.remediation).toBeDefined();
28
+ expect(['critical', 'high', 'medium', 'low', 'info']).toContain(rule.severity);
29
+ }
30
+ });
31
+
32
+ test('all rules should have unique IDs', () => {
33
+ const ids = allRules.map(r => r.id);
34
+ const uniqueIds = new Set(ids);
35
+ expect(uniqueIds.size).toBe(ids.length);
36
+ });
37
+
38
+ test('secrets rules should detect hardcoded API keys', () => {
39
+ const rule = secretsRules.find(r => r.id === 'SEC001');
40
+ expect(rule).toBeDefined();
41
+
42
+ const testCode = `const apiKey = "AKIA1234567890ABCDEF";`;
43
+ const findings = rule!.check!(testCode, 'test.ts');
44
+
45
+ expect(findings.length).toBeGreaterThan(0);
46
+ expect(findings[0].severity).toBe('critical');
47
+ });
48
+
49
+ test('secrets rules should detect Firebase keys', () => {
50
+ const rule = secretsRules.find(r => r.id === 'SEC001');
51
+ expect(rule).toBeDefined();
52
+
53
+ const testCode = `const firebase = "AIzaSyDOCAbC123dEf456GhI789jKl012mNo3456";`;
54
+ const findings = rule!.check!(testCode, 'test.ts');
55
+
56
+ expect(findings.length).toBeGreaterThan(0);
57
+ });
58
+
59
+ test('storage rules should detect sensitive data in Preferences', () => {
60
+ const rule = storageRules.find(r => r.id === 'STO001');
61
+ expect(rule).toBeDefined();
62
+
63
+ const testCode = `Preferences.set({ key: 'userPassword', value: password });`;
64
+ const findings = rule!.check!(testCode, 'test.ts');
65
+
66
+ expect(findings.length).toBeGreaterThan(0);
67
+ expect(findings[0].severity).toBe('high');
68
+ });
69
+
70
+ test('storage rules should detect localStorage for sensitive data', () => {
71
+ const rule = storageRules.find(r => r.id === 'STO002');
72
+ expect(rule).toBeDefined();
73
+
74
+ const testCode = `localStorage.setItem("authToken", token);`;
75
+ const findings = rule!.check!(testCode, 'test.ts');
76
+
77
+ expect(findings.length).toBeGreaterThan(0);
78
+ });
79
+
80
+ test('network rules should detect HTTP URLs', () => {
81
+ const rule = networkRules.find(r => r.id === 'NET001');
82
+ expect(rule).toBeDefined();
83
+
84
+ const testCode = `fetch("http://api.example.com/data");`;
85
+ const findings = rule!.check!(testCode, 'test.ts');
86
+
87
+ expect(findings.length).toBeGreaterThan(0);
88
+ expect(findings[0].severity).toBe('high');
89
+ });
90
+
91
+ test('network rules should allow localhost HTTP', () => {
92
+ const rule = networkRules.find(r => r.id === 'NET001');
93
+ expect(rule).toBeDefined();
94
+
95
+ const testCode = `fetch("http://localhost:3000/api");`;
96
+ const findings = rule!.check!(testCode, 'test.ts');
97
+
98
+ expect(findings.length).toBe(0);
99
+ });
100
+
101
+ test('capacitor rules should detect WebView debug mode', () => {
102
+ const rule = capacitorRules.find(r => r.id === 'CAP001');
103
+ expect(rule).toBeDefined();
104
+
105
+ const testCode = `
106
+ export default {
107
+ webContentsDebuggingEnabled: true,
108
+ appId: 'com.example.app'
109
+ };
110
+ `;
111
+ const findings = rule!.check!(testCode, 'capacitor.config.ts');
112
+
113
+ expect(findings.length).toBeGreaterThan(0);
114
+ expect(findings[0].severity).toBe('critical');
115
+ });
116
+
117
+ test('capacitor rules should detect eval usage', () => {
118
+ const rule = capacitorRules.find(r => r.id === 'CAP006');
119
+ expect(rule).toBeDefined();
120
+
121
+ const testCode = `const result = eval(userInput);`;
122
+ const findings = rule!.check!(testCode, 'test.ts');
123
+
124
+ expect(findings.length).toBeGreaterThan(0);
125
+ expect(findings[0].severity).toBe('critical');
126
+ });
127
+
128
+ test('android rules should detect cleartext traffic', () => {
129
+ const rule = androidRules.find(r => r.id === 'AND001');
130
+ expect(rule).toBeDefined();
131
+
132
+ const testXml = `
133
+ <application
134
+ android:usesCleartextTraffic="true"
135
+ android:label="@string/app_name">
136
+ </application>
137
+ `;
138
+ const findings = rule!.check!(testXml, 'AndroidManifest.xml');
139
+
140
+ expect(findings.length).toBeGreaterThan(0);
141
+ expect(findings[0].severity).toBe('critical');
142
+ });
143
+
144
+ test('android rules should detect debug mode', () => {
145
+ const rule = androidRules.find(r => r.id === 'AND002');
146
+ expect(rule).toBeDefined();
147
+
148
+ const testXml = `
149
+ <application
150
+ android:debuggable="true"
151
+ android:label="@string/app_name">
152
+ </application>
153
+ `;
154
+ const findings = rule!.check!(testXml, 'AndroidManifest.xml');
155
+
156
+ expect(findings.length).toBeGreaterThan(0);
157
+ });
158
+
159
+ test('ios rules should detect ATS disabled', () => {
160
+ const rule = iosRules.find(r => r.id === 'IOS001');
161
+ expect(rule).toBeDefined();
162
+
163
+ const testPlist = `
164
+ <key>NSAppTransportSecurity</key>
165
+ <dict>
166
+ <key>NSAllowsArbitraryLoads</key>
167
+ <true />
168
+ </dict>
169
+ `;
170
+ const findings = rule!.check!(testPlist, 'Info.plist');
171
+
172
+ expect(findings.length).toBeGreaterThan(0);
173
+ expect(findings[0].severity).toBe('critical');
174
+ });
175
+
176
+ test('authentication rules should detect weak JWT validation', () => {
177
+ const rule = authenticationRules.find(r => r.id === 'AUTH001');
178
+ expect(rule).toBeDefined();
179
+
180
+ const testCode = `const claims = jwtDecode(token);`;
181
+ const findings = rule!.check!(testCode, 'test.ts');
182
+
183
+ expect(findings.length).toBeGreaterThan(0);
184
+ });
185
+
186
+ test('authentication rules should detect Math.random in security context', () => {
187
+ const rule = authenticationRules.find(r => r.id === 'AUTH003');
188
+ expect(rule).toBeDefined();
189
+
190
+ const testCode = `const token = Math.random().toString(36);`;
191
+ const findings = rule!.check!(testCode, 'test.ts');
192
+
193
+ expect(findings.length).toBeGreaterThan(0);
194
+ });
195
+
196
+ test('webview rules should detect innerHTML usage', () => {
197
+ const rule = webviewRules.find(r => r.id === 'WEB001');
198
+ expect(rule).toBeDefined();
199
+
200
+ const testCode = `element.innerHTML = userInput;`;
201
+ const findings = rule!.check!(testCode, 'test.ts');
202
+
203
+ expect(findings.length).toBeGreaterThan(0);
204
+ });
205
+
206
+ test('cryptography rules should detect weak algorithms', () => {
207
+ const rule = cryptographyRules.find(r => r.id === 'CRY001');
208
+ expect(rule).toBeDefined();
209
+
210
+ const testCode = `const hash = crypto.createHash('md5').update(data).digest('hex');`;
211
+ const findings = rule!.check!(testCode, 'test.ts');
212
+
213
+ expect(findings.length).toBeGreaterThan(0);
214
+ });
215
+
216
+ test('logging rules should detect sensitive data in logs', () => {
217
+ const rule = loggingRules.find(r => r.id === 'LOG001');
218
+ expect(rule).toBeDefined();
219
+
220
+ const testCode = `console.log('User password:', password);`;
221
+ const findings = rule!.check!(testCode, 'test.ts');
222
+
223
+ expect(findings.length).toBeGreaterThan(0);
224
+ });
225
+
226
+ test('debug rules should detect debugger statements', () => {
227
+ const rule = debugRules.find(r => r.id === 'DBG001');
228
+ expect(rule).toBeDefined();
229
+
230
+ const testCode = `function test() { debugger; return 1; }`;
231
+ const findings = rule!.check!(testCode, 'test.ts');
232
+
233
+ expect(findings.length).toBeGreaterThan(0);
234
+ });
235
+ });
@@ -0,0 +1,292 @@
1
+ import { describe, expect, test, beforeAll, afterAll } from 'bun:test';
2
+ import { SecurityScanner } from '../src/scanners/engine';
3
+ import { mkdir, rm } from 'fs/promises';
4
+ import { join } from 'path';
5
+
6
+ const TEST_DIR = '/tmp/capsec-test-project';
7
+
8
+ describe('SecurityScanner', () => {
9
+ beforeAll(async () => {
10
+ // Create test project directory
11
+ await mkdir(TEST_DIR, { recursive: true });
12
+ await mkdir(join(TEST_DIR, 'src'), { recursive: true });
13
+ await mkdir(join(TEST_DIR, 'android/app/src/main'), { recursive: true });
14
+ await mkdir(join(TEST_DIR, 'ios/App'), { recursive: true });
15
+
16
+ // Create test files with vulnerabilities using Bun.write
17
+ await Bun.write(
18
+ join(TEST_DIR, 'src/api.ts'),
19
+ `
20
+ const API_KEY = "AKIA1234567890ABCDEF";
21
+ const secret = "my_secret_key_for_testing_purposes";
22
+
23
+ export async function fetchData() {
24
+ return fetch("http://api.example.com/data");
25
+ }
26
+
27
+ export function processInput(input: string) {
28
+ return eval(input);
29
+ }
30
+ `
31
+ );
32
+
33
+ await Bun.write(
34
+ join(TEST_DIR, 'src/auth.ts'),
35
+ `
36
+ import { jwtDecode } from 'jwt-decode';
37
+
38
+ export function getUserFromToken(token: string) {
39
+ const user = jwtDecode(token);
40
+ console.log('User token:', token);
41
+ return user;
42
+ }
43
+
44
+ export function generateSessionId() {
45
+ return Math.random().toString(36);
46
+ }
47
+ `
48
+ );
49
+
50
+ await Bun.write(
51
+ join(TEST_DIR, 'capacitor.config.ts'),
52
+ `
53
+ import { CapacitorConfig } from '@capacitor/cli';
54
+
55
+ const config: CapacitorConfig = {
56
+ appId: 'com.example.app',
57
+ appName: 'Test App',
58
+ webDir: 'dist',
59
+ server: {
60
+ cleartext: true
61
+ },
62
+ android: {
63
+ webContentsDebuggingEnabled: true,
64
+ allowMixedContent: true
65
+ }
66
+ };
67
+
68
+ export default config;
69
+ `
70
+ );
71
+
72
+ await Bun.write(
73
+ join(TEST_DIR, 'android/app/src/main/AndroidManifest.xml'),
74
+ `
75
+ <?xml version="1.0" encoding="utf-8"?>
76
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
77
+ <application
78
+ android:usesCleartextTraffic="true"
79
+ android:debuggable="true"
80
+ android:allowBackup="true">
81
+ </application>
82
+ <uses-permission android:name="android.permission.READ_SMS"/>
83
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
84
+ </manifest>
85
+ `
86
+ );
87
+
88
+ await Bun.write(
89
+ join(TEST_DIR, 'ios/App/Info.plist'),
90
+ `
91
+ <?xml version="1.0" encoding="UTF-8"?>
92
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN">
93
+ <plist version="1.0">
94
+ <dict>
95
+ <key>NSAppTransportSecurity</key>
96
+ <dict>
97
+ <key>NSAllowsArbitraryLoads</key>
98
+ <true />
99
+ </dict>
100
+ </dict>
101
+ </plist>
102
+ `
103
+ );
104
+
105
+ await Bun.write(
106
+ join(TEST_DIR, 'src/storage.ts'),
107
+ `
108
+ import { Preferences } from '@capacitor/preferences';
109
+
110
+ export async function saveCredentials(password: string) {
111
+ await Preferences.set({ key: 'userPassword', value: password });
112
+ localStorage.setItem('authToken', 'secret');
113
+ }
114
+ `
115
+ );
116
+ });
117
+
118
+ afterAll(async () => {
119
+ // Cleanup test directory
120
+ await rm(TEST_DIR, { recursive: true, force: true });
121
+ });
122
+
123
+ test('should scan project and find vulnerabilities', async () => {
124
+ const scanner = new SecurityScanner({
125
+ path: TEST_DIR
126
+ });
127
+
128
+ const result = await scanner.scan();
129
+
130
+ expect(result.filesScanned).toBeGreaterThan(0);
131
+ expect(result.findings.length).toBeGreaterThan(0);
132
+ expect(result.summary.total).toBeGreaterThan(0);
133
+ });
134
+
135
+ test('should find critical severity issues', async () => {
136
+ const scanner = new SecurityScanner({
137
+ path: TEST_DIR
138
+ });
139
+
140
+ const result = await scanner.scan();
141
+
142
+ expect(result.summary.critical).toBeGreaterThan(0);
143
+ });
144
+
145
+ test('should find high severity issues', async () => {
146
+ const scanner = new SecurityScanner({
147
+ path: TEST_DIR
148
+ });
149
+
150
+ const result = await scanner.scan();
151
+
152
+ expect(result.summary.high).toBeGreaterThan(0);
153
+ });
154
+
155
+ test('should detect secrets in code', async () => {
156
+ const scanner = new SecurityScanner({
157
+ path: TEST_DIR,
158
+ categories: ['secrets']
159
+ });
160
+
161
+ const result = await scanner.scan();
162
+
163
+ const secretFindings = result.findings.filter(f => f.category === 'secrets');
164
+ expect(secretFindings.length).toBeGreaterThan(0);
165
+ });
166
+
167
+ test('should detect network issues', async () => {
168
+ const scanner = new SecurityScanner({
169
+ path: TEST_DIR,
170
+ categories: ['network']
171
+ });
172
+
173
+ const result = await scanner.scan();
174
+
175
+ const networkFindings = result.findings.filter(f => f.category === 'network');
176
+ expect(networkFindings.length).toBeGreaterThan(0);
177
+ });
178
+
179
+ test('should detect capacitor config issues', async () => {
180
+ const scanner = new SecurityScanner({
181
+ path: TEST_DIR,
182
+ categories: ['capacitor']
183
+ });
184
+
185
+ const result = await scanner.scan();
186
+
187
+ const capFindings = result.findings.filter(f => f.category === 'capacitor');
188
+ expect(capFindings.length).toBeGreaterThan(0);
189
+ });
190
+
191
+ test('should detect android issues', async () => {
192
+ const scanner = new SecurityScanner({
193
+ path: TEST_DIR,
194
+ categories: ['android']
195
+ });
196
+
197
+ const result = await scanner.scan();
198
+
199
+ const androidFindings = result.findings.filter(f => f.category === 'android');
200
+ expect(androidFindings.length).toBeGreaterThan(0);
201
+ });
202
+
203
+ test('should detect iOS issues', async () => {
204
+ const scanner = new SecurityScanner({
205
+ path: TEST_DIR,
206
+ categories: ['ios']
207
+ });
208
+
209
+ const result = await scanner.scan();
210
+
211
+ const iosFindings = result.findings.filter(f => f.category === 'ios');
212
+ expect(iosFindings.length).toBeGreaterThan(0);
213
+ });
214
+
215
+ test('should filter by severity', async () => {
216
+ const scanner = new SecurityScanner({
217
+ path: TEST_DIR,
218
+ severity: 'high' // Show critical and high
219
+ });
220
+
221
+ const result = await scanner.scan();
222
+
223
+ // All findings should be critical or high
224
+ for (const finding of result.findings) {
225
+ expect(['critical', 'high']).toContain(finding.severity);
226
+ }
227
+ });
228
+
229
+ test('should return findings sorted by severity', async () => {
230
+ const scanner = new SecurityScanner({
231
+ path: TEST_DIR
232
+ });
233
+
234
+ const result = await scanner.scan();
235
+
236
+ const severityOrder = ['critical', 'high', 'medium', 'low', 'info'];
237
+ let lastIndex = -1;
238
+
239
+ for (const finding of result.findings) {
240
+ const currentIndex = severityOrder.indexOf(finding.severity);
241
+ expect(currentIndex).toBeGreaterThanOrEqual(lastIndex);
242
+ lastIndex = currentIndex;
243
+ }
244
+ });
245
+
246
+ test('should include remediation for all findings', async () => {
247
+ const scanner = new SecurityScanner({
248
+ path: TEST_DIR
249
+ });
250
+
251
+ const result = await scanner.scan();
252
+
253
+ for (const finding of result.findings) {
254
+ expect(finding.remediation).toBeDefined();
255
+ expect(finding.remediation.length).toBeGreaterThan(0);
256
+ }
257
+ });
258
+
259
+ test('should provide correct summary counts', async () => {
260
+ const scanner = new SecurityScanner({
261
+ path: TEST_DIR
262
+ });
263
+
264
+ const result = await scanner.scan();
265
+
266
+ const countedTotal =
267
+ result.summary.critical +
268
+ result.summary.high +
269
+ result.summary.medium +
270
+ result.summary.low +
271
+ result.summary.info;
272
+
273
+ expect(countedTotal).toBe(result.summary.total);
274
+ expect(result.summary.total).toBe(result.findings.length);
275
+ });
276
+
277
+ test('should have timing information', async () => {
278
+ const scanner = new SecurityScanner({
279
+ path: TEST_DIR
280
+ });
281
+
282
+ const result = await scanner.scan();
283
+
284
+ expect(result.duration).toBeGreaterThan(0);
285
+ expect(result.timestamp).toBeDefined();
286
+ });
287
+
288
+ test('getRuleCount should return correct count', () => {
289
+ const count = SecurityScanner.getRuleCount();
290
+ expect(count).toBeGreaterThan(60);
291
+ });
292
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "resolveJsonModule": true,
15
+ "types": ["bun-types"]
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "test"]
19
+ }