@profullstack/threatcrush 0.9.0 → 0.11.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/dist/daemon.js +183 -1
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +183 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -5135,7 +5135,189 @@ var CODE_RULES = [
|
|
|
5135
5135
|
// constant the author wrote and is not a finding.
|
|
5136
5136
|
pattern: /\btemplate\s*\.\s*(?:HTML|JS|CSS|HTMLAttr|URL|Srcset)\s*\(\s*(?!\s*[`"])/,
|
|
5137
5137
|
needsContext: true
|
|
5138
|
-
}
|
|
5138
|
+
},
|
|
5139
|
+
// ── Misconfiguration, weak crypto and the rest of the injection family ────
|
|
5140
|
+
//
|
|
5141
|
+
// Each of these is a defect the *line* shows — a debug flag left on, a
|
|
5142
|
+
// wildcard CORS origin, a key literal, a fast password hash — so the safe
|
|
5143
|
+
// counterpart in the corpus differs on something visible here, not three
|
|
5144
|
+
// functions away. The classes that genuinely need whole-function reasoning
|
|
5145
|
+
// (CSRF, IDOR, TOCTOU, missing authorization) are deliberately still absent;
|
|
5146
|
+
// see KNOWN_GAPS.
|
|
5147
|
+
{
|
|
5148
|
+
id: "py-framework-debug-enabled",
|
|
5149
|
+
title: "debug mode enabled",
|
|
5150
|
+
consequence: "Framework debug mode serves an interactive traceback console on any error \u2014 arbitrary code execution to whoever triggers it \u2014 and leaks source and configuration.",
|
|
5151
|
+
cwe: "CWE-489",
|
|
5152
|
+
severity: "high",
|
|
5153
|
+
languages: ["python"],
|
|
5154
|
+
pattern: /\bDEBUG['"\]]*\s*[=:]\s*True\b|\.\s*run\s*\([^)\n]*\bdebug\s*=\s*True\b/,
|
|
5155
|
+
inherent: true
|
|
5156
|
+
},
|
|
5157
|
+
{
|
|
5158
|
+
id: "js-cors-wildcard-credentials",
|
|
5159
|
+
title: "wildcard CORS origin with credentials",
|
|
5160
|
+
consequence: "A `*` origin combined with credentials lets any site read authenticated responses on the victim\u2019s behalf: the browser attaches their cookies and hands the result to the attacker\u2019s page.",
|
|
5161
|
+
cwe: "CWE-942",
|
|
5162
|
+
severity: "high",
|
|
5163
|
+
languages: ["javascript", "typescript"],
|
|
5164
|
+
pattern: /origin\s*:\s*['"`]\*['"`][^\n]*credentials\s*:\s*true|credentials\s*:\s*true[^\n]*origin\s*:\s*['"`]\*['"`]/,
|
|
5165
|
+
inherent: true
|
|
5166
|
+
},
|
|
5167
|
+
{
|
|
5168
|
+
id: "js-cookie-insecure-flag",
|
|
5169
|
+
title: "cookie set with Secure disabled",
|
|
5170
|
+
consequence: "Without Secure the cookie travels over plain HTTP, where anyone on the path reads it. A session cookie set this way is a session anyone can lift.",
|
|
5171
|
+
cwe: "CWE-614",
|
|
5172
|
+
severity: "medium",
|
|
5173
|
+
languages: ["javascript", "typescript"],
|
|
5174
|
+
pattern: /\.\s*cookie\s*\([^\n]*\bsecure\s*:\s*false\b/i
|
|
5175
|
+
},
|
|
5176
|
+
{
|
|
5177
|
+
id: "js-hardcoded-crypto-key",
|
|
5178
|
+
title: "hardcoded key material",
|
|
5179
|
+
consequence: "A key committed to source is a key everyone with the repo has. The encryption it backs protects nothing once the source is shared, forked or leaked.",
|
|
5180
|
+
cwe: "CWE-321",
|
|
5181
|
+
severity: "high",
|
|
5182
|
+
languages: ["javascript", "typescript"],
|
|
5183
|
+
pattern: /\b(?:key|secret|iv|passphrase|salt|hmac)\w*\s*=\s*Buffer\s*\.\s*from\s*\(\s*['"`]/i,
|
|
5184
|
+
fileRequires: /\bcrypto\b|createCipheriv|createDecipheriv|createHmac/
|
|
5185
|
+
},
|
|
5186
|
+
{
|
|
5187
|
+
id: "py-hardcoded-secret-key",
|
|
5188
|
+
title: "hardcoded application secret",
|
|
5189
|
+
consequence: "A signing secret in source lets anyone with the code forge what it signs \u2014 session cookies, tokens, password-reset links.",
|
|
5190
|
+
cwe: "CWE-798",
|
|
5191
|
+
severity: "high",
|
|
5192
|
+
languages: ["python"],
|
|
5193
|
+
// Assigned a *string literal*. A value from a provider call (`os.environ`,
|
|
5194
|
+
// `secret_provider(...)`) is the correct shape and starts with an
|
|
5195
|
+
// identifier after the `=`, not a quote.
|
|
5196
|
+
pattern: /\b(?:SECRET_KEY|JWT_SECRET|SIGNING_KEY|SESSION_SECRET|PRIVATE_KEY)['"\]]*\s*[=:]\s*['"`][^'"`\n]{6,}/
|
|
5197
|
+
},
|
|
5198
|
+
{
|
|
5199
|
+
id: "py-ldap-injection",
|
|
5200
|
+
title: "LDAP filter built by interpolation",
|
|
5201
|
+
consequence: "Unescaped input in an LDAP filter lets an attacker rewrite the query \u2014 widening a match, bypassing a check, or enumerating the directory.",
|
|
5202
|
+
cwe: "CWE-90",
|
|
5203
|
+
severity: "high",
|
|
5204
|
+
languages: ["python"],
|
|
5205
|
+
// An f-string whose content is an LDAP filter (`(&…` / `(|…`) with an
|
|
5206
|
+
// interpolation. One variant per quote so an inner `'` inside a `"`-string
|
|
5207
|
+
// does not end the class early.
|
|
5208
|
+
pattern: /\bf"[^"\n]*\([&|][^"\n]*\{[^}\n]+\}|\bf'[^'\n]*\([&|][^'\n]*\{[^}\n]+\}/,
|
|
5209
|
+
// The guard is an escaper *call*, not the mere presence of `escape` — the
|
|
5210
|
+
// module-level `from ldap.filter import escape_filter_chars` sits in every
|
|
5211
|
+
// window and would otherwise exonerate the unescaped filter too.
|
|
5212
|
+
guard: /\bescape\w*\s*\(/i
|
|
5213
|
+
},
|
|
5214
|
+
{
|
|
5215
|
+
id: "py-xpath-injection",
|
|
5216
|
+
title: "XPath built by interpolation",
|
|
5217
|
+
consequence: "Unescaped input in an XPath expression lets an attacker rewrite the query and read nodes it was meant to exclude.",
|
|
5218
|
+
cwe: "CWE-643",
|
|
5219
|
+
severity: "high",
|
|
5220
|
+
languages: ["python"],
|
|
5221
|
+
// Per-quote, so an inner `'` in a `"`-delimited f-string (`text()='{x}'`)
|
|
5222
|
+
// does not truncate the match before the interpolation.
|
|
5223
|
+
pattern: /\bf"[^"\n]*(?:\/\/|\/\w+\[)[^"\n]*\{[^}\n]+\}|\bf'[^'\n]*(?:\/\/|\/\w+\[)[^'\n]*\{[^}\n]+\}/
|
|
5224
|
+
},
|
|
5225
|
+
{
|
|
5226
|
+
id: "py-fast-password-hash",
|
|
5227
|
+
title: "password hashed with a fast digest",
|
|
5228
|
+
consequence: "SHA-2 is built to be fast, which is exactly wrong for a password: a leaked hash is brute-forced at billions of guesses a second. Passwords need a slow, salted KDF (bcrypt, scrypt, argon2, PBKDF2).",
|
|
5229
|
+
cwe: "CWE-759",
|
|
5230
|
+
severity: "high",
|
|
5231
|
+
languages: ["python"],
|
|
5232
|
+
pattern: /\bhashlib\s*\.\s*(?:sha224|sha256|sha384|sha512)\s*\([^)\n]*(?:password|passwd|passphrase|pwd)/i,
|
|
5233
|
+
guard: /pbkdf2|scrypt|bcrypt|argon/i
|
|
5234
|
+
},
|
|
5235
|
+
{
|
|
5236
|
+
id: "py-plaintext-password-retained",
|
|
5237
|
+
title: "plaintext password stored",
|
|
5238
|
+
consequence: "A record that keeps the password itself, not a hash, turns one database leak into every user\u2019s credential \u2014 reused across every other site they log into.",
|
|
5239
|
+
cwe: "CWE-256",
|
|
5240
|
+
severity: "high",
|
|
5241
|
+
languages: ["python"],
|
|
5242
|
+
pattern: /['"]password['"]\s*:\s*(?:form|request|req|data|payload|body|params)\b/i
|
|
5243
|
+
},
|
|
5244
|
+
{
|
|
5245
|
+
id: "js-timing-unsafe-mac-compare",
|
|
5246
|
+
title: "MAC or signature compared with ==",
|
|
5247
|
+
consequence: "`===` on a signature returns the moment a byte differs, so response time leaks how much of a forged signature is correct \u2014 enough to recover a valid one byte by byte. Use `crypto.timingSafeEqual`.",
|
|
5248
|
+
cwe: "CWE-208",
|
|
5249
|
+
severity: "medium",
|
|
5250
|
+
languages: ["javascript", "typescript"],
|
|
5251
|
+
pattern: /\b\w*(?:[Ss]ignature|[Hh]mac|[Dd]igest)\s*===?\s*\w|\w\s*===?\s*\w*(?:[Ss]ignature|[Hh]mac|[Dd]igest)\b/,
|
|
5252
|
+
// Comparing `.length` is the *safe* preamble to a constant-time check, not
|
|
5253
|
+
// the timing-unsafe value comparison this rule is about — and the
|
|
5254
|
+
// `timingSafeEqual` that follows it sits forward of the line, out of the
|
|
5255
|
+
// backward guard window.
|
|
5256
|
+
lineGuard: /\.\s*length\b/,
|
|
5257
|
+
guard: /timingSafeEqual/
|
|
5258
|
+
},
|
|
5259
|
+
{
|
|
5260
|
+
id: "js-predictable-cipher-iv",
|
|
5261
|
+
title: "static initialization vector",
|
|
5262
|
+
consequence: "A fixed IV reused across encryptions leaks whether two plaintexts are equal and, in CBC/CTR, breaks confidentiality outright. The IV must be random per message.",
|
|
5263
|
+
cwe: "CWE-329",
|
|
5264
|
+
severity: "high",
|
|
5265
|
+
languages: ["javascript", "typescript"],
|
|
5266
|
+
pattern: /\b\w*[Ii][Vv]\s*=\s*Buffer\s*\.\s*(?:alloc|from)\s*\(/,
|
|
5267
|
+
fileRequires: /createCipheriv|\bcrypto\b/,
|
|
5268
|
+
guard: /randomBytes|randomFill/
|
|
5269
|
+
},
|
|
5270
|
+
// A rule for `Math.random().toString(36)` was tried and dropped: the exact
|
|
5271
|
+
// shape generates security tokens *and* benign callback/correlation ids
|
|
5272
|
+
// (Capacitor's native bridge uses it for the latter), with no line-visible
|
|
5273
|
+
// signal between them. The credential-scoped `insecure-randomness-for-secret`
|
|
5274
|
+
// above still catches the `token = …Math.random…` case; the bare shape is
|
|
5275
|
+
// left alone rather than flagged on every id generator.
|
|
5276
|
+
{
|
|
5277
|
+
id: "js-mass-assignment",
|
|
5278
|
+
title: "mass assignment from request data",
|
|
5279
|
+
consequence: "Copying the whole request body onto a record lets a caller set fields you never exposed \u2014 `isAdmin`, `role`, `balance` \u2014 because nothing stands between the input and the object.",
|
|
5280
|
+
cwe: "CWE-915",
|
|
5281
|
+
severity: "high",
|
|
5282
|
+
languages: ["javascript", "typescript"],
|
|
5283
|
+
pattern: /\bObject\s*\.\s*assign\s*\([^,\n]+,\s*(?:req|request|ctx)\s*\.\s*(?:body|query|params)\b/
|
|
5284
|
+
},
|
|
5285
|
+
{
|
|
5286
|
+
id: "js-header-injection",
|
|
5287
|
+
title: "response header set from request input",
|
|
5288
|
+
consequence: "A CR/LF in the value splits the response \u2014 the attacker injects headers or a whole second response (cache poisoning, a forged Set-Cookie).",
|
|
5289
|
+
cwe: "CWE-113",
|
|
5290
|
+
severity: "high",
|
|
5291
|
+
languages: ["javascript", "typescript"],
|
|
5292
|
+
pattern: /\.\s*(?:setHeader|header|set)\s*\(\s*['"`][^'"`\n]+['"`]\s*,\s*(?:req|request|ctx)\s*\.\s*(?:query|body|params|headers)\b/
|
|
5293
|
+
},
|
|
5294
|
+
{
|
|
5295
|
+
id: "js-nosql-injection",
|
|
5296
|
+
title: "NoSQL query built from request data",
|
|
5297
|
+
consequence: 'A request object passed straight to `find`/`update` lets the caller supply MongoDB operators \u2014 `{"$ne": null}`, `{"$gt": ""}`, `{"$where": "\u2026"}` \u2014 turning a lookup into an authentication bypass or arbitrary query.',
|
|
5298
|
+
cwe: "CWE-943",
|
|
5299
|
+
severity: "high",
|
|
5300
|
+
languages: ["javascript", "typescript"],
|
|
5301
|
+
pattern: /\.\s*(?:find|findOne|findOneAndUpdate|updateOne|updateMany|update|deleteOne|deleteMany|remove|count|countDocuments|aggregate)\s*\(\s*(?:req|request|ctx)\s*\.\s*(?:body|query|params)\b/
|
|
5302
|
+
},
|
|
5303
|
+
{
|
|
5304
|
+
id: "js-host-header-trust",
|
|
5305
|
+
title: "URL built from the Host header",
|
|
5306
|
+
consequence: "The Host header is attacker-controlled. Building a link from it \u2014 a password-reset URL above all \u2014 lets an attacker point the victim\u2019s reset token at a domain they control.",
|
|
5307
|
+
cwe: "CWE-346",
|
|
5308
|
+
severity: "high",
|
|
5309
|
+
languages: ["javascript", "typescript"],
|
|
5310
|
+
pattern: /\$\{[^}\n]*\breq(?:uest)?\s*\.\s*(?:headers\s*\.\s*host|hostname|host)\b|['"`]\s*\+\s*req(?:uest)?\s*\.\s*(?:headers\s*\.\s*host|hostname)\b/
|
|
5311
|
+
}
|
|
5312
|
+
// A recursive-merge prototype-pollution rule (`target[key] = source[key]`
|
|
5313
|
+
// with no `__proto__` guard) was built and dropped. The bare copy-by-key is
|
|
5314
|
+
// the safe allow-listed shape (`updates[field] = body[field]` over an
|
|
5315
|
+
// `allowedFields` list) as often as the vulnerable one; whether it is a sink
|
|
5316
|
+
// depends on where the key comes from and which of endless guard idioms
|
|
5317
|
+
// filters it — a whole-function question this line-oriented engine does not
|
|
5318
|
+
// answer. It flagged legitimate merges in Capacitor and in this repo's own
|
|
5319
|
+
// web app. `js-prototype-pollution` still catches the explicit `__proto__`
|
|
5320
|
+
// literal; the recursive-merge case is left to KNOWN_GAPS.
|
|
5139
5321
|
];
|
|
5140
5322
|
var COMMENT_PREFIX = /^\s*(?:\/\/|\/\*|\*|#|--|<!--)/;
|
|
5141
5323
|
var DEFINITION_PREFIX = /^\s*(?:(?:export|public|private|protected|static|final|async|abstract)\s+)*(?:def|function|func|class|module|interface|struct|impl|fn|sub)\b/;
|