rules:
  - id: auth.flow.weak-password-hash
    languages:
      - javascript
      - typescript
    severity: ERROR
    message: |
      A password is being hashed with a fast, general-purpose hash
      (MD5/SHA-1/SHA-256/SHA-512 via `crypto.createHash`). These functions
      are designed to be FAST, so an attacker who steals the database can
      brute-force billions of candidate passwords per second on commodity
      GPUs. Salting alone does not fix this.

      Use a dedicated, deliberately-slow password hashing function instead:
      `bcrypt`, `argon2` (argon2id), or `scrypt` (`crypto.scrypt`). These add
      a tunable work factor that makes large-scale brute-forcing infeasible.
    # Anchored on the *name* of the hashed value: we only flag when the value
    # passed to `.update(...)` is named like a password. This avoids the
    # common FP class: checksums, file digests (`createHash('sha256')
    # .update(fileBuffer)`), and HMAC signatures (`createHmac`) are NOT
    # password hashing and must not be flagged.
    pattern-either:
      # Chained form: createHash('md5').update(password).digest(...)
      - patterns:
          - pattern-either:
              - pattern: crypto.createHash($ALGO).update($PW).digest(...)
              - pattern: createHash($ALGO).update($PW).digest(...)
              - pattern: crypto.createHash($ALGO).update($PW, ...).digest(...)
              - pattern: createHash($ALGO).update($PW, ...).digest(...)
          - metavariable-regex:
              metavariable: $ALGO
              regex: ^(["'])(?i)(md5|sha1|sha-1|sha224|sha256|sha-256|sha384|sha512|sha-512)\1$
          - metavariable-pattern:
              metavariable: $PW
              patterns:
                - pattern: $PWNAME
                - metavariable-regex:
                    metavariable: $PWNAME
                    regex: (?i)^.*(password|passwd|pwd)$
      # Two-step form:
      #   const h = crypto.createHash('sha256'); h.update(password); h.digest(...)
      - patterns:
          - pattern-either:
              - pattern: $H.update($PW)
              - pattern: $H.update($PW, ...)
          - metavariable-pattern:
              metavariable: $PW
              patterns:
                - pattern: $PWNAME
                - metavariable-regex:
                    metavariable: $PWNAME
                    regex: (?i)^.*(password|passwd|pwd)$
          - pattern-either:
              - pattern-inside: |
                  $H = crypto.createHash($ALGO);
                  ...
              - pattern-inside: |
                  $H = createHash($ALGO);
                  ...
          - metavariable-regex:
              metavariable: $ALGO
              regex: ^(["'])(?i)(md5|sha1|sha-1|sha224|sha256|sha-256|sha384|sha512|sha-512)\1$
    metadata:
      oauthlint-rule-id: AUTH-FLOW-006
      oauthlint-doc-url: https://oauthlint.dev/rules/flow-weak-password-hash
      category: security
      cwe: CWE-916
      owasp: A02:2021
      llm-prevalence: HIGH
      technology:
        - node:crypto
      references:
        - https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
        - https://cwe.mitre.org/data/definitions/916.html
