@indicated/vibeguard 1.0.0
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.
- package/.claude/settings.local.json +5 -0
- package/.github/workflows/ci.yml +65 -0
- package/.github/workflows/release.yml +85 -0
- package/PROGRESS.md +192 -0
- package/README.md +183 -0
- package/dist/api/license.d.ts +13 -0
- package/dist/api/license.d.ts.map +1 -0
- package/dist/api/license.js +138 -0
- package/dist/api/license.js.map +1 -0
- package/dist/api/rules.d.ts +13 -0
- package/dist/api/rules.d.ts.map +1 -0
- package/dist/api/rules.js +57 -0
- package/dist/api/rules.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +145 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/login.d.ts +4 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +121 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +14 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +3 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +52 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +3 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +114 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/config.d.ts +4 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +88 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +25 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output.d.ts +15 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +152 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +188 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/scanner/index.d.ts +15 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +207 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/parsers/javascript.d.ts +12 -0
- package/dist/scanner/parsers/javascript.d.ts.map +1 -0
- package/dist/scanner/parsers/javascript.js +266 -0
- package/dist/scanner/parsers/javascript.js.map +1 -0
- package/dist/scanner/parsers/python.d.ts +3 -0
- package/dist/scanner/parsers/python.d.ts.map +1 -0
- package/dist/scanner/parsers/python.js +108 -0
- package/dist/scanner/parsers/python.js.map +1 -0
- package/dist/scanner/rules/definitions.d.ts +5 -0
- package/dist/scanner/rules/definitions.d.ts.map +1 -0
- package/dist/scanner/rules/definitions.js +584 -0
- package/dist/scanner/rules/definitions.js.map +1 -0
- package/dist/scanner/rules/loader.d.ts +8 -0
- package/dist/scanner/rules/loader.d.ts.map +1 -0
- package/dist/scanner/rules/loader.js +45 -0
- package/dist/scanner/rules/loader.js.map +1 -0
- package/dist/scanner/rules/matcher.d.ts +11 -0
- package/dist/scanner/rules/matcher.d.ts.map +1 -0
- package/dist/scanner/rules/matcher.js +53 -0
- package/dist/scanner/rules/matcher.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/api/license.ts +120 -0
- package/src/api/rules.ts +70 -0
- package/src/cli/commands/init.ts +123 -0
- package/src/cli/commands/login.ts +92 -0
- package/src/cli/commands/mcp.ts +12 -0
- package/src/cli/commands/rules.ts +58 -0
- package/src/cli/commands/scan.ts +94 -0
- package/src/cli/config.ts +54 -0
- package/src/cli/index.ts +28 -0
- package/src/cli/output.ts +159 -0
- package/src/mcp/server.ts +195 -0
- package/src/scanner/index.ts +195 -0
- package/src/scanner/parsers/javascript.ts +285 -0
- package/src/scanner/parsers/python.ts +126 -0
- package/src/scanner/rules/definitions.ts +592 -0
- package/src/scanner/rules/loader.ts +59 -0
- package/src/scanner/rules/matcher.ts +68 -0
- package/src/types.ts +36 -0
- package/test-samples/secure.js +52 -0
- package/test-samples/vulnerable.js +56 -0
- package/test-samples/vulnerable.py +39 -0
- package/tests/helpers.ts +43 -0
- package/tests/rules/critical.test.ts +186 -0
- package/tests/rules/definitions.test.ts +167 -0
- package/tests/rules/high.test.ts +377 -0
- package/tests/rules/low.test.ts +172 -0
- package/tests/rules/medium.test.ts +224 -0
- package/tests/scanner/scanner.test.ts +161 -0
- package/tsconfig.json +19 -0
- package/vibe-coding-security-checklist.md +245 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { testRule, getRule } from '../helpers';
|
|
3
|
+
|
|
4
|
+
describe('High Security Rules', () => {
|
|
5
|
+
describe('missing-auth-route', () => {
|
|
6
|
+
const ruleId = 'missing-auth-route';
|
|
7
|
+
|
|
8
|
+
it('should exist', () => {
|
|
9
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have AST matcher configured', () => {
|
|
13
|
+
const rule = getRule(ruleId);
|
|
14
|
+
expect(rule?.astMatcher).toBe('missing-auth');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('xss-innerhtml', () => {
|
|
19
|
+
const ruleId = 'xss-innerhtml';
|
|
20
|
+
|
|
21
|
+
it('should exist', () => {
|
|
22
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should have AST matcher configured', () => {
|
|
26
|
+
const rule = getRule(ruleId);
|
|
27
|
+
expect(rule?.astMatcher).toBe('xss-innerhtml');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('secrets-localstorage', () => {
|
|
32
|
+
const ruleId = 'secrets-localstorage';
|
|
33
|
+
|
|
34
|
+
it('should exist', () => {
|
|
35
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should detect localStorage.setItem with token', () => {
|
|
39
|
+
expect(testRule(ruleId, `localStorage.setItem('token', jwt)`)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should detect localStorage.setItem with auth', () => {
|
|
43
|
+
expect(testRule(ruleId, `localStorage.setItem('auth', data)`)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should detect sessionStorage.setItem with jwt', () => {
|
|
47
|
+
expect(testRule(ruleId, `sessionStorage.setItem('jwt', token)`)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should detect localStorage.setItem with api-key', () => {
|
|
51
|
+
expect(testRule(ruleId, `localStorage.setItem('api-key', key)`)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should NOT detect localStorage for non-sensitive data', () => {
|
|
55
|
+
expect(testRule(ruleId, `localStorage.setItem('theme', 'dark')`)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should NOT detect localStorage for user preferences', () => {
|
|
59
|
+
expect(testRule(ruleId, `localStorage.setItem('language', 'en')`)).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('supabase-no-rls', () => {
|
|
64
|
+
const ruleId = 'supabase-no-rls';
|
|
65
|
+
|
|
66
|
+
it('should exist', () => {
|
|
67
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should detect .from().select()', () => {
|
|
71
|
+
expect(testRule(ruleId, `.from('users').select('*')`)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should detect .from().insert()', () => {
|
|
75
|
+
expect(testRule(ruleId, `.from('posts').insert(data)`)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should detect .from().update()', () => {
|
|
79
|
+
expect(testRule(ruleId, `.from('profiles').update({ name })`)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should detect .from().delete()', () => {
|
|
83
|
+
expect(testRule(ruleId, `.from('comments').delete()`)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('firebase-no-rules', () => {
|
|
88
|
+
const ruleId = 'firebase-no-rules';
|
|
89
|
+
|
|
90
|
+
it('should exist', () => {
|
|
91
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should detect firestore().collection()', () => {
|
|
95
|
+
expect(testRule(ruleId, `firestore().collection('users')`)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should detect firebase.database().ref()', () => {
|
|
99
|
+
expect(testRule(ruleId, `firebase.database().ref('posts')`)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('idor-vulnerability', () => {
|
|
104
|
+
const ruleId = 'idor-vulnerability';
|
|
105
|
+
|
|
106
|
+
it('should exist', () => {
|
|
107
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should have AST matcher configured', () => {
|
|
111
|
+
const rule = getRule(ruleId);
|
|
112
|
+
expect(rule?.astMatcher).toBe('idor');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('path-traversal', () => {
|
|
117
|
+
const ruleId = 'path-traversal';
|
|
118
|
+
|
|
119
|
+
it('should exist', () => {
|
|
120
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should detect readFile with req.params', () => {
|
|
124
|
+
expect(testRule(ruleId, `readFile(req.params.filename)`)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should detect readFileSync with query param', () => {
|
|
128
|
+
expect(testRule(ruleId, `readFileSync(req.query.path)`)).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should detect path.join with user input', () => {
|
|
132
|
+
expect(testRule(ruleId, `path.join(baseDir, req.params.file)`)).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should detect res.sendFile with params', () => {
|
|
136
|
+
expect(testRule(ruleId, `res.sendFile(req.params.file)`)).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should detect Python open with request', () => {
|
|
140
|
+
expect(testRule(ruleId, `open(request.args.get('file'))`)).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should NOT detect readFile with static path', () => {
|
|
144
|
+
expect(testRule(ruleId, `readFile('./config.json')`)).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('ssrf-vulnerability', () => {
|
|
149
|
+
const ruleId = 'ssrf-vulnerability';
|
|
150
|
+
|
|
151
|
+
it('should exist', () => {
|
|
152
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should detect fetch with user input', () => {
|
|
156
|
+
expect(testRule(ruleId, `fetch(req.body.url)`)).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should detect axios.get with params', () => {
|
|
160
|
+
expect(testRule(ruleId, `axios.get(req.query.endpoint)`)).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should detect Python requests with f-string', () => {
|
|
164
|
+
expect(testRule(ruleId, `requests.get(f"http://api.com/{user_input}")`)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should NOT detect fetch with static URL', () => {
|
|
168
|
+
expect(testRule(ruleId, `fetch('https://api.example.com/data')`)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('open-redirect', () => {
|
|
173
|
+
const ruleId = 'open-redirect';
|
|
174
|
+
|
|
175
|
+
it('should exist', () => {
|
|
176
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should detect res.redirect with req.query', () => {
|
|
180
|
+
expect(testRule(ruleId, `res.redirect(req.query.returnUrl)`)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should detect res.redirect with req.body', () => {
|
|
184
|
+
expect(testRule(ruleId, `res.redirect(req.body.next)`)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should detect window.location with params', () => {
|
|
188
|
+
expect(testRule(ruleId, `window.location = searchParams.get('redirect')`)).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should detect Django HttpResponseRedirect', () => {
|
|
192
|
+
expect(testRule(ruleId, `HttpResponseRedirect(request.GET.get('next'))`)).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should NOT detect redirect to static URL', () => {
|
|
196
|
+
expect(testRule(ruleId, `res.redirect('/dashboard')`)).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('insecure-cookie', () => {
|
|
201
|
+
const ruleId = 'insecure-cookie';
|
|
202
|
+
|
|
203
|
+
it('should exist', () => {
|
|
204
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should detect res.cookie for token without flags', () => {
|
|
208
|
+
expect(testRule(ruleId, ` res.cookie('token', jwt)`)).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should detect document.cookie setting session', () => {
|
|
212
|
+
expect(testRule(ruleId, ` document.cookie = "session=abc123"`)).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should NOT detect commented code', () => {
|
|
216
|
+
expect(testRule(ruleId, `// res.cookie('token', jwt, { httpOnly: true })`)).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should NOT detect non-sensitive cookies', () => {
|
|
220
|
+
expect(testRule(ruleId, ` document.cookie = "theme=dark"`)).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('missing-csrf', () => {
|
|
225
|
+
const ruleId = 'missing-csrf';
|
|
226
|
+
|
|
227
|
+
it('should exist', () => {
|
|
228
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should detect HTML form without CSRF token', () => {
|
|
232
|
+
expect(testRule(ruleId, `<form method="post" action="/submit"><input type="text"></form>`)).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should NOT detect form with CSRF token', () => {
|
|
236
|
+
// Pattern has limited lookahead, so forms with csrf close by should not match
|
|
237
|
+
expect(testRule(ruleId, `<form method="post" action="/submit">{% csrf_token %}<input></form>`)).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('nextjs-exposed-server-action', () => {
|
|
242
|
+
const ruleId = 'nextjs-exposed-server-action';
|
|
243
|
+
|
|
244
|
+
it('should exist', () => {
|
|
245
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should detect server action without auth check', () => {
|
|
249
|
+
const code = `'use server';\nexport async function deleteUser(id) {\n await db.delete(id);\n}`;
|
|
250
|
+
expect(testRule(ruleId, code)).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should NOT detect server action with session check', () => {
|
|
254
|
+
const code = `'use server';\nexport async function deleteUser(id) {\n const session = await getServerSession();\n}`;
|
|
255
|
+
expect(testRule(ruleId, code)).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('nextjs-api-route-no-auth', () => {
|
|
260
|
+
const ruleId = 'nextjs-api-route-no-auth';
|
|
261
|
+
|
|
262
|
+
it('should exist', () => {
|
|
263
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should detect API route without auth', () => {
|
|
267
|
+
const code = `export async function POST(request) {\n const data = await request.json();\n return Response.json({ ok: true });\n}`;
|
|
268
|
+
expect(testRule(ruleId, code)).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should NOT detect API route with getServerSession', () => {
|
|
272
|
+
const code = `export async function POST(request) {\n const session = await getServerSession();\n if (!session) return;\n}`;
|
|
273
|
+
expect(testRule(ruleId, code)).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('nextjs-dangerouslySetInnerHTML', () => {
|
|
278
|
+
const ruleId = 'nextjs-dangerouslySetInnerHTML';
|
|
279
|
+
|
|
280
|
+
it('should exist', () => {
|
|
281
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should detect dangerouslySetInnerHTML with variable', () => {
|
|
285
|
+
expect(testRule(ruleId, `dangerouslySetInnerHTML={{ __html: content }}`)).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should flag all dangerouslySetInnerHTML for review', () => {
|
|
289
|
+
// Note: Pattern flags all dangerouslySetInnerHTML for manual review since
|
|
290
|
+
// it's difficult to distinguish safe vs unsafe usage via regex
|
|
291
|
+
expect(testRule(ruleId, `dangerouslySetInnerHTML={{ __html: "<b>Hello</b>" }}`)).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('django-no-csrf-exempt', () => {
|
|
296
|
+
const ruleId = 'django-no-csrf-exempt';
|
|
297
|
+
|
|
298
|
+
it('should exist', () => {
|
|
299
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should detect @csrf_exempt decorator', () => {
|
|
303
|
+
expect(testRule(ruleId, `@csrf_exempt\ndef my_view(request):`)).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('fastapi-no-auth-dependency', () => {
|
|
308
|
+
const ruleId = 'fastapi-no-auth-dependency';
|
|
309
|
+
|
|
310
|
+
it('should exist', () => {
|
|
311
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should detect admin endpoint without Depends', () => {
|
|
315
|
+
const code = `@app.post("/admin/users")\nasync def create_user(data: dict):`;
|
|
316
|
+
expect(testRule(ruleId, code)).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should detect endpoint even with Depends (pattern limitation)', () => {
|
|
320
|
+
// Note: Current pattern has limitations - it checks function signature
|
|
321
|
+
// but Depends can appear in various positions. Manual review recommended.
|
|
322
|
+
const code = `@app.post("/admin/users")\nasync def create_user(data: dict, user: User = Depends(get_current_user)):`;
|
|
323
|
+
// Pattern matches because it can't reliably parse Python function signatures
|
|
324
|
+
expect(testRule(ruleId, code)).toBe(true);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('nestjs-no-auth-guard', () => {
|
|
329
|
+
const ruleId = 'nestjs-no-auth-guard';
|
|
330
|
+
|
|
331
|
+
it('should exist', () => {
|
|
332
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should detect admin controller without guard', () => {
|
|
336
|
+
const code = `@Controller('admin')\nexport class AdminController {`;
|
|
337
|
+
expect(testRule(ruleId, code)).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should NOT detect controller with UseGuards', () => {
|
|
341
|
+
const code = `@Controller('admin')\n@UseGuards(AuthGuard)\nexport class AdminController {`;
|
|
342
|
+
expect(testRule(ruleId, code)).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe('react-href-javascript', () => {
|
|
347
|
+
const ruleId = 'react-href-javascript';
|
|
348
|
+
|
|
349
|
+
it('should exist', () => {
|
|
350
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should detect href with javascript: protocol', () => {
|
|
354
|
+
expect(testRule(ruleId, `href="javascript:alert(1)"`)).toBe(true);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should detect dynamic href with javascript:', () => {
|
|
358
|
+
expect(testRule(ruleId, `href={"javascript:" + code}`)).toBe(true);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should NOT detect normal href', () => {
|
|
362
|
+
expect(testRule(ruleId, `href="/dashboard"`)).toBe(false);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('express-session-insecure', () => {
|
|
367
|
+
const ruleId = 'express-session-insecure';
|
|
368
|
+
|
|
369
|
+
it('should exist', () => {
|
|
370
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should detect session without secure options', () => {
|
|
374
|
+
expect(testRule(ruleId, `session({ secret: 'mysecret' })`)).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { testRule, getRule } from '../helpers';
|
|
3
|
+
|
|
4
|
+
describe('Low Security Rules', () => {
|
|
5
|
+
describe('verbose-errors', () => {
|
|
6
|
+
const ruleId = 'verbose-errors';
|
|
7
|
+
|
|
8
|
+
it('should exist', () => {
|
|
9
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should detect res.json(error)', () => {
|
|
13
|
+
expect(testRule(ruleId, `res.json(error)`)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should detect res.send(err.message)', () => {
|
|
17
|
+
expect(testRule(ruleId, `res.send(err.message)`)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should detect status(500).json with error', () => {
|
|
21
|
+
expect(testRule(ruleId, `.status(500).json({ error: err.message })`)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should NOT detect generic error response', () => {
|
|
25
|
+
expect(testRule(ruleId, `res.json({ error: 'An error occurred' })`)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('missing-rate-limit', () => {
|
|
30
|
+
const ruleId = 'missing-rate-limit';
|
|
31
|
+
|
|
32
|
+
it('should exist', () => {
|
|
33
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should detect app.post("/login") without middleware', () => {
|
|
37
|
+
expect(testRule(ruleId, `app.post("/login", async (`)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should detect app.post("/signin") without middleware', () => {
|
|
41
|
+
expect(testRule(ruleId, `app.post("/signin", async (`)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should detect app.post("/register") without middleware', () => {
|
|
45
|
+
expect(testRule(ruleId, `app.post("/register", async (`)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should detect app.post("/forgot-password")', () => {
|
|
49
|
+
expect(testRule(ruleId, `app.post("/forgot-password", async (`)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should NOT detect non-auth endpoints', () => {
|
|
53
|
+
expect(testRule(ruleId, `app.post("/api/posts", async (`)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('console-log-sensitive', () => {
|
|
58
|
+
const ruleId = 'console-log-sensitive';
|
|
59
|
+
|
|
60
|
+
it('should exist', () => {
|
|
61
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should detect console.log(password)', () => {
|
|
65
|
+
expect(testRule(ruleId, `console.log(password)`)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should detect console.log(secret)', () => {
|
|
69
|
+
expect(testRule(ruleId, `console.log(secret)`)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should detect console.log(apiKey)', () => {
|
|
73
|
+
expect(testRule(ruleId, `console.log(apiKey)`)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should detect console.log with token variable', () => {
|
|
77
|
+
expect(testRule(ruleId, `console.log(token)`)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should detect Python print with f-string password', () => {
|
|
81
|
+
expect(testRule(ruleId, `print(f"Password: {password}")`)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should NOT detect console.log with string literal', () => {
|
|
85
|
+
expect(testRule(ruleId, `console.log('Password is valid')`)).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should NOT detect console.log with non-sensitive data', () => {
|
|
89
|
+
expect(testRule(ruleId, `console.log(username)`)).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('debug-mode-enabled', () => {
|
|
94
|
+
const ruleId = 'debug-mode-enabled';
|
|
95
|
+
|
|
96
|
+
it('should exist', () => {
|
|
97
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should detect DEBUG = True', () => {
|
|
101
|
+
expect(testRule(ruleId, `DEBUG = True`)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should detect debug: true', () => {
|
|
105
|
+
expect(testRule(ruleId, `{ debug: true }`)).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should detect NODE_ENV=development', () => {
|
|
109
|
+
expect(testRule(ruleId, `process.env.NODE_ENV = 'development'`)).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should NOT detect DEBUG = False', () => {
|
|
113
|
+
expect(testRule(ruleId, `DEBUG = False`)).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should NOT detect debug: false', () => {
|
|
117
|
+
expect(testRule(ruleId, `{ debug: false }`)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('prototype-pollution', () => {
|
|
122
|
+
const ruleId = 'prototype-pollution';
|
|
123
|
+
|
|
124
|
+
it('should exist', () => {
|
|
125
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should detect Object.assign with req.body', () => {
|
|
129
|
+
expect(testRule(ruleId, `Object.assign({}, req.body)`)).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should detect spread operator with body', () => {
|
|
133
|
+
expect(testRule(ruleId, `{ ...body, name }`)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should detect spread with query', () => {
|
|
137
|
+
expect(testRule(ruleId, `{ ...query }`)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should detect lodash.merge with req.body', () => {
|
|
141
|
+
expect(testRule(ruleId, `lodash.merge(target, req.body)`)).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should detect deepmerge with body', () => {
|
|
145
|
+
expect(testRule(ruleId, `deepmerge(config, body.settings)`)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should NOT detect Object.assign with static object', () => {
|
|
149
|
+
expect(testRule(ruleId, `Object.assign({}, { name: 'test' })`)).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('nestjs-exposed-internal-exception', () => {
|
|
154
|
+
const ruleId = 'nestjs-exposed-internal-exception';
|
|
155
|
+
|
|
156
|
+
it('should exist', () => {
|
|
157
|
+
expect(getRule(ruleId)).toBeDefined();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should detect throw new Error(err.message)', () => {
|
|
161
|
+
expect(testRule(ruleId, `throw new Error(err.message)`)).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should detect throw new InternalServerErrorException(error.message)', () => {
|
|
165
|
+
expect(testRule(ruleId, `throw new InternalServerErrorException(error.message)`)).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should NOT detect throw new HttpException with generic message', () => {
|
|
169
|
+
expect(testRule(ruleId, `throw new HttpException('Internal error', 500)`)).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|