rules:
  - id: auth.ruby.secret.hardcoded-jwt-secret
    languages:
      - ruby
    severity: ERROR
    message: |
      The HMAC key passed to `JWT.encode` / `JWT.decode` is a hard-coded string
      literal. This key signs and verifies every token: committed to source
      control it is one search away from compromise, letting an attacker forge a
      token for any user or role (CWE-798). AI-generated samples inline the
      secret to make the snippet "just work" and it ships unchanged.

      Read the key from the environment or Rails credentials instead:
        JWT.encode(payload, ENV.fetch('JWT_SECRET'), 'HS256')
        JWT.decode(token, Rails.application.credentials.jwt_secret, true, { algorithm: 'HS256' })
    # $K must be a quoted string literal of a plausible secret length. ENV[...],
    # ENV.fetch(...), Rails.application.credentials.* and local variables are
    # not string literals, so they never match; the length floor drops trivial
    # placeholders and the empty string (the `none`/no-key forms live elsewhere).
    patterns:
      - pattern-either:
          - pattern: JWT.encode($P, $K, $ALG)
          - pattern: JWT.decode($T, $K, $V)
          - pattern: JWT.decode($T, $K, $V, ...)
      - metavariable-regex:
          metavariable: $K
          regex: "^[\"'][^\"']{5,}[\"']$"
    paths:
      exclude:
        - "**/spec/**"
        - "**/test/**"
        - "**/*_spec.rb"
        - "**/*_test.rb"
        - "**/features/**"
    metadata:
      oauthlint-rule-id: AUTH-RUBY-SECRET-001
      oauthlint-doc-url: https://oauthlint.dev/rules/ruby-secret-hardcoded-jwt-secret
      category: security
      cwe: CWE-798
      owasp: API2:2023
      llm-prevalence: HIGH
      technology:
        - ruby-jwt
      references:
        - https://github.com/jwt/ruby-jwt
        - https://cwe.mitre.org/data/definitions/798.html
