@datahogo/core 0.1.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/LICENSE +661 -0
- package/README.md +17 -0
- package/dist/engines/config.d.ts +3 -0
- package/dist/engines/config.d.ts.map +1 -0
- package/dist/engines/config.js +433 -0
- package/dist/engines/config.js.map +1 -0
- package/dist/engines/dast.d.ts +3 -0
- package/dist/engines/dast.d.ts.map +1 -0
- package/dist/engines/dast.js +167 -0
- package/dist/engines/dast.js.map +1 -0
- package/dist/engines/db-rules.d.ts +3 -0
- package/dist/engines/db-rules.d.ts.map +1 -0
- package/dist/engines/db-rules.js +166 -0
- package/dist/engines/db-rules.js.map +1 -0
- package/dist/engines/dependencies.d.ts +3 -0
- package/dist/engines/dependencies.d.ts.map +1 -0
- package/dist/engines/dependencies.js +167 -0
- package/dist/engines/dependencies.js.map +1 -0
- package/dist/engines/github-actions.d.ts +3 -0
- package/dist/engines/github-actions.d.ts.map +1 -0
- package/dist/engines/github-actions.js +215 -0
- package/dist/engines/github-actions.js.map +1 -0
- package/dist/engines/gitleaks.d.ts +3 -0
- package/dist/engines/gitleaks.d.ts.map +1 -0
- package/dist/engines/gitleaks.js +107 -0
- package/dist/engines/gitleaks.js.map +1 -0
- package/dist/engines/npm-audit.d.ts +3 -0
- package/dist/engines/npm-audit.d.ts.map +1 -0
- package/dist/engines/npm-audit.js +113 -0
- package/dist/engines/npm-audit.js.map +1 -0
- package/dist/engines/patterns.d.ts +3 -0
- package/dist/engines/patterns.d.ts.map +1 -0
- package/dist/engines/patterns.js +1330 -0
- package/dist/engines/patterns.js.map +1 -0
- package/dist/engines/secrets.d.ts +4 -0
- package/dist/engines/secrets.d.ts.map +1 -0
- package/dist/engines/secrets.js +260 -0
- package/dist/engines/secrets.js.map +1 -0
- package/dist/engines/semgrep.d.ts +3 -0
- package/dist/engines/semgrep.d.ts.map +1 -0
- package/dist/engines/semgrep.js +173 -0
- package/dist/engines/semgrep.js.map +1 -0
- package/dist/engines/types.d.ts +30 -0
- package/dist/engines/types.d.ts.map +1 -0
- package/dist/engines/types.js +3 -0
- package/dist/engines/types.js.map +1 -0
- package/dist/engines/url-scanner.d.ts +3 -0
- package/dist/engines/url-scanner.d.ts.map +1 -0
- package/dist/engines/url-scanner.js +469 -0
- package/dist/engines/url-scanner.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +32 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +237 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/rules/.gitkeep +0 -0
- package/dist/rules/ai-llm-security.yaml +199 -0
- package/dist/rules/api-security.yaml +489 -0
- package/dist/rules/auth-patterns.yaml +406 -0
- package/dist/rules/crypto-patterns.yaml +227 -0
- package/dist/rules/database-cloud-security.yaml +169 -0
- package/dist/rules/general-security.yaml +588 -0
- package/dist/rules/injection-patterns.yaml +318 -0
- package/dist/rules/nextjs-security.yaml +532 -0
- package/dist/rules/node-security.yaml +380 -0
- package/dist/rules/rules/.gitkeep +0 -0
- package/dist/rules/rules/ai-llm-security.yaml +199 -0
- package/dist/rules/rules/api-security.yaml +489 -0
- package/dist/rules/rules/auth-patterns.yaml +406 -0
- package/dist/rules/rules/crypto-patterns.yaml +227 -0
- package/dist/rules/rules/database-cloud-security.yaml +169 -0
- package/dist/rules/rules/general-security.yaml +588 -0
- package/dist/rules/rules/injection-patterns.yaml +318 -0
- package/dist/rules/rules/nextjs-security.yaml +532 -0
- package/dist/rules/rules/node-security.yaml +380 -0
- package/dist/rules/rules/supabase-security.yaml +387 -0
- package/dist/rules/supabase-security.yaml +387 -0
- package/dist/scanning/agents/csharp-agent.d.ts +12 -0
- package/dist/scanning/agents/csharp-agent.d.ts.map +1 -0
- package/dist/scanning/agents/csharp-agent.js +592 -0
- package/dist/scanning/agents/csharp-agent.js.map +1 -0
- package/dist/scanning/agents/go-agent.d.ts +14 -0
- package/dist/scanning/agents/go-agent.d.ts.map +1 -0
- package/dist/scanning/agents/go-agent.js +641 -0
- package/dist/scanning/agents/go-agent.js.map +1 -0
- package/dist/scanning/agents/java-agent.d.ts +8 -0
- package/dist/scanning/agents/java-agent.d.ts.map +1 -0
- package/dist/scanning/agents/java-agent.js +807 -0
- package/dist/scanning/agents/java-agent.js.map +1 -0
- package/dist/scanning/agents/javascript-agent.d.ts +8 -0
- package/dist/scanning/agents/javascript-agent.d.ts.map +1 -0
- package/dist/scanning/agents/javascript-agent.js +77 -0
- package/dist/scanning/agents/javascript-agent.js.map +1 -0
- package/dist/scanning/agents/mobile-agent.d.ts +8 -0
- package/dist/scanning/agents/mobile-agent.d.ts.map +1 -0
- package/dist/scanning/agents/mobile-agent.js +916 -0
- package/dist/scanning/agents/mobile-agent.js.map +1 -0
- package/dist/scanning/agents/php-agent.d.ts +8 -0
- package/dist/scanning/agents/php-agent.d.ts.map +1 -0
- package/dist/scanning/agents/php-agent.js +679 -0
- package/dist/scanning/agents/php-agent.js.map +1 -0
- package/dist/scanning/agents/python-agent.d.ts +8 -0
- package/dist/scanning/agents/python-agent.d.ts.map +1 -0
- package/dist/scanning/agents/python-agent.js +701 -0
- package/dist/scanning/agents/python-agent.js.map +1 -0
- package/dist/scanning/agents/supabase-agent.d.ts +8 -0
- package/dist/scanning/agents/supabase-agent.d.ts.map +1 -0
- package/dist/scanning/agents/supabase-agent.js +484 -0
- package/dist/scanning/agents/supabase-agent.js.map +1 -0
- package/dist/scanning/orchestrator.d.ts +29 -0
- package/dist/scanning/orchestrator.d.ts.map +1 -0
- package/dist/scanning/orchestrator.js +186 -0
- package/dist/scanning/orchestrator.js.map +1 -0
- package/dist/scanning/tech-detector.d.ts +12 -0
- package/dist/scanning/tech-detector.d.ts.map +1 -0
- package/dist/scanning/tech-detector.js +252 -0
- package/dist/scanning/tech-detector.js.map +1 -0
- package/dist/scanning/types.d.ts +87 -0
- package/dist/scanning/types.d.ts.map +1 -0
- package/dist/scanning/types.js +5 -0
- package/dist/scanning/types.js.map +1 -0
- package/dist/utils/child-process.d.ts +2 -0
- package/dist/utils/child-process.d.ts.map +1 -0
- package/dist/utils/child-process.js +4 -0
- package/dist/utils/child-process.js.map +1 -0
- package/dist/utils/context-classifier.d.ts +11 -0
- package/dist/utils/context-classifier.d.ts.map +1 -0
- package/dist/utils/context-classifier.js +79 -0
- package/dist/utils/context-classifier.js.map +1 -0
- package/dist/utils/exec.d.ts +18 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +51 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/file-filter.d.ts +4 -0
- package/dist/utils/file-filter.d.ts.map +1 -0
- package/dist/utils/file-filter.js +68 -0
- package/dist/utils/file-filter.js.map +1 -0
- package/dist/utils/fs.d.ts +2 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +5 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/post-processor.d.ts +10 -0
- package/dist/utils/post-processor.d.ts.map +1 -0
- package/dist/utils/post-processor.js +183 -0
- package/dist/utils/post-processor.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
# ID 21: BOLA - Broken Object Level Authorization
|
|
3
|
+
- id: datahogo-api-bola-no-ownership-check
|
|
4
|
+
patterns:
|
|
5
|
+
- pattern-either:
|
|
6
|
+
- pattern: |
|
|
7
|
+
export async function $METHOD(req: Request, { params }: { params: { $ID: string } }) {
|
|
8
|
+
...
|
|
9
|
+
$DB.$QUERY({ where: { id: params.$ID } })
|
|
10
|
+
...
|
|
11
|
+
}
|
|
12
|
+
- pattern: |
|
|
13
|
+
export async function $METHOD(req: Request) {
|
|
14
|
+
...
|
|
15
|
+
const $ID = $GETID
|
|
16
|
+
...
|
|
17
|
+
$DB.$QUERY({ where: { id: $ID } })
|
|
18
|
+
...
|
|
19
|
+
}
|
|
20
|
+
- pattern-not: |
|
|
21
|
+
export async function $METHOD(req: Request, ...) {
|
|
22
|
+
...
|
|
23
|
+
$CLIENT.auth.getUser()
|
|
24
|
+
...
|
|
25
|
+
$DB.$QUERY({ where: { id: $ID, userId: $USERID } })
|
|
26
|
+
...
|
|
27
|
+
}
|
|
28
|
+
- pattern-not-inside: |
|
|
29
|
+
if (!$USER || $ITEM.userId !== $USER.id) {
|
|
30
|
+
...
|
|
31
|
+
}
|
|
32
|
+
message: "API endpoint accesses resource by ID without verifying user ownership. This can lead to unauthorized access (BOLA/IDOR)."
|
|
33
|
+
severity: ERROR
|
|
34
|
+
languages: [typescript, javascript]
|
|
35
|
+
metadata:
|
|
36
|
+
datahogo_id: 21
|
|
37
|
+
datahogo_category: "api"
|
|
38
|
+
owasp_ref: "API1:2023"
|
|
39
|
+
|
|
40
|
+
# ID 22: Broken Authentication - JWT without validation
|
|
41
|
+
- id: datahogo-api-jwt-decode-only
|
|
42
|
+
pattern: |
|
|
43
|
+
jwt.decode($TOKEN)
|
|
44
|
+
message: "jwt.decode() does not verify the token signature. Use jwt.verify() for secure authentication."
|
|
45
|
+
severity: ERROR
|
|
46
|
+
languages: [typescript, javascript]
|
|
47
|
+
metadata:
|
|
48
|
+
datahogo_id: 22
|
|
49
|
+
datahogo_category: "api"
|
|
50
|
+
owasp_ref: "API2:2023"
|
|
51
|
+
|
|
52
|
+
- id: datahogo-api-jwt-no-expiry
|
|
53
|
+
patterns:
|
|
54
|
+
- pattern: |
|
|
55
|
+
jwt.sign($PAYLOAD, $SECRET)
|
|
56
|
+
- pattern-not: |
|
|
57
|
+
jwt.sign($PAYLOAD, $SECRET, { expiresIn: $EXPIRY, ... })
|
|
58
|
+
- pattern-not: |
|
|
59
|
+
jwt.sign({ ..., exp: $EXP }, $SECRET, ...)
|
|
60
|
+
message: "JWT token created without expiration time. Tokens should expire to limit exposure from theft."
|
|
61
|
+
severity: ERROR
|
|
62
|
+
languages: [typescript, javascript]
|
|
63
|
+
metadata:
|
|
64
|
+
datahogo_id: 22
|
|
65
|
+
datahogo_category: "api"
|
|
66
|
+
owasp_ref: "API2:2023"
|
|
67
|
+
|
|
68
|
+
- id: datahogo-api-bearer-token-in-url
|
|
69
|
+
patterns:
|
|
70
|
+
- pattern-either:
|
|
71
|
+
- pattern: "$URL + '?token=' + $TOKEN"
|
|
72
|
+
- pattern: "`${$URL}?token=${$TOKEN}`"
|
|
73
|
+
- pattern: "$URL + '&token=' + $TOKEN"
|
|
74
|
+
- pattern: "`${$URL}&token=${$TOKEN}`"
|
|
75
|
+
message: "Bearer token passed in URL query parameter. Tokens should be sent in Authorization header to prevent logging/leaking."
|
|
76
|
+
severity: ERROR
|
|
77
|
+
languages: [typescript, javascript]
|
|
78
|
+
metadata:
|
|
79
|
+
datahogo_id: 22
|
|
80
|
+
datahogo_category: "api"
|
|
81
|
+
owasp_ref: "API2:2023"
|
|
82
|
+
|
|
83
|
+
# ID 23: Broken Object Property Authorization - Mass Assignment
|
|
84
|
+
- id: datahogo-api-mass-assignment-spread
|
|
85
|
+
patterns:
|
|
86
|
+
- pattern-either:
|
|
87
|
+
- pattern: |
|
|
88
|
+
$DB.$TABLE.update({ where: { id: $ID }, data: { ...$REQ.body } })
|
|
89
|
+
- pattern: |
|
|
90
|
+
$DB.$TABLE.create({ data: { ...$REQ.body } })
|
|
91
|
+
- pattern: |
|
|
92
|
+
$DB.$TABLE.update({ where: { id: $ID }, data: $REQ.body })
|
|
93
|
+
- pattern: |
|
|
94
|
+
$DB.$TABLE.create({ data: $REQ.body })
|
|
95
|
+
- pattern-not-inside: |
|
|
96
|
+
const { $ALLOWED, ... } = $REQ.body
|
|
97
|
+
...
|
|
98
|
+
message: "Mass assignment vulnerability: request body spread directly into database update. Whitelist allowed fields to prevent unauthorized property changes."
|
|
99
|
+
severity: WARNING
|
|
100
|
+
languages: [typescript, javascript]
|
|
101
|
+
metadata:
|
|
102
|
+
datahogo_id: 23
|
|
103
|
+
datahogo_category: "api"
|
|
104
|
+
owasp_ref: "API3:2023"
|
|
105
|
+
|
|
106
|
+
- id: datahogo-api-mass-assignment-role
|
|
107
|
+
patterns:
|
|
108
|
+
- pattern-either:
|
|
109
|
+
- pattern: |
|
|
110
|
+
$DB.user.update({ data: { ...$DATA, role: $ROLE } })
|
|
111
|
+
- pattern: |
|
|
112
|
+
$DB.user.update({ data: { role: $REQ.body.role } })
|
|
113
|
+
message: "User role being set from request data. This allows privilege escalation. Only admins should update roles."
|
|
114
|
+
severity: ERROR
|
|
115
|
+
languages: [typescript, javascript]
|
|
116
|
+
metadata:
|
|
117
|
+
datahogo_id: 23
|
|
118
|
+
datahogo_category: "api"
|
|
119
|
+
owasp_ref: "API3:2023"
|
|
120
|
+
|
|
121
|
+
# ID 24: Unrestricted Resource Consumption - No rate limiting
|
|
122
|
+
- id: datahogo-api-no-rate-limit-express
|
|
123
|
+
patterns:
|
|
124
|
+
- pattern: |
|
|
125
|
+
$APP.$METHOD($PATH, async ($REQ, $RES) => { ... })
|
|
126
|
+
- pattern-not-inside: |
|
|
127
|
+
$APP.use(..., rateLimit(...), ...)
|
|
128
|
+
- pattern-not-inside: |
|
|
129
|
+
$APP.$METHOD($PATH, rateLimit(...), async ($REQ, $RES) => { ... })
|
|
130
|
+
message: "API route has no rate limiting middleware. Add rate limiting to prevent abuse and resource exhaustion."
|
|
131
|
+
severity: WARNING
|
|
132
|
+
languages: [typescript, javascript]
|
|
133
|
+
metadata:
|
|
134
|
+
datahogo_id: 24
|
|
135
|
+
datahogo_category: "api"
|
|
136
|
+
owasp_ref: "API4:2023"
|
|
137
|
+
|
|
138
|
+
- id: datahogo-api-no-rate-limit-nextjs
|
|
139
|
+
patterns:
|
|
140
|
+
- pattern: |
|
|
141
|
+
export async function POST(req: Request) {
|
|
142
|
+
...
|
|
143
|
+
}
|
|
144
|
+
- pattern-not-inside: |
|
|
145
|
+
const $LIMITER = new Ratelimit(...)
|
|
146
|
+
...
|
|
147
|
+
await $LIMITER.limit(...)
|
|
148
|
+
- metavariable-regex:
|
|
149
|
+
metavariable: $CONTENT
|
|
150
|
+
regex: "(login|register|reset|forgot|verify|otp|mfa|2fa)"
|
|
151
|
+
message: "Authentication endpoint missing rate limiting. Add rate limiting to prevent brute force attacks."
|
|
152
|
+
severity: ERROR
|
|
153
|
+
languages: [typescript, javascript]
|
|
154
|
+
metadata:
|
|
155
|
+
datahogo_id: 24
|
|
156
|
+
datahogo_category: "api"
|
|
157
|
+
owasp_ref: "API4:2023"
|
|
158
|
+
|
|
159
|
+
# ID 25: Broken Function Level Authorization - Admin endpoints without role check
|
|
160
|
+
- id: datahogo-api-admin-no-role-check
|
|
161
|
+
patterns:
|
|
162
|
+
- pattern-either:
|
|
163
|
+
- pattern: |
|
|
164
|
+
export async function $METHOD(req: Request) {
|
|
165
|
+
...
|
|
166
|
+
}
|
|
167
|
+
- pattern: |
|
|
168
|
+
$APP.$METHOD('/admin/...', async ($REQ, $RES) => {
|
|
169
|
+
...
|
|
170
|
+
})
|
|
171
|
+
- metavariable-regex:
|
|
172
|
+
metavariable: $METHOD
|
|
173
|
+
regex: "(DELETE|POST|PUT|PATCH)"
|
|
174
|
+
- pattern-not-inside: |
|
|
175
|
+
if ($USER.role !== 'admin') {
|
|
176
|
+
...
|
|
177
|
+
}
|
|
178
|
+
- pattern-not-inside: |
|
|
179
|
+
if (!$USER.isAdmin) {
|
|
180
|
+
...
|
|
181
|
+
}
|
|
182
|
+
message: "Admin or privileged endpoint missing role-based access control. Verify user role before allowing access."
|
|
183
|
+
severity: ERROR
|
|
184
|
+
languages: [typescript, javascript]
|
|
185
|
+
metadata:
|
|
186
|
+
datahogo_id: 25
|
|
187
|
+
datahogo_category: "api"
|
|
188
|
+
owasp_ref: "API5:2023"
|
|
189
|
+
|
|
190
|
+
- id: datahogo-api-delete-no-auth
|
|
191
|
+
patterns:
|
|
192
|
+
- pattern: |
|
|
193
|
+
export async function DELETE(req: Request, { params }: { params: { $ID: string } }) {
|
|
194
|
+
...
|
|
195
|
+
$DB.$TABLE.delete({ where: { id: params.$ID } })
|
|
196
|
+
...
|
|
197
|
+
}
|
|
198
|
+
- pattern-not: |
|
|
199
|
+
export async function DELETE(req: Request, { params }: { params: { $ID: string } }) {
|
|
200
|
+
...
|
|
201
|
+
const { data: { user } } = await $CLIENT.auth.getUser()
|
|
202
|
+
...
|
|
203
|
+
$DB.$TABLE.delete({ where: { id: params.$ID, userId: user.id } })
|
|
204
|
+
...
|
|
205
|
+
}
|
|
206
|
+
message: "DELETE endpoint allows deletion without verifying ownership or admin role. Add authorization check."
|
|
207
|
+
severity: ERROR
|
|
208
|
+
languages: [typescript, javascript]
|
|
209
|
+
metadata:
|
|
210
|
+
datahogo_id: 25
|
|
211
|
+
datahogo_category: "api"
|
|
212
|
+
owasp_ref: "API5:2023"
|
|
213
|
+
|
|
214
|
+
# ID 27: SSRF in API - fetch with user-controlled URL
|
|
215
|
+
- id: datahogo-api-ssrf-fetch
|
|
216
|
+
patterns:
|
|
217
|
+
- pattern-either:
|
|
218
|
+
- pattern: |
|
|
219
|
+
fetch($REQ.body.$URL)
|
|
220
|
+
- pattern: |
|
|
221
|
+
fetch($REQ.query.$URL)
|
|
222
|
+
- pattern: |
|
|
223
|
+
axios.get($REQ.body.$URL)
|
|
224
|
+
- pattern: |
|
|
225
|
+
axios($REQ.body.$URL)
|
|
226
|
+
- pattern: |
|
|
227
|
+
fetch(`${$REQ.body.$PATH}`)
|
|
228
|
+
- pattern-not-inside: |
|
|
229
|
+
if (!$ALLOWED_URLS.includes($URL)) {
|
|
230
|
+
...
|
|
231
|
+
}
|
|
232
|
+
- pattern-not-inside: |
|
|
233
|
+
if (!$URL.startsWith('https://')) {
|
|
234
|
+
...
|
|
235
|
+
}
|
|
236
|
+
message: "Server-Side Request Forgery (SSRF): fetch/axios called with user-controlled URL. Validate URL against allowlist."
|
|
237
|
+
severity: ERROR
|
|
238
|
+
languages: [typescript, javascript]
|
|
239
|
+
metadata:
|
|
240
|
+
datahogo_id: 27
|
|
241
|
+
datahogo_category: "api"
|
|
242
|
+
owasp_ref: "API7:2023"
|
|
243
|
+
|
|
244
|
+
# ID 28: Verbose errors returned to client
|
|
245
|
+
- id: datahogo-api-verbose-error
|
|
246
|
+
patterns:
|
|
247
|
+
- pattern-either:
|
|
248
|
+
- pattern: |
|
|
249
|
+
return Response.json({ error: $ERR.message, stack: $ERR.stack }, ...)
|
|
250
|
+
- pattern: |
|
|
251
|
+
return Response.json({ ..., stack: $ERR.stack }, ...)
|
|
252
|
+
- pattern: |
|
|
253
|
+
return $RES.status($CODE).json({ error: $ERR, ... })
|
|
254
|
+
- pattern: |
|
|
255
|
+
return $RES.json({ error: $ERR.stack, ... })
|
|
256
|
+
message: "Verbose error details (stack trace or full error object) exposed to client. Return generic error message instead."
|
|
257
|
+
severity: WARNING
|
|
258
|
+
languages: [typescript, javascript]
|
|
259
|
+
metadata:
|
|
260
|
+
datahogo_id: 28
|
|
261
|
+
datahogo_category: "api"
|
|
262
|
+
owasp_ref: "API8:2023"
|
|
263
|
+
|
|
264
|
+
- id: datahogo-api-error-db-details
|
|
265
|
+
patterns:
|
|
266
|
+
- pattern: |
|
|
267
|
+
catch ($ERR) {
|
|
268
|
+
...
|
|
269
|
+
return Response.json({ error: $ERR.message }, ...)
|
|
270
|
+
...
|
|
271
|
+
}
|
|
272
|
+
- metavariable-regex:
|
|
273
|
+
metavariable: $ERR
|
|
274
|
+
regex: "(dbError|queryError|sqlError|prismaError)"
|
|
275
|
+
message: "Database error message returned to client. This may leak schema information. Return generic error instead."
|
|
276
|
+
severity: WARNING
|
|
277
|
+
languages: [typescript, javascript]
|
|
278
|
+
metadata:
|
|
279
|
+
datahogo_id: 28
|
|
280
|
+
datahogo_category: "api"
|
|
281
|
+
owasp_ref: "API8:2023"
|
|
282
|
+
|
|
283
|
+
# ID 30: Unsafe API consumption - trusting external API without validation
|
|
284
|
+
- id: datahogo-api-unsafe-external-response
|
|
285
|
+
patterns:
|
|
286
|
+
- pattern: |
|
|
287
|
+
const $RESPONSE = await fetch($EXTERNAL_URL)
|
|
288
|
+
const $DATA = await $RESPONSE.json()
|
|
289
|
+
...
|
|
290
|
+
$DB.$TABLE.create({ data: $DATA })
|
|
291
|
+
- pattern-not-inside: |
|
|
292
|
+
const $SCHEMA = z.object(...)
|
|
293
|
+
...
|
|
294
|
+
$SCHEMA.parse($DATA)
|
|
295
|
+
message: "External API response used without validation. Validate schema with Zod before using or storing data."
|
|
296
|
+
severity: WARNING
|
|
297
|
+
languages: [typescript, javascript]
|
|
298
|
+
metadata:
|
|
299
|
+
datahogo_id: 30
|
|
300
|
+
datahogo_category: "api"
|
|
301
|
+
owasp_ref: "API10:2023"
|
|
302
|
+
|
|
303
|
+
# ID 94: API docs exposed in production
|
|
304
|
+
- id: datahogo-api-docs-route
|
|
305
|
+
patterns:
|
|
306
|
+
- pattern-either:
|
|
307
|
+
- pattern: |
|
|
308
|
+
$APP.use('/swagger', ...)
|
|
309
|
+
- pattern: |
|
|
310
|
+
$APP.use('/api-docs', ...)
|
|
311
|
+
- pattern: |
|
|
312
|
+
$APP.get('/docs', ...)
|
|
313
|
+
- pattern: |
|
|
314
|
+
$APP.use('/graphql', graphiql: true, ...)
|
|
315
|
+
- pattern-not-inside: |
|
|
316
|
+
if (process.env.NODE_ENV === 'development') {
|
|
317
|
+
...
|
|
318
|
+
}
|
|
319
|
+
message: "API documentation endpoint exposed without environment check. Restrict to development only."
|
|
320
|
+
severity: WARNING
|
|
321
|
+
languages: [typescript, javascript]
|
|
322
|
+
metadata:
|
|
323
|
+
datahogo_id: 94
|
|
324
|
+
datahogo_category: "api"
|
|
325
|
+
owasp_ref: "API9:2023"
|
|
326
|
+
|
|
327
|
+
# ID 96: GraphQL query depth - no depth limit
|
|
328
|
+
- id: datahogo-api-graphql-no-depth-limit
|
|
329
|
+
patterns:
|
|
330
|
+
- pattern: |
|
|
331
|
+
new ApolloServer({ ... })
|
|
332
|
+
- pattern-not: |
|
|
333
|
+
new ApolloServer({
|
|
334
|
+
...
|
|
335
|
+
validationRules: [..., depthLimit($LIMIT), ...],
|
|
336
|
+
...
|
|
337
|
+
})
|
|
338
|
+
message: "GraphQL server without query depth limit. Add depth limiting to prevent resource exhaustion attacks."
|
|
339
|
+
severity: WARNING
|
|
340
|
+
languages: [typescript, javascript]
|
|
341
|
+
metadata:
|
|
342
|
+
datahogo_id: 96
|
|
343
|
+
datahogo_category: "api"
|
|
344
|
+
owasp_ref: "API4:2023"
|
|
345
|
+
|
|
346
|
+
# ID 97: GraphQL batching attack - no batch size limit
|
|
347
|
+
- id: datahogo-api-graphql-no-batch-limit
|
|
348
|
+
patterns:
|
|
349
|
+
- pattern: |
|
|
350
|
+
new ApolloServer({ ... })
|
|
351
|
+
- pattern-not: |
|
|
352
|
+
new ApolloServer({
|
|
353
|
+
...
|
|
354
|
+
plugins: [..., ApolloServerPluginLandingPageDisabled(), ...],
|
|
355
|
+
...
|
|
356
|
+
})
|
|
357
|
+
- pattern-not-inside: |
|
|
358
|
+
if ($ARRAY.length > $LIMIT) {
|
|
359
|
+
...
|
|
360
|
+
}
|
|
361
|
+
message: "GraphQL server may be vulnerable to batching attacks. Implement query batch size limits."
|
|
362
|
+
severity: WARNING
|
|
363
|
+
languages: [typescript, javascript]
|
|
364
|
+
metadata:
|
|
365
|
+
datahogo_id: 97
|
|
366
|
+
datahogo_category: "api"
|
|
367
|
+
owasp_ref: "API4:2023"
|
|
368
|
+
|
|
369
|
+
# ID 100: API key scope too broad
|
|
370
|
+
- id: datahogo-api-key-admin-operations
|
|
371
|
+
patterns:
|
|
372
|
+
- pattern-either:
|
|
373
|
+
- pattern: |
|
|
374
|
+
const $CLIENT = createClient($URL, process.env.SUPABASE_SERVICE_ROLE_KEY)
|
|
375
|
+
- pattern: |
|
|
376
|
+
Authorization: 'Bearer ' + process.env.ADMIN_API_KEY
|
|
377
|
+
- pattern-not-inside: |
|
|
378
|
+
// Service role required for $REASON
|
|
379
|
+
...
|
|
380
|
+
- pattern-not-inside: |
|
|
381
|
+
// Admin key needed for $REASON
|
|
382
|
+
...
|
|
383
|
+
message: "Admin/service role API key used without justification comment. Use least-privilege keys when possible."
|
|
384
|
+
severity: WARNING
|
|
385
|
+
languages: [typescript, javascript]
|
|
386
|
+
metadata:
|
|
387
|
+
datahogo_id: 100
|
|
388
|
+
datahogo_category: "api"
|
|
389
|
+
owasp_ref: "API8:2023"
|
|
390
|
+
|
|
391
|
+
# ID 196: No request body limit
|
|
392
|
+
- id: datahogo-api-no-body-limit-express
|
|
393
|
+
patterns:
|
|
394
|
+
- pattern: |
|
|
395
|
+
$APP.use(express.json())
|
|
396
|
+
- pattern-not: |
|
|
397
|
+
$APP.use(express.json({ limit: $LIMIT }))
|
|
398
|
+
message: "express.json() middleware without body size limit. Add limit to prevent resource exhaustion (e.g., { limit: '1mb' })."
|
|
399
|
+
severity: WARNING
|
|
400
|
+
languages: [typescript, javascript]
|
|
401
|
+
metadata:
|
|
402
|
+
datahogo_id: 196
|
|
403
|
+
datahogo_category: "api"
|
|
404
|
+
owasp_ref: "API4:2023"
|
|
405
|
+
|
|
406
|
+
# ID 197: No rate limiting middleware
|
|
407
|
+
- id: datahogo-api-missing-global-rate-limit
|
|
408
|
+
patterns:
|
|
409
|
+
- pattern: |
|
|
410
|
+
const $APP = express()
|
|
411
|
+
...
|
|
412
|
+
$APP.listen($PORT)
|
|
413
|
+
- pattern-not: |
|
|
414
|
+
const $APP = express()
|
|
415
|
+
...
|
|
416
|
+
$APP.use(rateLimit(...))
|
|
417
|
+
...
|
|
418
|
+
$APP.listen($PORT)
|
|
419
|
+
message: "Express app without global rate limiting middleware. Add rate limiting to prevent abuse."
|
|
420
|
+
severity: WARNING
|
|
421
|
+
languages: [typescript, javascript]
|
|
422
|
+
metadata:
|
|
423
|
+
datahogo_id: 197
|
|
424
|
+
datahogo_category: "api"
|
|
425
|
+
owasp_ref: "API4:2023"
|
|
426
|
+
|
|
427
|
+
# ID 198: No request timeout
|
|
428
|
+
- id: datahogo-api-no-timeout
|
|
429
|
+
patterns:
|
|
430
|
+
- pattern-either:
|
|
431
|
+
- pattern: |
|
|
432
|
+
fetch($URL)
|
|
433
|
+
- pattern: |
|
|
434
|
+
axios.get($URL)
|
|
435
|
+
- pattern: |
|
|
436
|
+
axios.post($URL, $DATA)
|
|
437
|
+
- pattern-not: |
|
|
438
|
+
fetch($URL, { signal: AbortSignal.timeout($MS) })
|
|
439
|
+
- pattern-not: |
|
|
440
|
+
axios.get($URL, { timeout: $MS })
|
|
441
|
+
- pattern-not: |
|
|
442
|
+
axios.post($URL, $DATA, { timeout: $MS })
|
|
443
|
+
- pattern-inside: |
|
|
444
|
+
export async function $METHOD(req: Request) {
|
|
445
|
+
...
|
|
446
|
+
}
|
|
447
|
+
message: "HTTP request in API handler without timeout. Add timeout to prevent hung requests from exhausting resources."
|
|
448
|
+
severity: WARNING
|
|
449
|
+
languages: [typescript, javascript]
|
|
450
|
+
metadata:
|
|
451
|
+
datahogo_id: 198
|
|
452
|
+
datahogo_category: "api"
|
|
453
|
+
owasp_ref: "API4:2023"
|
|
454
|
+
|
|
455
|
+
# ID 199: WebSocket without auth
|
|
456
|
+
- id: datahogo-api-websocket-no-auth
|
|
457
|
+
patterns:
|
|
458
|
+
- pattern-either:
|
|
459
|
+
- pattern: |
|
|
460
|
+
$WS.on('connection', ($SOCKET) => {
|
|
461
|
+
...
|
|
462
|
+
})
|
|
463
|
+
- pattern: |
|
|
464
|
+
io.on('connection', ($SOCKET) => {
|
|
465
|
+
...
|
|
466
|
+
})
|
|
467
|
+
- pattern-not: |
|
|
468
|
+
$WS.on('connection', ($SOCKET) => {
|
|
469
|
+
...
|
|
470
|
+
if (!$SOCKET.handshake.auth.$TOKEN) {
|
|
471
|
+
...
|
|
472
|
+
}
|
|
473
|
+
...
|
|
474
|
+
})
|
|
475
|
+
- pattern-not: |
|
|
476
|
+
io.on('connection', ($SOCKET) => {
|
|
477
|
+
...
|
|
478
|
+
if (!$SOCKET.handshake.auth.$TOKEN) {
|
|
479
|
+
...
|
|
480
|
+
}
|
|
481
|
+
...
|
|
482
|
+
})
|
|
483
|
+
message: "WebSocket connection handler without authentication check. Verify user identity before allowing connections."
|
|
484
|
+
severity: ERROR
|
|
485
|
+
languages: [typescript, javascript]
|
|
486
|
+
metadata:
|
|
487
|
+
datahogo_id: 199
|
|
488
|
+
datahogo_category: "api"
|
|
489
|
+
owasp_ref: "API2:2023"
|