oauthlint-rules 0.2.6 → 0.3.1

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.
Files changed (43) hide show
  1. package/dist/schema.d.ts +60 -0
  2. package/dist/schema.d.ts.map +1 -1
  3. package/dist/schema.js +12 -0
  4. package/dist/schema.js.map +1 -1
  5. package/package.json +1 -1
  6. package/rules/cookie/no-httponly.yml +5 -1
  7. package/rules/cookie/no-samesite.yml +5 -1
  8. package/rules/cookie/no-secure.yml +8 -5
  9. package/rules/cookie/samesite-none-insecure.yml +22 -0
  10. package/rules/flow/oauth-credential-in-log.yml +97 -0
  11. package/rules/flow/open-redirect.yml +11 -0
  12. package/rules/flow/ssrf.yml +19 -0
  13. package/rules/flow/timing-unsafe-compare.yml +19 -0
  14. package/rules/go/cookie/insecure.yml +15 -6
  15. package/rules/go/flow/oauth-credential-in-log.yml +90 -0
  16. package/rules/go/jwt/untrusted-verify-key.yml +68 -0
  17. package/rules/go/oauth/insecure-token-endpoint.yml +39 -0
  18. package/rules/go/oauth/ropc-grant.yml +64 -0
  19. package/rules/go/oauth/static-state.yml +39 -0
  20. package/rules/go/tls/insecure-skip-verify.yml +14 -3
  21. package/rules/go/tls/min-version.yml +12 -3
  22. package/rules/java/cors/credentialed-wildcard.yml +44 -0
  23. package/rules/java/jwt/none-algorithm.yml +37 -0
  24. package/rules/java/jwt/untrusted-verify-key.yml +54 -0
  25. package/rules/java/oauth/insecure-token-endpoint.yml +39 -0
  26. package/rules/java/oauth/ropc-grant.yml +45 -0
  27. package/rules/java/oauth/static-state.yml +38 -0
  28. package/rules/java/web/permit-all-actuator.yml +43 -0
  29. package/rules/jwt/ignore-expiration.yml +21 -11
  30. package/rules/jwt/untrusted-verify-key.yml +77 -0
  31. package/rules/oauth/insecure-token-endpoint.yml +41 -0
  32. package/rules/oauth/ropc-grant.yml +48 -0
  33. package/rules/oauth/static-state.yml +49 -0
  34. package/rules/py/flow/oauth-credential-in-log.yml +91 -0
  35. package/rules/py/jwt/untrusted-verify-key.yml +75 -0
  36. package/rules/py/oauth/insecure-token-endpoint.yml +40 -0
  37. package/rules/py/oauth/insecure-transport-env.yml +43 -0
  38. package/rules/py/oauth/ropc-grant.yml +58 -0
  39. package/rules/py/oauth/static-state.yml +45 -0
  40. package/rules/py/oauth/token-request-verify-disabled.yml +41 -0
  41. package/rules/rust/oauth/insecure-token-endpoint.yml +39 -0
  42. package/rules/rust/oauth/ropc-grant.yml +57 -0
  43. package/rules/rust/oauth/static-state.yml +58 -0
