rules:
  - id: auth.py.jwt.hardcoded-secret
    languages:
      - python
    severity: ERROR
    message: |
      A JWT signing/verification key is hardcoded as a string literal in the
      call to PyJWT. Anyone who can read the source or git history can forge or
      tamper with tokens, which is a complete authentication bypass.

      Load the secret from the environment or a secret manager instead, e.g.
      `key = os.environ["JWT_SECRET"]` and `jwt.encode(payload, key, ...)`.
      Never commit signing keys to source control.
    # Scoped to PyJWT's `jwt.encode(...)` / `jwt.decode(...)`. The key argument
    # is the second positional argument; we only flag it when it is a string
    # literal (metavariable-regex requires it to start/end with a quote), so
    # `os.environ[...]`, `settings.SECRET_KEY`, and plain variables are ignored.
    pattern-either:
      - patterns:
          - pattern: jwt.encode($PAYLOAD, $KEY, ...)
          - metavariable-regex:
              metavariable: $KEY
              regex: ^["'].*["']$
      - patterns:
          - pattern: jwt.decode($TOKEN, $KEY, ...)
          - metavariable-regex:
              metavariable: $KEY
              regex: ^["'].*["']$
    metadata:
      oauthlint-rule-id: AUTH-PY-JWT-003
      oauthlint-doc-url: https://oauthlint.dev/rules/py-jwt-hardcoded-secret
      category: security
      cwe: CWE-798
      owasp: API2:2023
      llm-prevalence: HIGH
      technology:
        - pyjwt
      references:
        - https://pyjwt.readthedocs.io/en/stable/api.html#jwt.encode
        - https://cwe.mitre.org/data/definitions/798.html
