create-qa-architect 5.13.6 → 5.14.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/.semgrep/defensive-patterns.yaml +356 -0
- package/.semgrep/vibe-audit-rules.yaml +332 -0
- package/LICENSE +192 -39
- package/README.md +98 -46
- package/config/requirements-dev.txt +2 -2
- package/docs/POLAR-DEPLOYMENT.md +157 -0
- package/docs/plans/PLAN-vibe-code-auditor.md +130 -0
- package/docs/plans/POLAR-MIGRATION.md +111 -0
- package/docs/plans/pro-features-2026-05.md +159 -0
- package/lib/commands/analyze-ci.js +20 -11
- package/lib/commands/audit.js +668 -0
- package/lib/commands/ci-doctor.js +341 -0
- package/lib/commands/history-scan.js +342 -0
- package/lib/commands/index.js +8 -0
- package/lib/commands/pr-check.js +484 -0
- package/lib/commands/prelaunch-setup.js +4 -0
- package/lib/commands/ship-check.js +570 -0
- package/lib/license-validator.js +200 -6
- package/lib/licensing.js +99 -11
- package/package.json +7 -6
- package/scripts/deploy-consumers.sh +10 -5
- package/scripts/risk-policy-gate.js +410 -0
- package/setup.js +132 -4
- /package/docs/{STRIPE-LIVE-MODE-DEPLOYMENT.md → _archive/STRIPE-LIVE-MODE-DEPLOYMENT.md} +0 -0
- /package/lib/{billing-dashboard.html → _archive/billing-dashboard.html} +0 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Defensive Patterns Semgrep Rules (CS-090)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Security-focused rules that complement ESLint defensive patterns.
|
|
6
|
+
# Run with: semgrep --config .semgrep/defensive-patterns.yaml
|
|
7
|
+
#
|
|
8
|
+
# These rules catch patterns that are difficult or impossible to detect
|
|
9
|
+
# with ESLint alone.
|
|
10
|
+
# =============================================================================
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# PROTOTYPE POLLUTION
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
- id: prototype-pollution-json-parse
|
|
16
|
+
message: |
|
|
17
|
+
JSON.parse on user input can lead to prototype pollution if the parsed
|
|
18
|
+
object has __proto__ properties. Use a safe parse function that strips
|
|
19
|
+
dangerous properties.
|
|
20
|
+
severity: ERROR
|
|
21
|
+
languages:
|
|
22
|
+
- javascript
|
|
23
|
+
- typescript
|
|
24
|
+
pattern-either:
|
|
25
|
+
- pattern: JSON.parse($REQ.body)
|
|
26
|
+
- pattern: JSON.parse($REQ.query.$PARAM)
|
|
27
|
+
- pattern: JSON.parse($REQ.params.$PARAM)
|
|
28
|
+
- pattern: JSON.parse(await $REQ.json())
|
|
29
|
+
metadata:
|
|
30
|
+
category: security
|
|
31
|
+
cwe: 'CWE-1321'
|
|
32
|
+
owasp: 'A03:2021'
|
|
33
|
+
references:
|
|
34
|
+
- https://portswigger.net/web-security/prototype-pollution
|
|
35
|
+
|
|
36
|
+
- id: prototype-pollution-object-assign
|
|
37
|
+
message: |
|
|
38
|
+
Object.assign with user-controlled input can lead to prototype pollution.
|
|
39
|
+
Sanitize input or use a safer merge function.
|
|
40
|
+
severity: WARNING
|
|
41
|
+
languages:
|
|
42
|
+
- javascript
|
|
43
|
+
- typescript
|
|
44
|
+
pattern-either:
|
|
45
|
+
- pattern: Object.assign($OBJ, $REQ.body)
|
|
46
|
+
- pattern: Object.assign($OBJ, ...$USER_INPUT)
|
|
47
|
+
metadata:
|
|
48
|
+
category: security
|
|
49
|
+
cwe: 'CWE-1321'
|
|
50
|
+
|
|
51
|
+
- id: prototype-pollution-spread
|
|
52
|
+
message: |
|
|
53
|
+
Spreading user input directly into an object can lead to prototype
|
|
54
|
+
pollution. Validate keys before spreading.
|
|
55
|
+
severity: WARNING
|
|
56
|
+
languages:
|
|
57
|
+
- javascript
|
|
58
|
+
- typescript
|
|
59
|
+
pattern: '{ ...$REQ.body }'
|
|
60
|
+
metadata:
|
|
61
|
+
category: security
|
|
62
|
+
cwe: 'CWE-1321'
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# SQL INJECTION
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
- id: sql-injection-template-string
|
|
68
|
+
message: |
|
|
69
|
+
SQL query built with template string containing user input. Use
|
|
70
|
+
parameterized queries instead.
|
|
71
|
+
severity: ERROR
|
|
72
|
+
languages:
|
|
73
|
+
- javascript
|
|
74
|
+
- typescript
|
|
75
|
+
pattern-either:
|
|
76
|
+
- pattern: $DB.query(`...${$USER_INPUT}...`)
|
|
77
|
+
- pattern: $DB.raw(`...${$USER_INPUT}...`)
|
|
78
|
+
- pattern: $PRISMA.$queryRaw(`...${$USER_INPUT}...`)
|
|
79
|
+
metadata:
|
|
80
|
+
category: security
|
|
81
|
+
cwe: 'CWE-89'
|
|
82
|
+
owasp: 'A03:2021'
|
|
83
|
+
|
|
84
|
+
- id: sql-injection-string-concat
|
|
85
|
+
message: |
|
|
86
|
+
SQL query built with string concatenation. Use parameterized queries.
|
|
87
|
+
severity: ERROR
|
|
88
|
+
languages:
|
|
89
|
+
- javascript
|
|
90
|
+
- typescript
|
|
91
|
+
pattern-either:
|
|
92
|
+
- pattern: $DB.query("..." + $USER_INPUT + "...")
|
|
93
|
+
metadata:
|
|
94
|
+
category: security
|
|
95
|
+
cwe: 'CWE-89'
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# AUTHENTICATION BYPASS
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
- id: auth-bypass-or-condition
|
|
101
|
+
message: |
|
|
102
|
+
OR condition in authentication check may allow bypass. Use AND conditions
|
|
103
|
+
for security checks.
|
|
104
|
+
severity: WARNING
|
|
105
|
+
languages:
|
|
106
|
+
- javascript
|
|
107
|
+
- typescript
|
|
108
|
+
pattern-either:
|
|
109
|
+
- pattern: if ($AUTH || $OTHER) { ... }
|
|
110
|
+
- pattern: |
|
|
111
|
+
$AUTH || $OTHER ? $TRUE : $FALSE
|
|
112
|
+
metadata:
|
|
113
|
+
category: security
|
|
114
|
+
cwe: 'CWE-287'
|
|
115
|
+
|
|
116
|
+
- id: auth-skip-on-dev
|
|
117
|
+
message: |
|
|
118
|
+
Authentication skipped in development mode. This pattern can accidentally
|
|
119
|
+
reach production. Use environment-specific auth instead.
|
|
120
|
+
severity: WARNING
|
|
121
|
+
languages:
|
|
122
|
+
- javascript
|
|
123
|
+
- typescript
|
|
124
|
+
pattern-either:
|
|
125
|
+
- pattern: |
|
|
126
|
+
if (process.env.NODE_ENV !== "production") {
|
|
127
|
+
return $ALLOW;
|
|
128
|
+
}
|
|
129
|
+
metadata:
|
|
130
|
+
category: security
|
|
131
|
+
cwe: 'CWE-287'
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# INSECURE RANDOMNESS
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
- id: insecure-random-token
|
|
137
|
+
message: |
|
|
138
|
+
Math.random() is not cryptographically secure. Use crypto.randomBytes()
|
|
139
|
+
or crypto.randomUUID() for tokens, IDs, and secrets.
|
|
140
|
+
severity: ERROR
|
|
141
|
+
languages:
|
|
142
|
+
- javascript
|
|
143
|
+
- typescript
|
|
144
|
+
pattern-either:
|
|
145
|
+
- pattern: Math.random().toString($BASE)
|
|
146
|
+
- pattern: Math.random().toString($BASE).slice($N)
|
|
147
|
+
- pattern: Math.random().toString($BASE).substring($N)
|
|
148
|
+
metadata:
|
|
149
|
+
category: security
|
|
150
|
+
cwe: 'CWE-330'
|
|
151
|
+
fix: "Use crypto.randomBytes(32).toString('hex') or crypto.randomUUID()"
|
|
152
|
+
|
|
153
|
+
- id: insecure-random-array
|
|
154
|
+
message: |
|
|
155
|
+
Using Math.random() for shuffling or selection in security-sensitive
|
|
156
|
+
context. Use crypto.getRandomValues() instead.
|
|
157
|
+
severity: WARNING
|
|
158
|
+
languages:
|
|
159
|
+
- javascript
|
|
160
|
+
- typescript
|
|
161
|
+
pattern: Math.floor(Math.random() * $ARR.length)
|
|
162
|
+
metadata:
|
|
163
|
+
category: security
|
|
164
|
+
cwe: 'CWE-330'
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# COMMAND INJECTION
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
# Note: These rules detect DANGEROUS patterns that should be flagged.
|
|
170
|
+
# The patterns below are what we want to WARN about, not examples to follow.
|
|
171
|
+
|
|
172
|
+
- id: command-injection-template
|
|
173
|
+
message: |
|
|
174
|
+
User input in shell command template string. Use child_process.execFile
|
|
175
|
+
with argument array or validate/escape input.
|
|
176
|
+
severity: ERROR
|
|
177
|
+
languages:
|
|
178
|
+
- javascript
|
|
179
|
+
- typescript
|
|
180
|
+
pattern-either:
|
|
181
|
+
- pattern: child_process.execSync(`...${$USER_INPUT}...`)
|
|
182
|
+
metadata:
|
|
183
|
+
category: security
|
|
184
|
+
cwe: 'CWE-78'
|
|
185
|
+
owasp: 'A03:2021'
|
|
186
|
+
fix: 'Use execFile() with argument array instead of template strings'
|
|
187
|
+
|
|
188
|
+
- id: command-injection-shell-option
|
|
189
|
+
message: |
|
|
190
|
+
spawn() with shell: true can lead to command injection. Use shell: false
|
|
191
|
+
and pass arguments as array.
|
|
192
|
+
severity: WARNING
|
|
193
|
+
languages:
|
|
194
|
+
- javascript
|
|
195
|
+
- typescript
|
|
196
|
+
pattern: 'spawn($CMD, $ARGS, { ..., shell: true, ... })'
|
|
197
|
+
metadata:
|
|
198
|
+
category: security
|
|
199
|
+
cwe: 'CWE-78'
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# PATH TRAVERSAL
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
- id: path-traversal-join
|
|
205
|
+
message: |
|
|
206
|
+
User input in path.join() without validation can lead to path traversal.
|
|
207
|
+
Validate that the result stays within intended directory.
|
|
208
|
+
severity: WARNING
|
|
209
|
+
languages:
|
|
210
|
+
- javascript
|
|
211
|
+
- typescript
|
|
212
|
+
pattern-either:
|
|
213
|
+
- pattern: path.join($BASE, $REQ.params.$PARAM)
|
|
214
|
+
- pattern: path.join($BASE, $REQ.query.$PARAM)
|
|
215
|
+
- pattern: path.join($BASE, $USER_INPUT)
|
|
216
|
+
metadata:
|
|
217
|
+
category: security
|
|
218
|
+
cwe: 'CWE-22'
|
|
219
|
+
|
|
220
|
+
- id: path-traversal-resolve
|
|
221
|
+
message: |
|
|
222
|
+
path.resolve() with user input may escape intended directory. Use
|
|
223
|
+
path.join() and verify result with startsWith().
|
|
224
|
+
severity: WARNING
|
|
225
|
+
languages:
|
|
226
|
+
- javascript
|
|
227
|
+
- typescript
|
|
228
|
+
pattern: path.resolve($USER_INPUT)
|
|
229
|
+
metadata:
|
|
230
|
+
category: security
|
|
231
|
+
cwe: 'CWE-22'
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# HARDCODED SECRETS
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
- id: hardcoded-jwt-secret
|
|
237
|
+
message: |
|
|
238
|
+
JWT secret appears to be hardcoded. Use environment variables for secrets.
|
|
239
|
+
severity: ERROR
|
|
240
|
+
languages:
|
|
241
|
+
- javascript
|
|
242
|
+
- typescript
|
|
243
|
+
pattern-either:
|
|
244
|
+
- pattern: jwt.sign($PAYLOAD, "...")
|
|
245
|
+
- pattern: jwt.verify($TOKEN, "...")
|
|
246
|
+
metadata:
|
|
247
|
+
category: security
|
|
248
|
+
cwe: 'CWE-798'
|
|
249
|
+
|
|
250
|
+
- id: hardcoded-api-key
|
|
251
|
+
message: |
|
|
252
|
+
API key appears to be hardcoded. Use environment variables for secrets.
|
|
253
|
+
severity: ERROR
|
|
254
|
+
languages:
|
|
255
|
+
- javascript
|
|
256
|
+
- typescript
|
|
257
|
+
pattern-either:
|
|
258
|
+
- pattern: |
|
|
259
|
+
{ ..., apiKey: "...", ... }
|
|
260
|
+
- pattern: |
|
|
261
|
+
headers: { ..., "x-api-key": "...", ... }
|
|
262
|
+
metadata:
|
|
263
|
+
category: security
|
|
264
|
+
cwe: 'CWE-798'
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# UNSAFE DESERIALIZATION
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
- id: unsafe-eval
|
|
270
|
+
message: |
|
|
271
|
+
eval() with dynamic input is dangerous. Use safer alternatives.
|
|
272
|
+
severity: ERROR
|
|
273
|
+
languages:
|
|
274
|
+
- javascript
|
|
275
|
+
- typescript
|
|
276
|
+
pattern-either:
|
|
277
|
+
- pattern: eval($USER_INPUT)
|
|
278
|
+
- pattern: new Function($USER_INPUT)
|
|
279
|
+
metadata:
|
|
280
|
+
category: security
|
|
281
|
+
cwe: 'CWE-95'
|
|
282
|
+
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
# MISSING SECURITY HEADERS
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
- id: cors-allow-all
|
|
287
|
+
message: |
|
|
288
|
+
CORS configured to allow all origins. This may expose APIs to
|
|
289
|
+
unauthorized cross-origin requests.
|
|
290
|
+
severity: WARNING
|
|
291
|
+
languages:
|
|
292
|
+
- javascript
|
|
293
|
+
- typescript
|
|
294
|
+
pattern-either:
|
|
295
|
+
- pattern: |
|
|
296
|
+
cors({ origin: "*" })
|
|
297
|
+
- pattern: |
|
|
298
|
+
cors({ origin: true })
|
|
299
|
+
- pattern: |
|
|
300
|
+
res.setHeader("Access-Control-Allow-Origin", "*")
|
|
301
|
+
metadata:
|
|
302
|
+
category: security
|
|
303
|
+
cwe: 'CWE-942'
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
# DENIAL OF SERVICE
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
- id: unbounded-array-growth
|
|
309
|
+
message: |
|
|
310
|
+
Array grows unboundedly in loop. Add size limits to prevent memory
|
|
311
|
+
exhaustion.
|
|
312
|
+
severity: WARNING
|
|
313
|
+
languages:
|
|
314
|
+
- javascript
|
|
315
|
+
- typescript
|
|
316
|
+
pattern: |
|
|
317
|
+
while (...) {
|
|
318
|
+
...
|
|
319
|
+
$ARR.push(...)
|
|
320
|
+
...
|
|
321
|
+
}
|
|
322
|
+
metadata:
|
|
323
|
+
category: security
|
|
324
|
+
cwe: 'CWE-400'
|
|
325
|
+
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
# REACT SECURITY
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
- id: react-dangerous-html
|
|
330
|
+
message: |
|
|
331
|
+
dangerouslySetInnerHTML with user input can lead to XSS. Sanitize HTML
|
|
332
|
+
with DOMPurify or similar library.
|
|
333
|
+
severity: ERROR
|
|
334
|
+
languages:
|
|
335
|
+
- javascript
|
|
336
|
+
- typescript
|
|
337
|
+
pattern-regex: "dangerouslySetInnerHTML\\s*=\\s*\\{\\{\\s*__html"
|
|
338
|
+
metadata:
|
|
339
|
+
category: security
|
|
340
|
+
cwe: 'CWE-79'
|
|
341
|
+
owasp: 'A07:2021'
|
|
342
|
+
|
|
343
|
+
- id: react-href-javascript
|
|
344
|
+
message: |
|
|
345
|
+
javascript: URLs in href can lead to XSS. Validate URLs or use
|
|
346
|
+
onClick handlers instead.
|
|
347
|
+
severity: ERROR
|
|
348
|
+
languages:
|
|
349
|
+
- javascript
|
|
350
|
+
- typescript
|
|
351
|
+
pattern-either:
|
|
352
|
+
- pattern: <a href={`javascript:${$CODE}`} />
|
|
353
|
+
- pattern: <a href="javascript:..." />
|
|
354
|
+
metadata:
|
|
355
|
+
category: security
|
|
356
|
+
cwe: 'CWE-79'
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Vibe-Code Audit Rules
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Security patterns specifically common in AI-generated / vibe-coded apps.
|
|
6
|
+
# Complements defensive-patterns.yaml (which covers prototype pollution,
|
|
7
|
+
# SQL injection, command injection, path traversal, hardcoded secrets).
|
|
8
|
+
#
|
|
9
|
+
# Categories covered:
|
|
10
|
+
# 1. Auth & authorization gaps
|
|
11
|
+
# 2. Production misconfigs (debug mode, verbose errors, CORS)
|
|
12
|
+
# 3. Input validation gaps
|
|
13
|
+
# 4. XSS patterns
|
|
14
|
+
# 5. Insecure direct object reference (IDOR)
|
|
15
|
+
# 6. Sensitive data exposure
|
|
16
|
+
# =============================================================================
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# AUTH & AUTHORIZATION
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
- id: client-side-auth-check
|
|
23
|
+
message: |
|
|
24
|
+
Authorization check found in client-side code. Checks that run only in
|
|
25
|
+
the browser can be bypassed. Move auth enforcement to the server/API layer.
|
|
26
|
+
severity: ERROR
|
|
27
|
+
languages: [javascript, typescript]
|
|
28
|
+
pattern-either:
|
|
29
|
+
- pattern: |
|
|
30
|
+
if ($USER.role === $ROLE) {
|
|
31
|
+
...
|
|
32
|
+
}
|
|
33
|
+
- pattern: |
|
|
34
|
+
if ($USER.isAdmin) {
|
|
35
|
+
...
|
|
36
|
+
}
|
|
37
|
+
paths:
|
|
38
|
+
include:
|
|
39
|
+
- '*.tsx'
|
|
40
|
+
- '*.jsx'
|
|
41
|
+
- 'components/**'
|
|
42
|
+
- 'pages/**'
|
|
43
|
+
- 'app/**'
|
|
44
|
+
metadata:
|
|
45
|
+
category: security
|
|
46
|
+
cwe: 'CWE-602'
|
|
47
|
+
owasp: 'A01:2021'
|
|
48
|
+
|
|
49
|
+
- id: missing-auth-api-route
|
|
50
|
+
message: |
|
|
51
|
+
API route handler with no visible auth check. Vibe-coded routes often
|
|
52
|
+
skip auth middleware. Verify this route requires authentication.
|
|
53
|
+
severity: WARNING
|
|
54
|
+
languages: [javascript, typescript]
|
|
55
|
+
pattern-either:
|
|
56
|
+
- pattern: |
|
|
57
|
+
export default async function handler($REQ, $RES) {
|
|
58
|
+
...
|
|
59
|
+
$RES.json(...)
|
|
60
|
+
}
|
|
61
|
+
- pattern: |
|
|
62
|
+
export async function GET($REQ) {
|
|
63
|
+
...
|
|
64
|
+
return Response.json(...)
|
|
65
|
+
}
|
|
66
|
+
- pattern: |
|
|
67
|
+
router.get($PATH, async ($REQ, $RES) => {
|
|
68
|
+
...
|
|
69
|
+
$RES.json(...)
|
|
70
|
+
})
|
|
71
|
+
paths:
|
|
72
|
+
include:
|
|
73
|
+
- 'pages/api/**'
|
|
74
|
+
- 'app/api/**'
|
|
75
|
+
- 'routes/**'
|
|
76
|
+
- 'src/pages/api/**'
|
|
77
|
+
- 'src/app/api/**'
|
|
78
|
+
metadata:
|
|
79
|
+
category: security
|
|
80
|
+
cwe: 'CWE-306'
|
|
81
|
+
owasp: 'A01:2021'
|
|
82
|
+
note: 'Confirm this route has auth middleware or session validation'
|
|
83
|
+
|
|
84
|
+
- id: hardcoded-admin-identity
|
|
85
|
+
message: |
|
|
86
|
+
Admin check against a hardcoded email or username string. This is fragile
|
|
87
|
+
and likely to break. Use role-based access control or a proper admin flag.
|
|
88
|
+
severity: ERROR
|
|
89
|
+
languages: [javascript, typescript]
|
|
90
|
+
pattern-either:
|
|
91
|
+
- pattern: $EMAIL === "..."
|
|
92
|
+
- pattern: $USERNAME === "..."
|
|
93
|
+
paths:
|
|
94
|
+
include:
|
|
95
|
+
- 'pages/api/**'
|
|
96
|
+
- 'app/api/**'
|
|
97
|
+
- 'lib/**'
|
|
98
|
+
- 'server/**'
|
|
99
|
+
metadata:
|
|
100
|
+
category: security
|
|
101
|
+
cwe: 'CWE-798'
|
|
102
|
+
|
|
103
|
+
- id: jwt-no-expiry
|
|
104
|
+
message: |
|
|
105
|
+
JWT signed without an expiry option. Tokens that never expire are a
|
|
106
|
+
security risk — a stolen token grants permanent access. Set expiresIn.
|
|
107
|
+
severity: ERROR
|
|
108
|
+
languages: [javascript, typescript]
|
|
109
|
+
pattern-either:
|
|
110
|
+
- pattern: jwt.sign($PAYLOAD, $SECRET)
|
|
111
|
+
- pattern: sign($PAYLOAD, $SECRET)
|
|
112
|
+
metadata:
|
|
113
|
+
category: security
|
|
114
|
+
cwe: 'CWE-613'
|
|
115
|
+
fix: "jwt.sign(payload, secret, { expiresIn: '24h' })"
|
|
116
|
+
|
|
117
|
+
- id: cookie-no-httponly
|
|
118
|
+
message: |
|
|
119
|
+
Cookie set without httpOnly flag. Cookies without httpOnly are readable
|
|
120
|
+
by JavaScript and vulnerable to theft via script injection.
|
|
121
|
+
Add httpOnly: true to the cookie options.
|
|
122
|
+
severity: WARNING
|
|
123
|
+
languages: [javascript, typescript]
|
|
124
|
+
pattern: |
|
|
125
|
+
res.cookie($NAME, $VALUE, { ... })
|
|
126
|
+
metadata:
|
|
127
|
+
category: security
|
|
128
|
+
cwe: 'CWE-1004'
|
|
129
|
+
fix: "res.cookie(name, value, { httpOnly: true, secure: true, sameSite: 'strict' })"
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# PRODUCTION MISCONFIGS
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
- id: debug-flag-hardcoded
|
|
136
|
+
message: |
|
|
137
|
+
Debug or development flag hardcoded to true. If this reaches production
|
|
138
|
+
it may expose stack traces, verbose logs, or disable security checks.
|
|
139
|
+
severity: WARNING
|
|
140
|
+
languages: [javascript, typescript]
|
|
141
|
+
pattern-either:
|
|
142
|
+
- pattern: 'debug: true'
|
|
143
|
+
- pattern: 'DEBUG: true'
|
|
144
|
+
metadata:
|
|
145
|
+
category: security
|
|
146
|
+
cwe: 'CWE-489'
|
|
147
|
+
|
|
148
|
+
- id: verbose-error-to-client
|
|
149
|
+
message: |
|
|
150
|
+
Full error message or stack trace sent back to the client. This exposes
|
|
151
|
+
internal implementation details. Return a generic message to the client
|
|
152
|
+
and log the full error server-side.
|
|
153
|
+
severity: WARNING
|
|
154
|
+
languages: [javascript, typescript]
|
|
155
|
+
pattern-either:
|
|
156
|
+
- pattern: 'res.status(500).json({ error: $ERR.message })'
|
|
157
|
+
- pattern: 'res.status(500).json({ error: $ERR })'
|
|
158
|
+
- pattern: 'res.status(500).json({ stack: $ERR.stack })'
|
|
159
|
+
- pattern: 'return Response.json({ error: $ERR.message }, { status: 500 })'
|
|
160
|
+
metadata:
|
|
161
|
+
category: security
|
|
162
|
+
cwe: 'CWE-209'
|
|
163
|
+
owasp: 'A05:2021'
|
|
164
|
+
fix: 'return res.status(500).json({ error: "Internal server error" })'
|
|
165
|
+
|
|
166
|
+
- id: console-log-credential
|
|
167
|
+
message: |
|
|
168
|
+
Logging a field that may be a credential (password, token, secret, key).
|
|
169
|
+
Console logs in production can appear in log aggregation services and
|
|
170
|
+
expose secrets. Remove this log or redact the sensitive field.
|
|
171
|
+
severity: WARNING
|
|
172
|
+
languages: [javascript, typescript]
|
|
173
|
+
pattern-either:
|
|
174
|
+
- pattern: console.log(..., $X.password, ...)
|
|
175
|
+
- pattern: console.log(..., $X.token, ...)
|
|
176
|
+
- pattern: console.log(..., $X.secret, ...)
|
|
177
|
+
- pattern: console.log(..., $X.apiKey, ...)
|
|
178
|
+
- pattern: console.log(..., $X.privateKey, ...)
|
|
179
|
+
metadata:
|
|
180
|
+
category: security
|
|
181
|
+
cwe: 'CWE-532'
|
|
182
|
+
|
|
183
|
+
- id: express-no-helmet
|
|
184
|
+
message: |
|
|
185
|
+
Express app created without helmet middleware. Helmet sets secure HTTP
|
|
186
|
+
response headers (X-Frame-Options, HSTS, CSP, etc.) automatically.
|
|
187
|
+
severity: WARNING
|
|
188
|
+
languages: [javascript, typescript]
|
|
189
|
+
pattern: |
|
|
190
|
+
const $APP = express()
|
|
191
|
+
...
|
|
192
|
+
$APP.use(...)
|
|
193
|
+
metadata:
|
|
194
|
+
category: security
|
|
195
|
+
cwe: 'CWE-693'
|
|
196
|
+
fix: "const helmet = require('helmet'); app.use(helmet())"
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# XSS PATTERNS
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
- id: dynamic-html-assignment
|
|
203
|
+
message: |
|
|
204
|
+
Assigning dynamic content to an HTML property that interprets markup.
|
|
205
|
+
If the value contains user input this is a script injection vector.
|
|
206
|
+
Use textContent for plain text, or sanitize HTML before assignment.
|
|
207
|
+
severity: ERROR
|
|
208
|
+
languages: [javascript, typescript]
|
|
209
|
+
pattern-either:
|
|
210
|
+
- pattern: $EL.innerHTML = $DATA
|
|
211
|
+
- pattern: document.getElementById($ID).innerHTML = $DATA
|
|
212
|
+
metadata:
|
|
213
|
+
category: security
|
|
214
|
+
cwe: 'CWE-79'
|
|
215
|
+
owasp: 'A03:2021'
|
|
216
|
+
fix: 'Use textContent for plain text or DOMPurify.sanitize() for HTML'
|
|
217
|
+
|
|
218
|
+
- id: dynamic-href-user-input
|
|
219
|
+
message: |
|
|
220
|
+
Anchor href set from a dynamic expression. If the value comes from user
|
|
221
|
+
input this can lead to open redirect or script injection via javascript:
|
|
222
|
+
URLs. Validate that the URL starts with https:// before use.
|
|
223
|
+
severity: WARNING
|
|
224
|
+
languages: [javascript, typescript]
|
|
225
|
+
pattern: <a href={$DYNAMIC}>...</a>
|
|
226
|
+
metadata:
|
|
227
|
+
category: security
|
|
228
|
+
cwe: 'CWE-79'
|
|
229
|
+
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
# INPUT VALIDATION GAPS
|
|
232
|
+
# ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
- id: unvalidated-redirect
|
|
235
|
+
message: |
|
|
236
|
+
Redirect target taken directly from user input or query params.
|
|
237
|
+
Open redirects enable phishing. Validate the target against an allowlist
|
|
238
|
+
of permitted domains before redirecting.
|
|
239
|
+
severity: WARNING
|
|
240
|
+
languages: [javascript, typescript]
|
|
241
|
+
pattern-either:
|
|
242
|
+
- pattern: res.redirect($REQ.query.$PARAM)
|
|
243
|
+
- pattern: res.redirect($REQ.body.$FIELD)
|
|
244
|
+
metadata:
|
|
245
|
+
category: security
|
|
246
|
+
cwe: 'CWE-601'
|
|
247
|
+
owasp: 'A01:2021'
|
|
248
|
+
|
|
249
|
+
- id: file-upload-unchecked
|
|
250
|
+
message: |
|
|
251
|
+
File upload handling found with no MIME type or extension check visible.
|
|
252
|
+
Accepting arbitrary files can lead to code execution or storage abuse.
|
|
253
|
+
Validate file type using an allowlist before processing.
|
|
254
|
+
severity: WARNING
|
|
255
|
+
languages: [javascript, typescript]
|
|
256
|
+
pattern: formData.get("file")
|
|
257
|
+
metadata:
|
|
258
|
+
category: security
|
|
259
|
+
cwe: 'CWE-434'
|
|
260
|
+
note: 'Verify file type is validated with a MIME type + extension allowlist'
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# INSECURE DIRECT OBJECT REFERENCE (IDOR)
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
- id: idor-prisma-no-owner-filter
|
|
267
|
+
message: |
|
|
268
|
+
Prisma query by ID from request params with no user ownership filter.
|
|
269
|
+
Without filtering by the authenticated user's ID, any authenticated user
|
|
270
|
+
can read or modify other users' records. Add userId: session.user.id to
|
|
271
|
+
the where clause.
|
|
272
|
+
severity: ERROR
|
|
273
|
+
languages: [javascript, typescript]
|
|
274
|
+
pattern-either:
|
|
275
|
+
- pattern: |
|
|
276
|
+
$DB.findUnique({ where: { id: $REQ.params.id } })
|
|
277
|
+
- pattern: |
|
|
278
|
+
$DB.findUnique({ where: { id: $REQ.query.id } })
|
|
279
|
+
metadata:
|
|
280
|
+
category: security
|
|
281
|
+
cwe: 'CWE-639'
|
|
282
|
+
owasp: 'A01:2021'
|
|
283
|
+
fix: 'findUnique({ where: { id: params.id, userId: session.user.id } })'
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# SENSITIVE DATA EXPOSURE
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
- id: env-var-in-client-component
|
|
290
|
+
message: |
|
|
291
|
+
process.env access in a file that appears to be a client component.
|
|
292
|
+
Server-only env vars are not available in the browser. Expose only
|
|
293
|
+
NEXT_PUBLIC_ prefixed vars client-side, and never expose secrets.
|
|
294
|
+
severity: WARNING
|
|
295
|
+
languages: [javascript, typescript]
|
|
296
|
+
pattern: process.env.$VAR
|
|
297
|
+
paths:
|
|
298
|
+
include:
|
|
299
|
+
- 'components/**'
|
|
300
|
+
- 'app/**/*.tsx'
|
|
301
|
+
- 'app/**/*.jsx'
|
|
302
|
+
- 'pages/**/*.tsx'
|
|
303
|
+
- 'pages/**/*.jsx'
|
|
304
|
+
metadata:
|
|
305
|
+
category: security
|
|
306
|
+
cwe: 'CWE-526'
|
|
307
|
+
|
|
308
|
+
- id: dynamic-require-variable
|
|
309
|
+
message: |
|
|
310
|
+
Dynamic require() with a variable path. If the path is influenced by
|
|
311
|
+
user input this can be exploited to load unexpected modules.
|
|
312
|
+
severity: ERROR
|
|
313
|
+
languages: [javascript, typescript]
|
|
314
|
+
pattern: require($VAR)
|
|
315
|
+
metadata:
|
|
316
|
+
category: security
|
|
317
|
+
cwe: 'CWE-706'
|
|
318
|
+
|
|
319
|
+
- id: bracket-notation-user-key
|
|
320
|
+
message: |
|
|
321
|
+
Object property accessed with user-controlled bracket notation.
|
|
322
|
+
If the key is "__proto__" or "constructor" this corrupts object prototypes.
|
|
323
|
+
Validate the key against an allowlist before use.
|
|
324
|
+
severity: WARNING
|
|
325
|
+
languages: [javascript, typescript]
|
|
326
|
+
pattern-either:
|
|
327
|
+
- pattern: $OBJ[$REQ.body.$FIELD]
|
|
328
|
+
- pattern: $OBJ[$REQ.query.$PARAM]
|
|
329
|
+
- pattern: $OBJ[$REQ.params.$PARAM]
|
|
330
|
+
metadata:
|
|
331
|
+
category: security
|
|
332
|
+
cwe: 'CWE-1321'
|