@profullstack/threatcrush 0.8.0 → 0.10.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/index.js CHANGED
@@ -9998,6 +9998,56 @@ var CODE_RULES = [
9998
9998
  severity: "high",
9999
9999
  pattern: /(?:token|secret|password|salt|nonce|session|otp|reset|apikey|api_key)[\w]*\s*[:=][^;\n]{0,60}(?:Math\s*\.\s*random\s*\(|\brandom\s*\.\s*(?:random|randint|choice)\s*\(|\brand\s*\()/i
10000
10000
  },
10001
+ // ── Weak crypto: Python ──────────────────────────────────────────────────
10002
+ //
10003
+ // The generic rules above catch the credential case on the matched line.
10004
+ // These cover what they miss in Python, where the security role of a value is
10005
+ // set by the enclosing function rather than a same-line assignment — a
10006
+ // `random`-drawn token that is *returned*, an MD5 used to *verify* an
10007
+ // artifact — and the broken ciphers, which have no safe use at all.
10008
+ {
10009
+ id: "py-broken-cipher",
10010
+ title: "broken cipher or ECB mode",
10011
+ consequence: "DES, RC2, RC4 and Blowfish are broken or too small to rely on, and ECB encrypts identical plaintext blocks to identical ciphertext, so structure in the data survives encryption. None of them provides the confidentiality their use implies.",
10012
+ cwe: "CWE-327",
10013
+ severity: "high",
10014
+ languages: ["python"],
10015
+ // PyCryptodome/PyCrypto constructors. The mode matters only for AES, whose
10016
+ // safe modes (GCM, CTR, CBC) are common — so AES matches solely on ECB,
10017
+ // while DES/RC4/Blowfish are broken by the algorithm regardless of mode.
10018
+ pattern: /\b(?:DES|DES3|ARC2|RC2|ARC4|RC4|Blowfish|XOR)\s*\.\s*new\s*\(|\bAES\s*\.\s*new\s*\([^)\n]*\bMODE_ECB\b/,
10019
+ inherent: true,
10020
+ guard: false
10021
+ },
10022
+ {
10023
+ id: "py-weak-hash",
10024
+ title: "broken hash algorithm",
10025
+ consequence: "MD5 and SHA-1 have practical collisions, so a digest used for integrity or a signature can be forged to match a value the code trusts.",
10026
+ cwe: "CWE-327",
10027
+ severity: "medium",
10028
+ languages: ["python"],
10029
+ pattern: /\bhashlib\s*\.\s*(?:md5|sha1)\s*\(/,
10030
+ // Python 3.9+ marks a non-security digest — a cache key, an ETag — with
10031
+ // `usedforsecurity=False`, which is exactly the "this MD5 is not a security
10032
+ // claim" signal, so it exempts the line rather than being flagged.
10033
+ lineGuard: /usedforsecurity\s*=\s*False/
10034
+ },
10035
+ {
10036
+ id: "py-predictable-random-seed",
10037
+ title: "PRNG seeded from a predictable value",
10038
+ consequence: "Seeding `random` from the clock or the process id makes its whole sequence reproducible, so anything drawn from it afterwards \u2014 a token, an id, a shuffle \u2014 can be regenerated by guessing the seed.",
10039
+ cwe: "CWE-338",
10040
+ severity: "high",
10041
+ languages: ["python"],
10042
+ // A time- or pid-derived seed, which is the predictable kind. A fixed
10043
+ // integer seed (`random.seed(42)`) is deliberate reproducibility for tests
10044
+ // and simulations, so it is left alone. This needs no credential context:
10045
+ // seeding the global PRNG from the clock is a weakness on its own terms,
10046
+ // which is why the enclosing function's name — that this engine does not
10047
+ // read as evidence anyway — is not consulted.
10048
+ pattern: /\brandom\s*\.\s*seed\s*\([^)\n]*(?:time\s*\.\s*time|datetime|\.\s*now\s*\(|getpid)/,
10049
+ inherent: true
10050
+ },
10001
10051
  {
10002
10052
  id: "redos-nested-quantifier",
10003
10053
  title: "regex with nested unbounded quantifiers",
@@ -10355,6 +10405,155 @@ var CODE_RULES = [
10355
10405
  // constant the author wrote and is not a finding.
10356
10406
  pattern: /\btemplate\s*\.\s*(?:HTML|JS|CSS|HTMLAttr|URL|Srcset)\s*\(\s*(?!\s*[`"])/,
10357
10407
  needsContext: true
10408
+ },
10409
+ // ── Misconfiguration, weak crypto and the rest of the injection family ────
10410
+ //
10411
+ // Each of these is a defect the *line* shows — a debug flag left on, a
10412
+ // wildcard CORS origin, a key literal, a fast password hash — so the safe
10413
+ // counterpart in the corpus differs on something visible here, not three
10414
+ // functions away. The classes that genuinely need whole-function reasoning
10415
+ // (CSRF, IDOR, TOCTOU, missing authorization) are deliberately still absent;
10416
+ // see KNOWN_GAPS.
10417
+ {
10418
+ id: "py-framework-debug-enabled",
10419
+ title: "debug mode enabled",
10420
+ 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.",
10421
+ cwe: "CWE-489",
10422
+ severity: "high",
10423
+ languages: ["python"],
10424
+ pattern: /\bDEBUG['"\]]*\s*[=:]\s*True\b|\.\s*run\s*\([^)\n]*\bdebug\s*=\s*True\b/,
10425
+ inherent: true
10426
+ },
10427
+ {
10428
+ id: "js-cors-wildcard-credentials",
10429
+ title: "wildcard CORS origin with credentials",
10430
+ 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.",
10431
+ cwe: "CWE-942",
10432
+ severity: "high",
10433
+ languages: ["javascript", "typescript"],
10434
+ pattern: /origin\s*:\s*['"`]\*['"`][^\n]*credentials\s*:\s*true|credentials\s*:\s*true[^\n]*origin\s*:\s*['"`]\*['"`]/,
10435
+ inherent: true
10436
+ },
10437
+ {
10438
+ id: "js-cookie-insecure-flag",
10439
+ title: "cookie set with Secure disabled",
10440
+ 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.",
10441
+ cwe: "CWE-614",
10442
+ severity: "medium",
10443
+ languages: ["javascript", "typescript"],
10444
+ pattern: /\.\s*cookie\s*\([^\n]*\bsecure\s*:\s*false\b/i
10445
+ },
10446
+ {
10447
+ id: "js-hardcoded-crypto-key",
10448
+ title: "hardcoded key material",
10449
+ 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.",
10450
+ cwe: "CWE-321",
10451
+ severity: "high",
10452
+ languages: ["javascript", "typescript"],
10453
+ pattern: /\b(?:key|secret|iv|passphrase|salt|hmac)\w*\s*=\s*Buffer\s*\.\s*from\s*\(\s*['"`]/i,
10454
+ fileRequires: /\bcrypto\b|createCipheriv|createDecipheriv|createHmac/
10455
+ },
10456
+ {
10457
+ id: "py-hardcoded-secret-key",
10458
+ title: "hardcoded application secret",
10459
+ consequence: "A signing secret in source lets anyone with the code forge what it signs \u2014 session cookies, tokens, password-reset links.",
10460
+ cwe: "CWE-798",
10461
+ severity: "high",
10462
+ languages: ["python"],
10463
+ // Assigned a *string literal*. A value from a provider call (`os.environ`,
10464
+ // `secret_provider(...)`) is the correct shape and starts with an
10465
+ // identifier after the `=`, not a quote.
10466
+ pattern: /\b(?:SECRET_KEY|JWT_SECRET|SIGNING_KEY|SESSION_SECRET|PRIVATE_KEY)['"\]]*\s*[=:]\s*['"`][^'"`\n]{6,}/
10467
+ },
10468
+ {
10469
+ id: "py-ldap-injection",
10470
+ title: "LDAP filter built by interpolation",
10471
+ consequence: "Unescaped input in an LDAP filter lets an attacker rewrite the query \u2014 widening a match, bypassing a check, or enumerating the directory.",
10472
+ cwe: "CWE-90",
10473
+ severity: "high",
10474
+ languages: ["python"],
10475
+ // An f-string whose content is an LDAP filter (`(&…` / `(|…`) with an
10476
+ // interpolation. The safe form escapes, so the window carries an escaper.
10477
+ pattern: /\bf['"][^'"\n]*\([&|][^'"\n]*\{[^}\n]+\}/,
10478
+ guard: /escap/i
10479
+ },
10480
+ {
10481
+ id: "py-xpath-injection",
10482
+ title: "XPath built by interpolation",
10483
+ consequence: "Unescaped input in an XPath expression lets an attacker rewrite the query and read nodes it was meant to exclude.",
10484
+ cwe: "CWE-643",
10485
+ severity: "high",
10486
+ languages: ["python"],
10487
+ pattern: /\bf['"][^'"\n]*(?:\/\/|\/\w+\[)[^'"\n]*\{[^}\n]+\}/
10488
+ },
10489
+ {
10490
+ id: "py-fast-password-hash",
10491
+ title: "password hashed with a fast digest",
10492
+ 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).",
10493
+ cwe: "CWE-759",
10494
+ severity: "high",
10495
+ languages: ["python"],
10496
+ pattern: /\bhashlib\s*\.\s*(?:sha224|sha256|sha384|sha512)\s*\([^)\n]*(?:password|passwd|passphrase|pwd)/i,
10497
+ guard: /pbkdf2|scrypt|bcrypt|argon/i
10498
+ },
10499
+ {
10500
+ id: "py-plaintext-password-retained",
10501
+ title: "plaintext password stored",
10502
+ 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.",
10503
+ cwe: "CWE-256",
10504
+ severity: "high",
10505
+ languages: ["python"],
10506
+ pattern: /['"]password['"]\s*:\s*(?:form|request|req|data|payload|body|params)\b/i
10507
+ },
10508
+ {
10509
+ id: "js-timing-unsafe-mac-compare",
10510
+ title: "MAC or signature compared with ==",
10511
+ 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`.",
10512
+ cwe: "CWE-208",
10513
+ severity: "medium",
10514
+ languages: ["javascript", "typescript"],
10515
+ pattern: /\b\w*(?:[Ss]ignature|[Hh]mac|[Dd]igest)\s*===?\s*\w|\w\s*===?\s*\w*(?:[Ss]ignature|[Hh]mac|[Dd]igest)\b/,
10516
+ // Comparing `.length` is the *safe* preamble to a constant-time check, not
10517
+ // the timing-unsafe value comparison this rule is about — and the
10518
+ // `timingSafeEqual` that follows it sits forward of the line, out of the
10519
+ // backward guard window.
10520
+ lineGuard: /\.\s*length\b/,
10521
+ guard: /timingSafeEqual/
10522
+ },
10523
+ {
10524
+ id: "js-predictable-cipher-iv",
10525
+ title: "static initialization vector",
10526
+ 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.",
10527
+ cwe: "CWE-329",
10528
+ severity: "high",
10529
+ languages: ["javascript", "typescript"],
10530
+ pattern: /\b\w*[Ii][Vv]\s*=\s*Buffer\s*\.\s*(?:alloc|from)\s*\(/,
10531
+ fileRequires: /createCipheriv|\bcrypto\b/,
10532
+ guard: /randomBytes|randomFill/
10533
+ },
10534
+ // A rule for `Math.random().toString(36)` was tried and dropped: the exact
10535
+ // shape generates security tokens *and* benign callback/correlation ids
10536
+ // (Capacitor's native bridge uses it for the latter), with no line-visible
10537
+ // signal between them. The credential-scoped `insecure-randomness-for-secret`
10538
+ // above still catches the `token = …Math.random…` case; the bare shape is
10539
+ // left alone rather than flagged on every id generator.
10540
+ {
10541
+ id: "js-mass-assignment",
10542
+ title: "mass assignment from request data",
10543
+ 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.",
10544
+ cwe: "CWE-915",
10545
+ severity: "high",
10546
+ languages: ["javascript", "typescript"],
10547
+ pattern: /\bObject\s*\.\s*assign\s*\([^,\n]+,\s*(?:req|request|ctx)\s*\.\s*(?:body|query|params)\b/
10548
+ },
10549
+ {
10550
+ id: "js-header-injection",
10551
+ title: "response header set from request input",
10552
+ 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).",
10553
+ cwe: "CWE-113",
10554
+ severity: "high",
10555
+ languages: ["javascript", "typescript"],
10556
+ pattern: /\.\s*(?:setHeader|header|set)\s*\(\s*['"`][^'"`\n]+['"`]\s*,\s*(?:req|request|ctx)\s*\.\s*(?:query|body|params|headers)\b/
10358
10557
  }
10359
10558
  ];
10360
10559
  var COMMENT_PREFIX = /^\s*(?:\/\/|\/\*|\*|#|--|<!--)/;