@@ -0,0 +1,39 @@
1
+ rules:
2
+ - id: auth.go.oauth.insecure-token-endpoint
3
+ languages:
4
+ - go
5
+ severity: ERROR
6
+ message: |
7
+ An OAuth/OIDC endpoint is being contacted over cleartext `http://`.
8
+ Authorization codes, `client_secret`, access/refresh tokens, and the
9
+ `code_verifier` then travel unencrypted — a network attacker can read
10
+ or rewrite them and take over the flow.
11
+
12
+ RFC 6749 §3.1 / §10.9 require TLS for the authorization and token
13
+ endpoints. Use `https://` for every authorize, token, and userinfo
14
+ URL (including `oauth2.Endpoint{AuthURL, TokenURL}`).
15
+ `http://localhost` is fine for local development and is not flagged.
16
+ # A string literal that targets an OAuth/OIDC endpoint over http://.
17
+ # Required OAuth markers keep this precise: a generic http URL is NOT
18
+ # flagged, only one carrying an authorize/token request or an /oauth path.
19
+ # `https://` cannot match (the scheme is `http://` literally), and the
20
+ # localhost / loopback dev hosts are subtracted below.
21
+ patterns:
22
+ - pattern-regex: |-
23
+ "http://[^"\s]+(?:response_type=|client_id=|client_secret=|grant_type=|code_challenge=|/oauth2?/|/connect/token|/o/oauth2|/authorize|/oauth/token)[^"]*"
24
+ - pattern-not-regex: |-
25
+ http://(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])
26
+ metadata:
27
+ oauthlint-rule-id: AUTH-GO-OAUTH-002
28
+ oauthlint-doc-url: https://oauthlint.dev/rules/go-oauth-insecure-token-endpoint
29
+ category: security
30
+ cwe: CWE-319
31
+ owasp: A02:2021
32
+ llm-prevalence: MEDIUM
33
+ technology:
34
+ - oauth2
35
+ - oidc
36
+ references:
37
+ - https://datatracker.ietf.org/doc/html/rfc6749#section-3.1
38
+ - https://datatracker.ietf.org/doc/html/rfc6749#section-10.9
39
+ - https://cwe.mitre.org/data/definitions/319.html
@@ -0,0 +1,64 @@
1
+ rules:
2
+ - id: auth.go.oauth.ropc-grant
3
+ languages:
4
+ - go
5
+ severity: ERROR
6
+ message: |
7
+ OAuth token request uses the Resource Owner Password Credentials
8
+ grant (`grant_type=password`). The app collects the user's password
9
+ and replays it to the authorization server — exactly what OAuth was
10
+ designed to avoid. It cannot support federation, MFA, or step-up
11
+ auth, and any compromise of your service exposes raw user passwords.
12
+
13
+ The OAuth 2.0 Security BCP (RFC 9700 §2.4) forbids ROPC and OAuth 2.1
14
+ removes it entirely. Use the authorization-code flow with PKCE
15
+ (`grant_type=authorization_code`) for user login, or
16
+ `client_credentials` for machine-to-machine. With `golang.org/x/oauth2`,
17
+ avoid `Config.PasswordCredentialsToken` and use `AuthCodeURL` /
18
+ `Exchange` instead.
19
+ pattern-either:
20
+ # golang.org/x/oauth2: Config.PasswordCredentialsToken(ctx, user, pass)
21
+ # is the ROPC helper — its only purpose is the password grant.
22
+ - pattern: $C.PasswordCredentialsToken(...)
23
+ # url.Values builders: v.Set("grant_type", "password") /
24
+ # v.Add("grant_type", "password"). The value is bounded so
25
+ # `password_reset` and friends are not matched.
26
+ - pattern-regex: |-
27
+ "grant_type"\s*,\s*"password"
28
+ # url.Values / map composite literal: {"grant_type": {"password"}} or
29
+ # {"grant_type": "password"}.
30
+ - pattern-regex: |-
31
+ "grant_type"\s*:\s*\[?\s*\{?\s*"password"
32
+ # URL-encoded request body string: "grant_type=password&username=…"
33
+ # (double-quoted or raw backtick string). The trailing class bounds the
34
+ # value so only the password grant matches.
35
+ - pattern-regex: |-
36
+ [?&"`]grant_type=password(?:[&"`\s\\]|$)
37
+ # We flag an application sending ROPC, not the example/test code or vendored
38
+ # client libraries that legitimately implement the grant. Excluding these
39
+ # trees keeps the signal on first-party application code; a vendored copy of
40
+ # `golang.org/x/oauth2`, for instance, must not trip this rule.
41
+ paths:
42
+ exclude:
43
+ - "**/test/**"
44
+ - "**/*_test.go"
45
+ - "**/*_test.*"
46
+ - "**/example/**"
47
+ - "**/examples/**"
48
+ - "**/mock*/**"
49
+ - "**/testdata/**"
50
+ - "**/vendor/**"
51
+ - "**/node_modules/**"
52
+ metadata:
53
+ oauthlint-rule-id: AUTH-GO-OAUTH-001
54
+ oauthlint-doc-url: https://oauthlint.dev/rules/go-oauth-ropc-grant
55
+ category: security
56
+ cwe: CWE-522
57
+ owasp: API2:2023
58
+ llm-prevalence: MEDIUM
59
+ technology:
60
+ - oauth2
61
+ references:
62
+ - https://datatracker.ietf.org/doc/html/rfc9700#section-2.4
63
+ - https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1#section-2.4
64
+ - https://cwe.mitre.org/data/definitions/522.html
@@ -0,0 +1,39 @@
1
+ rules:
2
+ - id: auth.go.oauth.static-state
3
+ languages:
4
+ - go
5
+ severity: WARNING
6
+ message: |
7
+ OAuth authorization request is built with a hardcoded, constant `state`
8
+ value. A static `state` provides ZERO CSRF protection — the whole point
9
+ is an unguessable, per-request value that you store and then compare on
10
+ the callback. A literal that ships in your source is known to everyone
11
+ and identical on every request, so an attacker can forge a matching
12
+ callback.
13
+
14
+ Generate `state` fresh per request from a CSPRNG (e.g.
15
+ `crypto/rand` -> `base64.URLEncoding.EncodeToString(b)`), persist it in
16
+ the session/cookie, and verify it when the provider redirects back. With
17
+ `golang.org/x/oauth2`, pass that random value as the first argument to
18
+ `Config.AuthCodeURL(state, ...)`.
19
+ # `Config.AuthCodeURL(state, ...)` is the golang.org/x/oauth2 authorize-URL
20
+ # builder; its first argument is the CSRF `state`. A string LITERAL there is
21
+ # constant on every request and cannot provide CSRF protection. A
22
+ # per-request value is a variable (not a quoted literal) and does not match.
23
+ # The empty-string form is excluded — that is a missing state, covered
24
+ # elsewhere — so this fires only on a genuinely hardcoded value.
25
+ patterns:
26
+ - pattern: $C.AuthCodeURL("...", ...)
27
+ - pattern-not: $C.AuthCodeURL("", ...)
28
+ metadata:
29
+ oauthlint-rule-id: AUTH-GO-OAUTH-003
30
+ oauthlint-doc-url: https://oauthlint.dev/rules/go-oauth-static-state
31
+ category: security
32
+ cwe: CWE-330
33
+ owasp: API1:2023
34
+ llm-prevalence: MEDIUM
35
+ technology:
36
+ - oauth2
37
+ references:
38
+ - https://datatracker.ietf.org/doc/html/rfc6749#section-10.12
39
+ - https://cwe.mitre.org/data/definitions/330.html
@@ -15,9 +15,20 @@ rules:
15
15
  To trust a private CA in development, set `RootCAs` to a `*x509.CertPool`
16
16
  loaded with that CA instead.
17
17
  # Matches only the literal `true`. `InsecureSkipVerify: false` and the
18
- # field's absence are not flagged. Works for `tls.Config{...}` and
19
- # `&tls.Config{...}` (the composite literal matches inside the address-of).
20
- pattern: 'tls.Config{..., InsecureSkipVerify: true, ...}'
18
+ # field's absence are not flagged. The `pattern-inside` keeps detection
19
+ # scoped to a `tls.Config{...}` (and `&tls.Config{...}`) composite literal,
20
+ # so an unrelated struct that happens to expose an `InsecureSkipVerify`
21
+ # field is never matched. Narrowing the matched node to the field itself
22
+ # (rather than the whole literal) is what lets the autofix below rewrite
23
+ # just that field and leave the surrounding `...` fields untouched.
24
+ patterns:
25
+ - pattern-inside: 'tls.Config{...}'
26
+ - pattern: 'InsecureSkipVerify: true'
27
+ # Safe, deterministic autofix: flip the boolean to `false`, the secure
28
+ # default that restores certificate verification and fully resolves the
29
+ # finding. Only the `true` literal changes; every other field in the
30
+ # `tls.Config` is preserved verbatim.
31
+ fix: 'InsecureSkipVerify: false'
21
32
  metadata:
22
33
  oauthlint-rule-id: AUTH-GO-TLS-001
23
34
  oauthlint-doc-url: https://oauthlint.dev/rules/go-tls-insecure-skip-verify
@@ -14,13 +14,22 @@ rules:
14
14
  Set `MinVersion` to at least `tls.VersionTLS12`, and ideally
15
15
  `tls.VersionTLS13`, so the handshake refuses obsolete protocols.
16
16
  # Matches the obsolete constants only. `tls.VersionTLS12` and
17
- # `tls.VersionTLS13` are not flagged. Works for both `tls.Config{...}`
18
- # and `&tls.Config{...}` (the composite literal matches inside `&`).
17
+ # `tls.VersionTLS13` are not flagged. The `pattern-inside` keeps detection
18
+ # scoped to a `tls.Config{...}` (and `&tls.Config{...}`) composite literal;
19
+ # narrowing the matched node to the `MinVersion` field itself is what lets
20
+ # the autofix below rewrite just that field and leave neighbours untouched.
19
21
  patterns:
20
- - pattern: 'tls.Config{..., MinVersion: $V, ...}'
22
+ - pattern-inside: 'tls.Config{...}'
23
+ - pattern: 'MinVersion: $V'
21
24
  - metavariable-regex:
22
25
  metavariable: $V
23
26
  regex: ^tls\.(VersionSSL30|VersionTLS10|VersionTLS11)$
27
+ # Safe, deterministic autofix: raise the floor to `tls.VersionTLS12`, the
28
+ # lowest version RFC 8996 still permits. This is the minimal correct upgrade
29
+ # — it fully resolves the finding (TLS 1.2+ is no longer flagged) without
30
+ # presuming the stricter `tls.VersionTLS13`, which can break interop with
31
+ # peers that don't yet speak 1.3.
32
+ fix: 'MinVersion: tls.VersionTLS12'
24
33
  metadata:
25
34
  oauthlint-rule-id: AUTH-GO-TLS-002
26
35
  oauthlint-doc-url: https://oauthlint.dev/rules/go-tls-min-version
@@ -0,0 +1,44 @@
1
+ rules:
2
+ - id: auth.java.cors.credentialed-wildcard
3
+ languages:
4
+ - java
5
+ severity: ERROR
6
+ message: |
7
+ CORS is configured to allow any origin together with credentials. A
8
+ wildcard origin combined with `allowCredentials = true` tells the browser
9
+ to send the victim's cookies and authorization headers to a response that
10
+ any site can read — a cross-origin account-takeover primitive (CWE-942).
11
+ Because browsers reject a literal `*` when credentials are allowed,
12
+ `addAllowedOriginPattern("*")` exists specifically to re-enable this unsafe
13
+ combination, so its very use is the smell.
14
+
15
+ Never pair a wildcard origin with credentials. List the exact trusted
16
+ origins — `setAllowedOrigins(List.of("https://app.example.com"))` with
17
+ `setAllowCredentials(true)` — or drop credentials if you genuinely need a
18
+ public, anonymous API.
19
+ # Annotation form: @CrossOrigin with a wildcard origin AND allowCredentials
20
+ # = "true" (both attribute orderings, `origins` and the `value` alias).
21
+ # Programmatic form: the origin-PATTERN wildcard API, which only exists to
22
+ # allow `*` with credentials — flagged on its own. The plain
23
+ # `addAllowedOrigin`/`setAllowedOrigins` wildcards are covered by
24
+ # auth.java.cors.allow-all and are intentionally not duplicated here.
25
+ pattern-either:
26
+ - pattern: '@CrossOrigin(..., origins = "*", ..., allowCredentials = "true", ...)'
27
+ - pattern: '@CrossOrigin(..., allowCredentials = "true", ..., origins = "*", ...)'
28
+ - pattern: '@CrossOrigin(..., value = "*", ..., allowCredentials = "true", ...)'
29
+ - pattern: '@CrossOrigin(..., allowCredentials = "true", ..., value = "*", ...)'
30
+ - pattern: $C.addAllowedOriginPattern("*")
31
+ - pattern: $C.setAllowedOriginPatterns($L(..., "*", ...))
32
+ metadata:
33
+ oauthlint-rule-id: AUTH-JAVA-CORS-002
34
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-cors-credentialed-wildcard
35
+ category: security
36
+ cwe: CWE-942
37
+ owasp: A05:2021
38
+ llm-prevalence: MEDIUM
39
+ technology:
40
+ - spring-web
41
+ references:
42
+ - https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html
43
+ - https://portswigger.net/web-security/cors
44
+ - https://cwe.mitre.org/data/definitions/942.html
@@ -0,0 +1,37 @@
1
+ rules:
2
+ - id: auth.java.jwt.none-algorithm
3
+ languages:
4
+ - java
5
+ severity: ERROR
6
+ message: |
7
+ A JWT is created or verified with the `none` algorithm, which means there
8
+ is no signature at all. An `alg=none` token can be forged by anyone —
9
+ changing the subject, roles, or expiry costs nothing because there is no
10
+ signature to verify (CWE-347). This is a common AI-generated mistake: the
11
+ "no signature" algorithm is reached for during prototyping or testing and
12
+ never swapped for a real signing key.
13
+
14
+ Always sign and verify with a real algorithm. With jjwt use
15
+ `Jwts.builder()...signWith(key)` and a keyed parser; with Auth0 java-jwt
16
+ use `Algorithm.HMAC256(secret)` / `Algorithm.RSA256(...)` instead of
17
+ `Algorithm.none()`.
18
+ # jjwt (io.jsonwebtoken): the SignatureAlgorithm.NONE enum constant.
19
+ # Auth0 java-jwt (com.auth0.jwt): the Algorithm.none() factory.
20
+ pattern-either:
21
+ - pattern: io.jsonwebtoken.SignatureAlgorithm.NONE
22
+ - pattern: SignatureAlgorithm.NONE
23
+ - pattern: com.auth0.jwt.algorithms.Algorithm.none()
24
+ - pattern: Algorithm.none()
25
+ metadata:
26
+ oauthlint-rule-id: AUTH-JAVA-JWT-004
27
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-jwt-none-algorithm
28
+ category: security
29
+ cwe: CWE-347
30
+ owasp: API2:2023
31
+ llm-prevalence: MEDIUM
32
+ technology:
33
+ - jjwt
34
+ - java-jwt
35
+ references:
36
+ - https://datatracker.ietf.org/doc/html/rfc8725#section-2.1
37
+ - https://cwe.mitre.org/data/definitions/347.html
@@ -0,0 +1,54 @@
1
+ rules:
2
+ - id: auth.java.jwt.untrusted-verify-key
3
+ languages:
4
+ - java
5
+ severity: ERROR
6
+ message: |
7
+ Untrusted request input flows into the JWT verification key. When the
8
+ attacker controls the key, they sign their own forged token and supply the
9
+ matching key, so every token "verifies" — a complete authentication bypass
10
+ (CWE-347, Improper Verification of Cryptographic Signature).
11
+
12
+ The verification key must be fixed server-side. Resolve it from trusted
13
+ configuration, a keystore, or a vetted key set keyed by a validated `kid`
14
+ — never from `request.getParameter(...)` / `request.getHeader(...)` or a
15
+ `@RequestParam` / `@RequestHeader` value.
16
+ # Taint mode with the sink FOCUSED on the key argument — never the token. The
17
+ # token is supposed to come from the request, so focusing on it would
18
+ # false-positive on every correct call; focusing on the key fires only when
19
+ # the attacker controls verification itself. Sources are the servlet request
20
+ # accessors that return raw client input. A key fetched from a keystore
21
+ # (`KeyStore.getKey(...)`) clears the taint.
22
+ mode: taint
23
+ pattern-sources:
24
+ - pattern: $REQ.getParameter(...)
25
+ - pattern: $REQ.getParameterValues(...)
26
+ - pattern: $REQ.getHeader(...)
27
+ - pattern: $REQ.getHeaders(...)
28
+ pattern-sanitizers:
29
+ - pattern: $KS.getKey(...)
30
+ pattern-sinks:
31
+ # jjwt: the signing/verification key passed to the parser.
32
+ - patterns:
33
+ - pattern-either:
34
+ - pattern: $P.setSigningKey($SINK)
35
+ - pattern: $P.verifyWith($SINK)
36
+ - focus-metavariable: $SINK
37
+ # nimbus-jose-jwt: the HMAC secret handed to the verifier.
38
+ - patterns:
39
+ - pattern: new MACVerifier($SINK)
40
+ - focus-metavariable: $SINK
41
+ metadata:
42
+ oauthlint-rule-id: AUTH-JAVA-JWT-003
43
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-jwt-untrusted-verify-key
44
+ category: security
45
+ cwe: CWE-347
46
+ owasp: API2:2023
47
+ llm-prevalence: LOW
48
+ technology:
49
+ - jjwt
50
+ - nimbus-jose-jwt
51
+ references:
52
+ - https://cwe.mitre.org/data/definitions/347.html
53
+ - https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
54
+ - https://owasp.org/www-project-api-security/
@@ -0,0 +1,39 @@
1
+ rules:
2
+ - id: auth.java.oauth.insecure-token-endpoint
3
+ languages:
4
+ - java
5
+ severity: ERROR
6
+ message: |
7
+ An OAuth/OIDC endpoint is being contacted over cleartext `http://`.
8
+ Authorization codes, `client_secret`, access/refresh tokens, and the
9
+ `code_verifier` then travel unencrypted — a network attacker can read or
10
+ rewrite them and take over the flow (CWE-319).
11
+
12
+ RFC 6749 §3.1 / §10.9 require TLS for the authorization and token
13
+ endpoints. Use `https://` for every authorize, token, and userinfo URL.
14
+ `http://localhost` and loopback addresses are fine for local development
15
+ and are not flagged.
16
+ # A string literal that targets an OAuth/OIDC endpoint over http://. Required
17
+ # OAuth markers keep this precise: a generic http URL is NOT flagged, only
18
+ # one carrying an authorize/token request or an /oauth path. `https://`
19
+ # cannot match (the scheme is literal), and localhost / loopback dev hosts
20
+ # are subtracted.
21
+ patterns:
22
+ - pattern-regex: |-
23
+ "http://[^"\s]+(?:response_type=|client_id=|client_secret=|grant_type=|code_challenge=|/oauth2?/|/connect/token|/o/oauth2|/authorize|/oauth/token)[^"]*"
24
+ - pattern-not-regex: |-
25
+ http://(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])
26
+ metadata:
27
+ oauthlint-rule-id: AUTH-JAVA-OAUTH-002
28
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-oauth-insecure-token-endpoint
29
+ category: security
30
+ cwe: CWE-319
31
+ owasp: A02:2021
32
+ llm-prevalence: MEDIUM
33
+ technology:
34
+ - oauth2
35
+ - oidc
36
+ references:
37
+ - https://datatracker.ietf.org/doc/html/rfc6749#section-3.1
38
+ - https://datatracker.ietf.org/doc/html/rfc6749#section-10.9
39
+ - https://cwe.mitre.org/data/definitions/319.html
@@ -0,0 +1,45 @@
1
+ rules:
2
+ - id: auth.java.oauth.ropc-grant
3
+ languages:
4
+ - java
5
+ severity: ERROR
6
+ message: |
7
+ OAuth token request uses the Resource Owner Password Credentials grant
8
+ (`grant_type=password`). The application collects the user's password and
9
+ replays it to the authorization server — exactly what OAuth was designed
10
+ to avoid. It cannot support federation, MFA, or step-up auth, and any
11
+ compromise of your service exposes raw user passwords (CWE-522).
12
+
13
+ The OAuth 2.0 Security BCP (RFC 9700 §2.4) forbids ROPC and OAuth 2.1
14
+ removes it entirely. Use the authorization-code flow with PKCE
15
+ (`grant_type=authorization_code`) for user login, or `client_credentials`
16
+ for machine-to-machine.
17
+ # Match `grant_type=password` only where it is a token-request body, so an
18
+ # unrelated `grant_type` variable is never flagged.
19
+ # - URL-encoded form string body: "grant_type=password&username=…". The
20
+ # value is bounded so `grant_type=password_reset` is NOT matched.
21
+ # - Form builders that pair the key and value as two string-literal args:
22
+ # OkHttp `FormBody.Builder().add("grant_type", "password")`, Spring
23
+ # `BodyInserters.fromFormData("grant_type", "password")` /
24
+ # `MultiValueMap.add(...)`, Apache `new BasicNameValuePair(...)`.
25
+ pattern-either:
26
+ - pattern-regex: |-
27
+ [?&"']grant_type=password(?:["'&\s]|$)
28
+ - pattern-regex: |-
29
+ ["']grant_type["']\s*,\s*["']password["']
30
+ metadata:
31
+ oauthlint-rule-id: AUTH-JAVA-OAUTH-001
32
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-oauth-ropc-grant
33
+ category: security
34
+ cwe: CWE-522
35
+ owasp: API2:2023
36
+ llm-prevalence: MEDIUM
37
+ technology:
38
+ - oauth2
39
+ - spring-web
40
+ - okhttp
41
+ - apache-httpclient
42
+ references:
43
+ - https://datatracker.ietf.org/doc/html/rfc9700#section-2.4
44
+ - https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1#section-2.4
45
+ - https://cwe.mitre.org/data/definitions/522.html
@@ -0,0 +1,38 @@
1
+ rules:
2
+ - id: auth.java.oauth.static-state
3
+ languages:
4
+ - java
5
+ severity: WARNING
6
+ message: |
7
+ OAuth authorization request sends a hardcoded, constant `state` value. A
8
+ static `state` provides ZERO CSRF protection — the whole point is an
9
+ unguessable, per-request value that you store and then compare on the
10
+ callback. A literal that ships in your source is known to everyone and
11
+ identical on every request, so an attacker can forge a matching callback
12
+ (CWE-330).
13
+
14
+ Generate `state` fresh per request from a CSPRNG (`new SecureRandom()` /
15
+ `Base64.getUrlEncoder().encodeToString(randomBytes)`), persist it in the
16
+ session, and verify it when the provider redirects back.
17
+ # An inline authorize URL string literal that carries BOTH a `response_type`
18
+ # (so we know it is an authorize request, not some unrelated `state` field)
19
+ # AND a constant `state=` value. A per-request value is built by
20
+ # concatenation — `"…&state=" + state` — whose literal ends right after
21
+ # `state=`, so the value-character class below cannot match it.
22
+ patterns:
23
+ - pattern-regex: |-
24
+ "https?://[^"\s]+\?[^"]*response_type=[^"]*"
25
+ - pattern-regex: |-
26
+ "https?://[^"\s]+\?[^"]*state=[A-Za-z0-9._~%-]+[^"]*"
27
+ metadata:
28
+ oauthlint-rule-id: AUTH-JAVA-OAUTH-003
29
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-oauth-static-state
30
+ category: security
31
+ cwe: CWE-330
32
+ owasp: API1:2023
33
+ llm-prevalence: MEDIUM
34
+ technology:
35
+ - oauth2
36
+ references:
37
+ - https://datatracker.ietf.org/doc/html/rfc6749#section-10.12
38
+ - https://cwe.mitre.org/data/definitions/330.html
@@ -0,0 +1,43 @@
1
+ rules:
2
+ - id: auth.java.web.permit-all-actuator
3
+ languages:
4
+ - java
5
+ severity: WARNING
6
+ message: |
7
+ Spring Security grants `permitAll()` to a sensitive management path. Spring
8
+ Boot Actuator and similar diagnostic endpoints expose health, environment,
9
+ configuration, thread dumps, and heap dumps — opening them to anonymous
10
+ access leaks secrets and internal state and can enable remote code
11
+ execution (CWE-862, broken access control). This is a common AI-generated
12
+ mistake: the management path is opened to "fix" a probe or scrape and the
13
+ intended authentication is never added.
14
+
15
+ Require authentication for management endpoints — e.g.
16
+ `requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")` — and
17
+ expose only `/actuator/health` (and `/info`) publicly if you must.
18
+ # Flag permitAll() on a matcher whose path literal targets a management /
19
+ # diagnostics surface (actuator, jolokia, heap/thread dumps, internal).
20
+ # Ordinary application matchers (e.g. "/public/**", "/login") are not
21
+ # flagged. Covers the Spring Security 6 `requestMatchers` and the legacy
22
+ # `antMatchers` / `mvcMatchers`.
23
+ patterns:
24
+ - pattern-either:
25
+ - pattern: $X.requestMatchers($P).permitAll()
26
+ - pattern: $X.antMatchers($P).permitAll()
27
+ - pattern: $X.mvcMatchers($P).permitAll()
28
+ - metavariable-regex:
29
+ metavariable: $P
30
+ regex: (?s).*/(?:actuator|jolokia|heapdump|threaddump|internal)\b.*
31
+ metadata:
32
+ oauthlint-rule-id: AUTH-JAVA-WEB-004
33
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-web-permit-all-actuator
34
+ category: security
35
+ cwe: CWE-862
36
+ owasp: A01:2021
37
+ llm-prevalence: MEDIUM
38
+ technology:
39
+ - spring-security
40
+ - spring-boot-actuator
41
+ references:
42
+ - https://docs.spring.io/spring-boot/reference/actuator/endpoints.html
43
+ - https://cwe.mitre.org/data/definitions/862.html
@@ -18,17 +18,27 @@ rules:
18
18
  # pass `ignoreExpiration: true`; `verify(...)` without that option, and
19
19
  # `ignoreExpiration: false`, are never matched. We also support the
20
20
  # destructured import `import { verify } from 'jsonwebtoken'`, scoped to that
21
- # import so an unrelated `verify()` is safe. The options object is matched
22
- # with `{ ..., ignoreExpiration: true, ... }` so the flag is caught
23
- # regardless of which other options (algorithms, maxAge, audience, ...)
24
- # appear alongside it.
25
- pattern-either:
26
- - pattern: 'jwt.verify($T, $K, { ..., ignoreExpiration: true, ... })'
27
- - patterns:
28
- - pattern-inside: |
29
- import { ..., verify, ... } from 'jsonwebtoken'
30
- ...
31
- - pattern: 'verify($T, $K, { ..., ignoreExpiration: true, ... })'
21
+ # import so an unrelated `verify()` is safe. The verify call is matched with
22
+ # `pattern-inside` (any options object), and the matched node is narrowed to
23
+ # the `ignoreExpiration: true` property itself this keeps detection
24
+ # identical (the flag is still caught regardless of which other options
25
+ # appear alongside it) while letting the autofix below rewrite just that
26
+ # property and leave every sibling option untouched.
27
+ patterns:
28
+ - pattern-either:
29
+ - pattern-inside: 'jwt.verify($T, $K, {...})'
30
+ - patterns:
31
+ - pattern-inside: |
32
+ import { ..., verify, ... } from 'jsonwebtoken'
33
+ ...
34
+ - pattern-inside: 'verify($T, $K, {...})'
35
+ - pattern: 'ignoreExpiration: true'
36
+ # Safe, deterministic autofix: flip the disabled check back on. `false` is the
37
+ # secure value — it is the library default (expiry enforced) and the exact
38
+ # value the rule treats as compliant — so the rewrite fully resolves the
39
+ # finding. The match is narrowed to the property, so `ignoreExpiration: true`
40
+ # becomes `ignoreExpiration: false` with the rest of the options object intact.
41
+ fix: 'ignoreExpiration: false'
32
42
  metadata:
33
43
  oauthlint-rule-id: AUTH-JWT-011
34
44
  oauthlint-doc-url: https://oauthlint.dev/rules/jwt-ignore-expiration
@@ -0,0 +1,77 @@
1
+ rules:
2
+ - id: auth.jwt.untrusted-verify-key
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: ERROR
7
+ message: |
8
+ Untrusted request input flows into the verification key or the
9
+ `algorithms` allowlist of `jwt.verify(...)`. When the attacker controls
10
+ the key, they sign their own forged token and supply the matching key,
11
+ so every token "verifies" — a complete authentication bypass. When the
12
+ attacker controls `algorithms`, they can downgrade verification (e.g. to
13
+ `HS256` against a public key, or to `none` on older libraries) and defeat
14
+ the signature check (CWE-347, Improper Verification of Cryptographic
15
+ Signature).
16
+
17
+ The verification key and the accepted algorithms must be fixed
18
+ server-side. Pin `algorithms` to a constant allowlist
19
+ (`{ algorithms: ['RS256'] }`) and resolve the key from trusted
20
+ configuration or a vetted key set keyed by a validated `kid` — never from
21
+ `req.query` / `req.body` / `req.params` / `req.headers`.
22
+ # Taint mode with the sink FOCUSED on the key / algorithms argument — never
23
+ # the token. The token is supposed to come from the request, so focusing on
24
+ # it would false-positive on every correct call; focusing on the key and the
25
+ # `algorithms` value fires only when the attacker controls verification
26
+ # itself. Routing a candidate algorithm/key through an allow-list /
27
+ # validation helper clears the taint. Distinct from
28
+ # auth.jwt.algorithm-confusion (HS* + a PEM public key) and
29
+ # auth.jwt.no-algorithms-allowlist (a MISSING allowlist): this is about a
30
+ # request-CONTROLLED key or algorithm.
31
+ mode: taint
32
+ pattern-sources:
33
+ - pattern: $REQ.query
34
+ - pattern: $REQ.params
35
+ - pattern: $REQ.body
36
+ - pattern: $REQ.headers
37
+ - pattern: $REQ.query.$X
38
+ - pattern: $REQ.params.$X
39
+ - pattern: $REQ.body.$X
40
+ - pattern: $REQ.headers.$X
41
+ - pattern: $REQ.query[$K]
42
+ - pattern: $REQ.params[$K]
43
+ - pattern: $REQ.body[$K]
44
+ - pattern: $REQ.headers[$K]
45
+ pattern-sanitizers:
46
+ # A candidate algorithm or key vetted against an allow-list / validator is
47
+ # no longer attacker-controlled when it reaches verify().
48
+ - pattern: isAllowedAlgorithm(...)
49
+ - pattern: validateAlgorithm(...)
50
+ - pattern: isAllowedKey(...)
51
+ - pattern: $ALLOW.includes(...)
52
+ - pattern: $ALLOW.has(...)
53
+ - pattern: $ALLOW.indexOf(...)
54
+ pattern-sinks:
55
+ # Request input used as the verification key (2- and 3-argument forms).
56
+ - patterns:
57
+ - pattern-either:
58
+ - pattern: jwt.verify($T, $SINK)
59
+ - pattern: jwt.verify($T, $SINK, $OPTS)
60
+ - focus-metavariable: $SINK
61
+ # Request input used as the accepted `algorithms`.
62
+ - patterns:
63
+ - pattern: 'jwt.verify($T, $KEY, {..., algorithms: $SINK, ...})'
64
+ - focus-metavariable: $SINK
65
+ metadata:
66
+ oauthlint-rule-id: AUTH-JWT-012
67
+ oauthlint-doc-url: https://oauthlint.dev/rules/jwt-untrusted-verify-key
68
+ category: security
69
+ cwe: CWE-347
70
+ owasp: API2:2023
71
+ llm-prevalence: LOW
72
+ technology:
73
+ - jsonwebtoken
74
+ references:
75
+ - https://cwe.mitre.org/data/definitions/347.html
76
+ - https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
77
+ - https://owasp.org/www-project-api-security/