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.
- package/dist/schema.d.ts +60 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +12 -0
- package/dist/schema.js.map +1 -1
- package/package.json +1 -1
- package/rules/cookie/no-httponly.yml +5 -1
- package/rules/cookie/no-samesite.yml +5 -1
- package/rules/cookie/no-secure.yml +8 -5
- package/rules/cookie/samesite-none-insecure.yml +22 -0
- package/rules/flow/oauth-credential-in-log.yml +97 -0
- package/rules/flow/open-redirect.yml +11 -0
- package/rules/flow/ssrf.yml +19 -0
- package/rules/flow/timing-unsafe-compare.yml +19 -0
- package/rules/go/cookie/insecure.yml +15 -6
- package/rules/go/flow/oauth-credential-in-log.yml +90 -0
- package/rules/go/jwt/untrusted-verify-key.yml +68 -0
- package/rules/go/oauth/insecure-token-endpoint.yml +39 -0
- package/rules/go/oauth/ropc-grant.yml +64 -0
- package/rules/go/oauth/static-state.yml +39 -0
- package/rules/go/tls/insecure-skip-verify.yml +14 -3
- package/rules/go/tls/min-version.yml +12 -3
- package/rules/java/cors/credentialed-wildcard.yml +44 -0
- package/rules/java/jwt/none-algorithm.yml +37 -0
- package/rules/java/jwt/untrusted-verify-key.yml +54 -0
- package/rules/java/oauth/insecure-token-endpoint.yml +39 -0
- package/rules/java/oauth/ropc-grant.yml +45 -0
- package/rules/java/oauth/static-state.yml +38 -0
- package/rules/java/web/permit-all-actuator.yml +43 -0
- package/rules/jwt/ignore-expiration.yml +21 -11
- package/rules/jwt/untrusted-verify-key.yml +77 -0
- package/rules/oauth/insecure-token-endpoint.yml +41 -0
- package/rules/oauth/ropc-grant.yml +48 -0
- package/rules/oauth/static-state.yml +49 -0
- package/rules/py/flow/oauth-credential-in-log.yml +91 -0
- package/rules/py/jwt/untrusted-verify-key.yml +75 -0
- package/rules/py/oauth/insecure-token-endpoint.yml +40 -0
- package/rules/py/oauth/insecure-transport-env.yml +43 -0
- package/rules/py/oauth/ropc-grant.yml +58 -0
- package/rules/py/oauth/static-state.yml +45 -0
- package/rules/py/oauth/token-request-verify-disabled.yml +41 -0
- package/rules/rust/oauth/insecure-token-endpoint.yml +39 -0
- package/rules/rust/oauth/ropc-grant.yml +57 -0
- 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.
|
|
19
|
-
#
|
|
20
|
-
|
|
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.
|
|
18
|
-
# and `&tls.Config{...}`
|
|
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{
|
|
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
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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/
|