oauthlint-rules 0.2.6 → 0.3.0
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/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/flow/oauth-credential-in-log.yml +97 -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 +49 -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 +43 -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 +50 -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 +40 -0
- package/rules/rust/oauth/static-state.yml +38 -0
package/package.json
CHANGED
|
@@ -39,7 +39,11 @@ rules:
|
|
|
39
39
|
- metavariable-regex:
|
|
40
40
|
metavariable: $NAME
|
|
41
41
|
regex: ^(['"])(?:[a-zA-Z0-9_-]*(?:session|sid|sess|auth|token|jwt|refresh|access)[a-zA-Z0-9_-]*)\1$
|
|
42
|
-
|
|
42
|
+
# No autofix. As with `auth.cookie.no-secure`, a single rule-level `fix:`
|
|
43
|
+
# cannot cover all three matched shapes safely: a spread template corrupts
|
|
44
|
+
# the 2-arg form (unbound `$OPTS` is emitted literally) and does not resolve
|
|
45
|
+
# an explicit `httpOnly: false`. Inserting a missing key is not a clean
|
|
46
|
+
# literal replacement, so this rule deliberately ships no `fix:`.
|
|
43
47
|
metadata:
|
|
44
48
|
oauthlint-rule-id: AUTH-COOKIE-002
|
|
45
49
|
oauthlint-doc-url: https://oauthlint.dev/rules/cookie-no-httponly
|
|
@@ -41,7 +41,11 @@ rules:
|
|
|
41
41
|
- pattern: "{..., sameSite: 'None', ...}"
|
|
42
42
|
- pattern: '{..., sameSite: "None", ...}'
|
|
43
43
|
- pattern-not: '{..., secure: true, ...}'
|
|
44
|
-
|
|
44
|
+
# No autofix. The correct `SameSite` value is context-dependent — `Strict`
|
|
45
|
+
# suits most auth flows but breaks OAuth callbacks, which need `Lax`, while a
|
|
46
|
+
# cookie deliberately set cross-site needs `None` plus `Secure`. The tool
|
|
47
|
+
# cannot know which, and a spread template would also corrupt the 2-arg form
|
|
48
|
+
# (unbound `$OPTS`). So this rule deliberately ships no `fix:`.
|
|
45
49
|
metadata:
|
|
46
50
|
oauthlint-rule-id: AUTH-COOKIE-003
|
|
47
51
|
oauthlint-doc-url: https://oauthlint.dev/rules/cookie-no-samesite
|
|
@@ -39,11 +39,14 @@ rules:
|
|
|
39
39
|
- metavariable-regex:
|
|
40
40
|
metavariable: $NAME
|
|
41
41
|
regex: ^(['"])(?:[a-zA-Z0-9_-]*(?:session|sid|sess|auth|token|jwt|refresh|access)[a-zA-Z0-9_-]*)\1$
|
|
42
|
-
#
|
|
43
|
-
# options object
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
42
|
+
# No autofix. A single rule-level `fix:` cannot be made safe here: this rule
|
|
43
|
+
# matches three shapes — an options object missing `secure`, the 2-arg form
|
|
44
|
+
# with no options object at all, and an explicit `secure: false`. A spread
|
|
45
|
+
# template like `{ ...$OPTS, secure: true }` corrupts the 2-arg form (`$OPTS`
|
|
46
|
+
# is unbound, so Semgrep emits the literal text `$OPTS`) and leaves an
|
|
47
|
+
# explicit `secure: false` key in place (the finding re-fires). Inserting a
|
|
48
|
+
# brand-new argument/key is not a clean literal replacement, so we ship no
|
|
49
|
+
# `fix:` rather than a rewrite that can break source.
|
|
47
50
|
metadata:
|
|
48
51
|
oauthlint-rule-id: AUTH-COOKIE-001
|
|
49
52
|
oauthlint-doc-url: https://oauthlint.dev/rules/cookie-no-secure
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.flow.oauth-credential-in-log
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: ERROR
|
|
7
|
+
message: |
|
|
8
|
+
An OAuth/OIDC credential taken from the request — an authorization
|
|
9
|
+
`code`, an `access_token` / `refresh_token` / `id_token`, a bearer
|
|
10
|
+
`token`, a `client_secret`, or the raw `Authorization` header — flows
|
|
11
|
+
into a logging call (`console.*` or `logger.*`). Logs are written to
|
|
12
|
+
files, shipped to aggregators (Datadog, Splunk, CloudWatch) and read by
|
|
13
|
+
people and systems that should never see live credentials. A leaked
|
|
14
|
+
authorization code or token can be replayed to impersonate the user or
|
|
15
|
+
complete the OAuth exchange (CWE-532).
|
|
16
|
+
|
|
17
|
+
Never log the raw credential. Redact or mask it before logging
|
|
18
|
+
(`token.slice(0, 4) + '…'`), log a non-sensitive identifier instead
|
|
19
|
+
(a user id, a key id), or drop the field entirely.
|
|
20
|
+
# Taint mode so indirection is caught — `const at = req.query.access_token;
|
|
21
|
+
# logger.info(at)` flags, not just the inline `console.log(req.query.code)`
|
|
22
|
+
# form. Distinct from auth.flow.secret-in-log (a search-mode rule keyed on
|
|
23
|
+
# the NAME of the logged identifier): this is a dataflow rule keyed on the
|
|
24
|
+
# request SOURCE, so it fires even when the credential is carried through an
|
|
25
|
+
# arbitrarily-named intermediate variable. The source list is narrowed to
|
|
26
|
+
# OAuth/OIDC credential fields (and the Authorization header), so logging a
|
|
27
|
+
# benign request field such as `req.query.page` does not fire. Routing the
|
|
28
|
+
# value through a redaction/masking helper or a truncating slice clears the
|
|
29
|
+
# taint.
|
|
30
|
+
mode: taint
|
|
31
|
+
pattern-sources:
|
|
32
|
+
# Dotted accessor: req.query.code / req.body.access_token / …
|
|
33
|
+
- patterns:
|
|
34
|
+
- pattern-either:
|
|
35
|
+
- pattern: $REQ.query.$K
|
|
36
|
+
- pattern: $REQ.body.$K
|
|
37
|
+
- pattern: $REQ.params.$K
|
|
38
|
+
- metavariable-regex:
|
|
39
|
+
metavariable: $K
|
|
40
|
+
regex: (?i)^(?:code|access[_-]?token|accesstoken|refresh[_-]?token|refreshtoken|id[_-]?token|idtoken|token|client[_-]?secret|clientsecret)$
|
|
41
|
+
# Index accessor: req.query['id_token'] / req.body["code"] / …
|
|
42
|
+
- patterns:
|
|
43
|
+
- pattern-either:
|
|
44
|
+
- pattern: $REQ.query[$K]
|
|
45
|
+
- pattern: $REQ.body[$K]
|
|
46
|
+
- pattern: $REQ.params[$K]
|
|
47
|
+
- metavariable-regex:
|
|
48
|
+
metavariable: $K
|
|
49
|
+
regex: (?i)^["'](?:code|access[_-]?token|accesstoken|refresh[_-]?token|refreshtoken|id[_-]?token|idtoken|token|client[_-]?secret|clientsecret)["']$
|
|
50
|
+
# Raw Authorization header (carries the bearer token / basic credentials).
|
|
51
|
+
- pattern: $REQ.headers.authorization
|
|
52
|
+
- pattern: $REQ.headers['authorization']
|
|
53
|
+
- pattern: $REQ.headers["authorization"]
|
|
54
|
+
- pattern: $REQ.get('authorization')
|
|
55
|
+
- pattern: $REQ.get('Authorization')
|
|
56
|
+
- pattern: $REQ.header('authorization')
|
|
57
|
+
- pattern: $REQ.header('Authorization')
|
|
58
|
+
pattern-sanitizers:
|
|
59
|
+
# Redaction / masking helpers, or a truncating slice/substring — the value
|
|
60
|
+
# that reaches the log is no longer the live credential.
|
|
61
|
+
- pattern: redact(...)
|
|
62
|
+
- pattern: mask(...)
|
|
63
|
+
- pattern: maskToken(...)
|
|
64
|
+
- pattern: $S.slice(...)
|
|
65
|
+
- pattern: $S.substring(...)
|
|
66
|
+
- pattern: $S.substr(...)
|
|
67
|
+
pattern-sinks:
|
|
68
|
+
# console.log/info/debug/warn/error(...) — any tainted argument fires.
|
|
69
|
+
- patterns:
|
|
70
|
+
- pattern-either:
|
|
71
|
+
- pattern: console.log(...)
|
|
72
|
+
- pattern: console.info(...)
|
|
73
|
+
- pattern: console.debug(...)
|
|
74
|
+
- pattern: console.warn(...)
|
|
75
|
+
- pattern: console.error(...)
|
|
76
|
+
# logger.<level>(...) — a log-named receiver with a log-level method.
|
|
77
|
+
- patterns:
|
|
78
|
+
- pattern: $LOG.$LEVEL(...)
|
|
79
|
+
- metavariable-regex:
|
|
80
|
+
metavariable: $LOG
|
|
81
|
+
regex: (?i)^.*log(?:ger)?$
|
|
82
|
+
- metavariable-regex:
|
|
83
|
+
metavariable: $LEVEL
|
|
84
|
+
regex: ^(?:log|info|debug|warn|warning|error|trace|fatal|verbose|silly)$
|
|
85
|
+
metadata:
|
|
86
|
+
oauthlint-rule-id: AUTH-FLOW-013
|
|
87
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/flow-oauth-credential-in-log
|
|
88
|
+
category: security
|
|
89
|
+
cwe: CWE-532
|
|
90
|
+
owasp: API8:2023
|
|
91
|
+
llm-prevalence: MEDIUM
|
|
92
|
+
technology:
|
|
93
|
+
- express
|
|
94
|
+
references:
|
|
95
|
+
- https://cwe.mitre.org/data/definitions/532.html
|
|
96
|
+
- https://datatracker.ietf.org/doc/html/rfc6749#section-10.3
|
|
97
|
+
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
|
|
@@ -14,13 +14,22 @@ rules:
|
|
|
14
14
|
Set `Secure: true` and `HttpOnly: true` on auth cookies, and add an
|
|
15
15
|
appropriate `SameSite` mode (for example `SameSite: http.SameSiteLaxMode`).
|
|
16
16
|
# Matches only the literal `false`. `Secure: true`, `HttpOnly: true`, and
|
|
17
|
-
# the absence of the field are not flagged.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
17
|
+
# the absence of the field are not flagged. The `pattern-inside` keeps
|
|
18
|
+
# detection scoped to an `http.Cookie{...}` (and `&http.Cookie{...}`)
|
|
19
|
+
# composite literal, so an unrelated struct exposing a `Secure`/`HttpOnly`
|
|
20
|
+
# bool is never matched. `$FIELD` captures the offending field name so the
|
|
21
|
+
# narrow match — and the autofix below — targets just that field.
|
|
20
22
|
patterns:
|
|
21
|
-
- pattern-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
- pattern-inside: 'http.Cookie{...}'
|
|
24
|
+
- pattern: '$FIELD: false'
|
|
25
|
+
- metavariable-regex:
|
|
26
|
+
metavariable: $FIELD
|
|
27
|
+
regex: ^(Secure|HttpOnly)$
|
|
28
|
+
# Safe, deterministic autofix: flip the disabled flag to `true`. `$FIELD` is
|
|
29
|
+
# preserved, so `Secure: false` becomes `Secure: true` and `HttpOnly: false`
|
|
30
|
+
# becomes `HttpOnly: true` — each the secure value that resolves the finding,
|
|
31
|
+
# with every other field in the literal left untouched.
|
|
32
|
+
fix: '$FIELD: true'
|
|
24
33
|
metadata:
|
|
25
34
|
oauthlint-rule-id: AUTH-GO-COOKIE-001
|
|
26
35
|
oauthlint-doc-url: https://oauthlint.dev/rules/go-cookie-insecure
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.flow.oauth-credential-in-log
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
An OAuth/OIDC credential taken from the HTTP request — an authorization
|
|
8
|
+
`code`, an `access_token` / `refresh_token` / `id_token`, a bearer
|
|
9
|
+
`token`, a `client_secret`, or the raw `Authorization` header — flows
|
|
10
|
+
into a logging call (`log.*`, `slog.*`, `fmt.Print*`, or a `logger.*`
|
|
11
|
+
method). Logs are written to files, shipped to aggregators (Datadog,
|
|
12
|
+
Splunk, CloudWatch) and read by people and systems that should never see
|
|
13
|
+
live credentials. A leaked authorization code or token can be replayed
|
|
14
|
+
to impersonate the user or complete the OAuth exchange (CWE-532).
|
|
15
|
+
|
|
16
|
+
Never log the raw credential. Redact or mask it before logging, log a
|
|
17
|
+
non-sensitive identifier instead (a user id, a key id), or drop the
|
|
18
|
+
field entirely.
|
|
19
|
+
# Taint mode so indirection is caught — `at := r.FormValue("access_token");
|
|
20
|
+
# log.Println(at)` flags, not just the inline form. The source list is
|
|
21
|
+
# narrowed to OAuth/OIDC credential field names (and the Authorization
|
|
22
|
+
# header), so logging a benign request field such as `r.FormValue("page")`
|
|
23
|
+
# does not fire. Routing the value through a redaction/masking helper clears
|
|
24
|
+
# the taint.
|
|
25
|
+
mode: taint
|
|
26
|
+
pattern-sources:
|
|
27
|
+
# Request getters keyed by a credential-looking parameter name.
|
|
28
|
+
- patterns:
|
|
29
|
+
- pattern-either:
|
|
30
|
+
- pattern: $R.URL.Query().Get($K)
|
|
31
|
+
- pattern: $R.FormValue($K)
|
|
32
|
+
- pattern: $R.PostFormValue($K)
|
|
33
|
+
- metavariable-regex:
|
|
34
|
+
metavariable: $K
|
|
35
|
+
regex: (?i)^"(code|access[_-]?token|accesstoken|refresh[_-]?token|refreshtoken|id[_-]?token|idtoken|token|client[_-]?secret|clientsecret)"$
|
|
36
|
+
# The raw Authorization header carries the bearer / basic credential.
|
|
37
|
+
- patterns:
|
|
38
|
+
- pattern: $R.Header.Get($K)
|
|
39
|
+
- metavariable-regex:
|
|
40
|
+
metavariable: $K
|
|
41
|
+
regex: (?i)^"authorization"$
|
|
42
|
+
pattern-sanitizers:
|
|
43
|
+
# Redaction / masking helpers — the value reaching the log is no longer
|
|
44
|
+
# the live credential.
|
|
45
|
+
- pattern: redact(...)
|
|
46
|
+
- pattern: mask(...)
|
|
47
|
+
- pattern: maskToken(...)
|
|
48
|
+
pattern-sinks:
|
|
49
|
+
# Standard library log / slog / fmt print sinks — any tainted argument.
|
|
50
|
+
- patterns:
|
|
51
|
+
- pattern-either:
|
|
52
|
+
- pattern: log.Print(...)
|
|
53
|
+
- pattern: log.Printf(...)
|
|
54
|
+
- pattern: log.Println(...)
|
|
55
|
+
- pattern: log.Fatal(...)
|
|
56
|
+
- pattern: log.Fatalf(...)
|
|
57
|
+
- pattern: log.Fatalln(...)
|
|
58
|
+
- pattern: log.Panic(...)
|
|
59
|
+
- pattern: log.Panicf(...)
|
|
60
|
+
- pattern: slog.Info(...)
|
|
61
|
+
- pattern: slog.Debug(...)
|
|
62
|
+
- pattern: slog.Warn(...)
|
|
63
|
+
- pattern: slog.Error(...)
|
|
64
|
+
- pattern: fmt.Print(...)
|
|
65
|
+
- pattern: fmt.Printf(...)
|
|
66
|
+
- pattern: fmt.Println(...)
|
|
67
|
+
# A log-named receiver with a log-level method: logger.Info(...),
|
|
68
|
+
# myLog.Printf(...). The receiver-name constraint keeps this off
|
|
69
|
+
# unrelated method calls.
|
|
70
|
+
- patterns:
|
|
71
|
+
- pattern: $LOG.$LEVEL(...)
|
|
72
|
+
- metavariable-regex:
|
|
73
|
+
metavariable: $LOG
|
|
74
|
+
regex: (?i)^.*log(ger)?$
|
|
75
|
+
- metavariable-regex:
|
|
76
|
+
metavariable: $LEVEL
|
|
77
|
+
regex: ^(Print|Printf|Println|Info|Infof|Infow|Debug|Debugf|Debugw|Warn|Warnf|Warning|Error|Errorf|Errorw|Fatal|Fatalf|Panic|Panicf|Trace|Tracef)$
|
|
78
|
+
metadata:
|
|
79
|
+
oauthlint-rule-id: AUTH-GO-FLOW-005
|
|
80
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-flow-oauth-credential-in-log
|
|
81
|
+
category: security
|
|
82
|
+
cwe: CWE-532
|
|
83
|
+
owasp: API8:2023
|
|
84
|
+
llm-prevalence: MEDIUM
|
|
85
|
+
technology:
|
|
86
|
+
- net/http
|
|
87
|
+
references:
|
|
88
|
+
- https://cwe.mitre.org/data/definitions/532.html
|
|
89
|
+
- https://datatracker.ietf.org/doc/html/rfc6749#section-10.3
|
|
90
|
+
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.jwt.untrusted-verify-key
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
Untrusted request input flows into the verification key returned by a
|
|
8
|
+
`golang-jwt` `Keyfunc` (or into the `WithValidMethods` allowlist). When
|
|
9
|
+
the attacker controls the key, they sign their own forged token and
|
|
10
|
+
supply the matching key, so every token "verifies" — a complete
|
|
11
|
+
authentication bypass. When the attacker controls the accepted methods,
|
|
12
|
+
they can downgrade verification and defeat the signature check (CWE-347,
|
|
13
|
+
Improper Verification of Cryptographic Signature).
|
|
14
|
+
|
|
15
|
+
The verification key and the accepted algorithms must be fixed
|
|
16
|
+
server-side. Return the key from trusted configuration or a vetted key
|
|
17
|
+
set keyed by a validated `kid`, and pin accepted methods to a constant
|
|
18
|
+
allowlist — never resolve them from `r.URL.Query()`, `r.FormValue`, or a
|
|
19
|
+
request header.
|
|
20
|
+
# Taint mode. The source is request input; the sink is the value RETURNED
|
|
21
|
+
# from a keyfunc (focused on the key expression, not the token) or the
|
|
22
|
+
# argument to `jwt.WithValidMethods`. Focusing on the key/methods — never
|
|
23
|
+
# the token, which is supposed to come from the request — keeps this from
|
|
24
|
+
# firing on every correct call. Distinct from auth.go.jwt.unchecked-method
|
|
25
|
+
# (a keyfunc that skips the `token.Method` check): this is about a
|
|
26
|
+
# request-CONTROLLED key or method list. Routing the value through an
|
|
27
|
+
# allow-list / validation helper clears the taint.
|
|
28
|
+
mode: taint
|
|
29
|
+
pattern-sources:
|
|
30
|
+
- pattern: $R.URL.Query().Get(...)
|
|
31
|
+
- pattern: $R.URL.Query()[$K]
|
|
32
|
+
- pattern: $R.FormValue(...)
|
|
33
|
+
- pattern: $R.PostFormValue(...)
|
|
34
|
+
- pattern: $R.Header.Get(...)
|
|
35
|
+
- pattern: $R.PathValue(...)
|
|
36
|
+
pattern-sanitizers:
|
|
37
|
+
- pattern: isAllowedKey(...)
|
|
38
|
+
- pattern: validateKey(...)
|
|
39
|
+
- pattern: isAllowedAlgorithm(...)
|
|
40
|
+
- pattern: validateAlgorithm(...)
|
|
41
|
+
pattern-sinks:
|
|
42
|
+
# The key returned from a golang-jwt Keyfunc literal. Scoped with
|
|
43
|
+
# pattern-inside so only a keyfunc return is a sink, and focused on the
|
|
44
|
+
# returned key expression so the token argument is never the trigger.
|
|
45
|
+
- patterns:
|
|
46
|
+
- pattern-inside: |
|
|
47
|
+
func($T *jwt.Token) ($RET, error) {
|
|
48
|
+
...
|
|
49
|
+
}
|
|
50
|
+
- pattern: return $SINK, $E
|
|
51
|
+
- focus-metavariable: $SINK
|
|
52
|
+
# Request input used as the accepted signing methods.
|
|
53
|
+
- patterns:
|
|
54
|
+
- pattern: jwt.WithValidMethods($SINK)
|
|
55
|
+
- focus-metavariable: $SINK
|
|
56
|
+
metadata:
|
|
57
|
+
oauthlint-rule-id: AUTH-GO-JWT-006
|
|
58
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-jwt-untrusted-verify-key
|
|
59
|
+
category: security
|
|
60
|
+
cwe: CWE-347
|
|
61
|
+
owasp: API2:2023
|
|
62
|
+
llm-prevalence: LOW
|
|
63
|
+
technology:
|
|
64
|
+
- golang-jwt
|
|
65
|
+
references:
|
|
66
|
+
- https://cwe.mitre.org/data/definitions/347.html
|
|
67
|
+
- https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Keyfunc
|
|
68
|
+
- https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
|
|
@@ -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,49 @@
|
|
|
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
|
+
metadata:
|
|
38
|
+
oauthlint-rule-id: AUTH-GO-OAUTH-001
|
|
39
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-oauth-ropc-grant
|
|
40
|
+
category: security
|
|
41
|
+
cwe: CWE-522
|
|
42
|
+
owasp: API2:2023
|
|
43
|
+
llm-prevalence: MEDIUM
|
|
44
|
+
technology:
|
|
45
|
+
- oauth2
|
|
46
|
+
references:
|
|
47
|
+
- https://datatracker.ietf.org/doc/html/rfc9700#section-2.4
|
|
48
|
+
- https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1#section-2.4
|
|
49
|
+
- 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
|