oauthlint-rules 0.2.3 → 0.2.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oauthlint-rules",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Semgrep rules catching the OAuth/OIDC/JWT anti-patterns that AI coding tools systematically produce.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,76 @@
1
+ rules:
2
+ - id: auth.flow.open-redirect
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: ERROR
7
+ message: |
8
+ Untrusted request input flows into a redirect destination. Because the
9
+ target URL is attacker-controlled, this is an open redirect (CWE-601):
10
+ an attacker can craft a link to your trusted host that bounces the victim
11
+ to an arbitrary external site. That fuels phishing, and in OAuth flows it
12
+ can be chained to steal authorization codes or access tokens by sending
13
+ the victim (and their callback) to a server you do not control.
14
+
15
+ Never redirect to a raw `req.query` / `req.body` / `req.params` /
16
+ `req.cookies` / `req.headers` value. Validate the destination against an
17
+ explicit allow-list of hosts or route names, or only allow relative paths
18
+ you control (reject anything containing a scheme or `//`).
19
+ # Taint mode so indirection is caught — `const dest = req.body.url;
20
+ # res.redirect(dest)` flags, not just the direct `res.redirect(req.query.x)`
21
+ # form. Passing the value through an allow-list / validation call
22
+ # (isAllowedUrl, allowlist.includes, Set.has) clears the taint, so a
23
+ # genuinely validated redirect does not fire.
24
+ mode: taint
25
+ pattern-sources:
26
+ - pattern: $REQ.query
27
+ - pattern: $REQ.params
28
+ - pattern: $REQ.body
29
+ - pattern: $REQ.cookies
30
+ - pattern: $REQ.headers
31
+ - pattern: $REQ.query.$X
32
+ - pattern: $REQ.params.$X
33
+ - pattern: $REQ.body.$X
34
+ - pattern: $REQ.cookies.$X
35
+ - pattern: $REQ.headers.$X
36
+ - pattern: $REQ.query[$K]
37
+ - pattern: $REQ.params[$K]
38
+ - pattern: $REQ.body[$K]
39
+ - pattern: $REQ.cookies[$K]
40
+ - pattern: $REQ.headers[$K]
41
+ pattern-sanitizers:
42
+ # Routing the value through an allow-list / validation helper clears the
43
+ # taint: only the vetted result (not the raw request input) reaches the
44
+ # sink. Matched by a conventional validation-helper call, or by an
45
+ # allow-list membership test (`Set.has` / `Array.includes`) that the
46
+ # value is passed through.
47
+ - pattern: isAllowedUrl(...)
48
+ - pattern: validateRedirect(...)
49
+ - pattern: sanitizeRedirect(...)
50
+ - pattern: $ALLOW.includes(...)
51
+ - pattern: $ALLOW.has(...)
52
+ - pattern: $ALLOW.indexOf(...)
53
+ pattern-sinks:
54
+ - patterns:
55
+ - pattern-either:
56
+ - pattern: $RES.redirect($SINK)
57
+ - pattern: $RES.redirect($CODE, $SINK)
58
+ - pattern: $RES.location($SINK)
59
+ - pattern: "$RES.set('Location', $SINK)"
60
+ - pattern: '$RES.set("Location", $SINK)'
61
+ - pattern: "$RES.setHeader('Location', $SINK)"
62
+ - pattern: '$RES.setHeader("Location", $SINK)'
63
+ - pattern: "$RES.writeHead($S, {..., Location: $SINK, ...})"
64
+ - focus-metavariable: $SINK
65
+ metadata:
66
+ oauthlint-rule-id: AUTH-FLOW-010
67
+ oauthlint-doc-url: https://oauthlint.dev/rules/flow-open-redirect
68
+ category: security
69
+ cwe: CWE-601
70
+ owasp: A01:2021
71
+ llm-prevalence: HIGH
72
+ technology:
73
+ - express
74
+ references:
75
+ - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
76
+ - https://cwe.mitre.org/data/definitions/601.html
@@ -0,0 +1,85 @@
1
+ rules:
2
+ - id: auth.flow.ssrf
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: ERROR
7
+ message: |
8
+ Untrusted request input flows into the URL of an outbound HTTP request.
9
+ Because the destination is attacker-controlled, this is a Server-Side
10
+ Request Forgery (CWE-918): an attacker can point the request at internal
11
+ services behind your firewall, or at the cloud metadata endpoint
12
+ (http://169.254.169.254/...) to steal IAM/instance credentials and pivot
13
+ deeper into your infrastructure.
14
+
15
+ Never build a request URL straight from a raw `req.query` / `req.body` /
16
+ `req.params` / `req.cookies` / `req.headers` value. Validate the
17
+ destination against an explicit allow-list of hosts (resolve the URL and
18
+ check its host against the allow-list, rejecting private/loopback ranges)
19
+ before issuing the request.
20
+ # Taint mode so indirection is caught — `const u = req.query.endpoint;
21
+ # http.get(u)` flags, not just the direct `fetch(req.query.url)` form.
22
+ # Passing the value through a host allow-list / validation call
23
+ # (isAllowedUrl, allowlist.includes, Set.has) clears the taint, so a
24
+ # genuinely vetted outbound request does not fire.
25
+ mode: taint
26
+ pattern-sources:
27
+ - pattern: $REQ.query
28
+ - pattern: $REQ.params
29
+ - pattern: $REQ.body
30
+ - pattern: $REQ.cookies
31
+ - pattern: $REQ.headers
32
+ - pattern: $REQ.query.$X
33
+ - pattern: $REQ.params.$X
34
+ - pattern: $REQ.body.$X
35
+ - pattern: $REQ.cookies.$X
36
+ - pattern: $REQ.headers.$X
37
+ - pattern: $REQ.query[$K]
38
+ - pattern: $REQ.params[$K]
39
+ - pattern: $REQ.body[$K]
40
+ - pattern: $REQ.cookies[$K]
41
+ - pattern: $REQ.headers[$K]
42
+ pattern-sanitizers:
43
+ # Routing the value through a host allow-list / validation helper clears
44
+ # the taint: only the vetted destination (not the raw request input)
45
+ # reaches the request. Matched by a conventional validation-helper call,
46
+ # or by an allow-list membership test (`Set.has` / `Array.includes`) that
47
+ # the value is passed through.
48
+ - pattern: isAllowedUrl(...)
49
+ - pattern: validateUrl(...)
50
+ - pattern: assertAllowedHost(...)
51
+ - pattern: $ALLOW.includes(...)
52
+ - pattern: $ALLOW.has(...)
53
+ - pattern: $ALLOW.indexOf(...)
54
+ pattern-sinks:
55
+ - patterns:
56
+ - pattern-either:
57
+ - pattern: fetch($SINK, ...)
58
+ - pattern: nodeFetch($SINK, ...)
59
+ - pattern: request($SINK, ...)
60
+ - pattern: got($SINK, ...)
61
+ - pattern: axios($SINK)
62
+ - pattern: axios.get($SINK, ...)
63
+ - pattern: axios.post($SINK, ...)
64
+ - pattern: axios.put($SINK, ...)
65
+ - pattern: axios.delete($SINK, ...)
66
+ - pattern: "axios.request({..., url: $SINK, ...})"
67
+ - pattern: http.get($SINK, ...)
68
+ - pattern: https.get($SINK, ...)
69
+ - pattern: http.request($SINK, ...)
70
+ - pattern: https.request($SINK, ...)
71
+ - focus-metavariable: $SINK
72
+ metadata:
73
+ oauthlint-rule-id: AUTH-FLOW-011
74
+ oauthlint-doc-url: https://oauthlint.dev/rules/flow-ssrf
75
+ category: security
76
+ cwe: CWE-918
77
+ owasp: API7:2023
78
+ llm-prevalence: HIGH
79
+ technology:
80
+ - express
81
+ - axios
82
+ - fetch
83
+ references:
84
+ - https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
85
+ - https://cwe.mitre.org/data/definitions/918.html
@@ -0,0 +1,58 @@
1
+ rules:
2
+ - id: auth.go.flow.open-redirect
3
+ languages:
4
+ - go
5
+ severity: ERROR
6
+ message: |
7
+ Untrusted request data flows into an HTTP redirect destination. An
8
+ attacker who controls the redirect target (via a query parameter, form
9
+ field, or request header) can forward the victim to an arbitrary
10
+ external site while the link still appears to point at your trusted
11
+ domain — a classic open redirect, commonly abused to bypass OAuth
12
+ `redirect_uri` checks and to mount convincing phishing.
13
+
14
+ Do not pass request-derived values straight into `http.Redirect(...)`
15
+ or a `Location` header. Validate the destination against an explicit
16
+ allow-list, or restrict it to a known relative path (reject absolute
17
+ URLs, scheme-relative `//host` values, and back-references) before
18
+ redirecting. See CWE-601.
19
+ # Taint mode so indirection (dest := r.FormValue("url"); http.Redirect(w,
20
+ # r, dest, ...)) is caught, not just the inline form. The taint is cleared
21
+ # by an allow-list / validation helper that vets the destination, or by a
22
+ # url.Parse(...)-then-checked flow where the parsed value is inspected
23
+ # before use.
24
+ mode: taint
25
+ pattern-sources:
26
+ # $R is the *http.Request. Cover the common net/http input shapes.
27
+ - pattern: $R.URL.Query().Get(...)
28
+ - pattern: $R.URL.Query()
29
+ - pattern: $R.URL.Query()[$K]
30
+ - pattern: $R.FormValue(...)
31
+ - pattern: $R.PostFormValue(...)
32
+ - pattern: $R.Header.Get(...)
33
+ pattern-sanitizers:
34
+ # Allow-list / validation helper that vets the destination string.
35
+ - pattern: validateRedirect(...)
36
+ - pattern: isAllowedRedirect(...)
37
+ - pattern: $ALLOW.MatchString(...)
38
+ # url.Parse-then-checked: a parsed URL that has been inspected is treated
39
+ # as cleared (the result is rebuilt/validated rather than reflected raw).
40
+ - pattern: url.Parse(...)
41
+ pattern-sinks:
42
+ - patterns:
43
+ - pattern-either:
44
+ - pattern: http.Redirect($W, $R, $DEST, $CODE)
45
+ - pattern: $W.Header().Set("Location", $DEST)
46
+ - focus-metavariable: $DEST
47
+ metadata:
48
+ oauthlint-rule-id: AUTH-GO-FLOW-002
49
+ oauthlint-doc-url: https://oauthlint.dev/rules/go-flow-open-redirect
50
+ category: security
51
+ cwe: CWE-601
52
+ owasp: A01:2021
53
+ llm-prevalence: HIGH
54
+ technology:
55
+ - net/http
56
+ references:
57
+ - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
58
+ - https://cwe.mitre.org/data/definitions/601.html
@@ -0,0 +1,76 @@
1
+ rules:
2
+ - id: auth.go.flow.ssrf
3
+ languages:
4
+ - go
5
+ severity: ERROR
6
+ message: |
7
+ Untrusted request data flows into the URL of an outbound HTTP request.
8
+ An attacker who controls the request target (via a query parameter, form
9
+ field, or request header) can coerce your server into making requests to
10
+ arbitrary destinations — Server-Side Request Forgery (SSRF). This is
11
+ routinely abused to reach internal-only services behind your network
12
+ perimeter and, most damagingly, the cloud instance metadata endpoint
13
+ (e.g. http://169.254.169.254/...), letting an attacker steal short-lived
14
+ credentials and pivot into your cloud account.
15
+
16
+ Never pass a request-derived value straight into `http.Get`,
17
+ `http.Post`, `http.NewRequest`, or a client's `Get`/`Post`. Validate the
18
+ destination host against an explicit allow-list (parse the URL and check
19
+ the resolved host/scheme), and reject requests to private, loopback, and
20
+ link-local address ranges before dialing. See CWE-918.
21
+ # Taint mode so indirection (target := r.FormValue("endpoint");
22
+ # http.Get(target)) is caught, not just the inline form. The taint is
23
+ # cleared by an allow-list / host-validation helper, or by a
24
+ # url.Parse(...)-then-checked flow where the parsed host is vetted before
25
+ # the request is made.
26
+ mode: taint
27
+ pattern-sources:
28
+ # $R is the *http.Request. Cover the common net/http input shapes.
29
+ - pattern: $R.URL.Query().Get(...)
30
+ - pattern: $R.URL.Query()
31
+ - pattern: $R.URL.Query()[$K]
32
+ - pattern: $R.FormValue(...)
33
+ - pattern: $R.PostFormValue(...)
34
+ - pattern: $R.Header.Get(...)
35
+ pattern-sanitizers:
36
+ # Allow-list / host-validation helper that vets the URL string.
37
+ - pattern: validateURL(...)
38
+ - pattern: isAllowedHost(...)
39
+ - pattern: $ALLOW.MatchString(...)
40
+ # url.Parse-then-checked: a parsed URL whose host has been inspected is
41
+ # treated as cleared (the request is built from a vetted destination
42
+ # rather than the raw untrusted string).
43
+ - pattern: url.Parse(...)
44
+ pattern-sinks:
45
+ # Focus the URL argument so the finding lands on the tainted destination,
46
+ # not the whole call. The plain call form is used (no aliasing) because
47
+ # it matches reliably in taint mode.
48
+ - patterns:
49
+ - pattern-either:
50
+ - pattern: http.Get($URL)
51
+ - pattern: http.Post($URL, ...)
52
+ - pattern: http.Head($URL)
53
+ - pattern: http.NewRequest($M, $URL, ...)
54
+ - pattern: http.NewRequestWithContext($CTX, $M, $URL, ...)
55
+ - pattern: $CLIENT.Get($URL)
56
+ - pattern: $CLIENT.Post($URL, ...)
57
+ # The bare `$CLIENT.Get(...)` shape collides with the request-input
58
+ # SOURCES that are themselves `.Get(...)` calls (`r.URL.Query().Get(...)`
59
+ # and `r.Header.Get(...)`), which over-matched the source-assignment
60
+ # line. Exclude those accessors so each is only ever a source, leaving
61
+ # `client.Get(url)` as the genuine outbound-request sink.
62
+ - pattern-not: $Q.Query().Get(...)
63
+ - pattern-not: $H.Header.Get(...)
64
+ - focus-metavariable: $URL
65
+ metadata:
66
+ oauthlint-rule-id: AUTH-GO-FLOW-003
67
+ oauthlint-doc-url: https://oauthlint.dev/rules/go-flow-ssrf
68
+ category: security
69
+ cwe: CWE-918
70
+ owasp: API7:2023
71
+ llm-prevalence: HIGH
72
+ technology:
73
+ - net/http
74
+ references:
75
+ - https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
76
+ - https://cwe.mitre.org/data/definitions/918.html
@@ -0,0 +1,41 @@
1
+ rules:
2
+ - id: auth.java.crypto.weak-hash
3
+ languages:
4
+ - java
5
+ severity: WARNING
6
+ message: |
7
+ A broken hash algorithm (MD5 or SHA-1) is being instantiated via JCA
8
+ `MessageDigest.getInstance(...)`. MD5 and SHA-1 are cryptographically
9
+ broken: practical collision attacks exist, so they must NOT be used for
10
+ any security purpose — integrity checks, content/token fingerprints,
11
+ digital signatures, HMAC keys, or deduplication that a trust decision
12
+ depends on (CWE-328 / CWE-327).
13
+
14
+ Use SHA-256 or stronger (SHA-384, SHA-512, SHA-3):
15
+ `MessageDigest.getInstance("SHA-256")`. Note: this rule covers the general
16
+ weak-digest case; storing *passwords* needs a dedicated slow hasher
17
+ (BCrypt/Argon2/PBKDF2), which is enforced separately.
18
+ # Match only the algorithm string passed to MessageDigest.getInstance(...).
19
+ # The metavariable-regex flags the broken digests MD5, SHA-1 and the SHA1
20
+ # alias (case-insensitive, optional hyphen). It is scoped to the
21
+ # getInstance(...) instantiation site on purpose so it is low-FP and does
22
+ # NOT overlap auth.java.crypto.weak-password-hash, which instead anchors on
23
+ # a password-named digest()/update() call (and also flags SHA-256/SHA-512 in
24
+ # that password context). "SHA-256"/"SHA-512" are silent here.
25
+ patterns:
26
+ - pattern: java.security.MessageDigest.getInstance($ALG, ...)
27
+ - metavariable-regex:
28
+ metavariable: $ALG
29
+ regex: (?i)^"(md5|sha-?1)"$
30
+ metadata:
31
+ oauthlint-rule-id: AUTH-JAVA-CRYPTO-004
32
+ oauthlint-doc-url: https://oauthlint.dev/rules/java-crypto-weak-hash
33
+ category: security
34
+ cwe: CWE-328
35
+ owasp: A02:2021
36
+ llm-prevalence: MEDIUM
37
+ technology:
38
+ - java
39
+ references:
40
+ - https://cwe.mitre.org/data/definitions/328.html
41
+ - https://csrc.nist.gov/news/2022/nist-transitioning-away-from-sha-1-for-all-apps
@@ -0,0 +1,39 @@
1
+ rules:
2
+ - id: auth.oauth.access-token-in-url
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: WARNING
7
+ message: |
8
+ An OAuth `access_token` (or `refresh_token` / `id_token`) is placed in a
9
+ URL query string. URLs leak: the full URL — token included — is recorded
10
+ in server and reverse-proxy access logs, saved in browser history, and
11
+ sent in the `Referer` header to every third-party CDN, analytics, and ad
12
+ script loaded by the destination page. A token in a URL is a leaked token.
13
+
14
+ Send the token in the `Authorization: Bearer …` header, or in a POST
15
+ request body. Never in the URL query string.
16
+
17
+ CWE-598: Use of GET Request Method With Sensitive Query Strings.
18
+ # Anchored to the query-param shape `?<name>=` / `&<name>=` (the `[?&]…=`
19
+ # form) so we fire on token-carrying URLs built as string or template
20
+ # literals, while staying silent on: a token in an `Authorization` header,
21
+ # a POST body / object property `{ access_token: t }` (no leading `?`/`&`
22
+ # and no trailing `=`), `params.set('access_token', …)` builders, and the
23
+ # bare word `access_token` in a comment or non-URL string. Case-insensitive
24
+ # on the param name. This is deliberately distinct from
25
+ # auth.flow.credentials-in-url (which excludes `access_token`) and
26
+ # auth.jwt.in-url (which only matches JWT-shaped `eyJ…` values).
27
+ pattern-regex: '[?&](?i:access_token|refresh_token|id_token)='
28
+ metadata:
29
+ oauthlint-rule-id: AUTH-OAUTH-013
30
+ oauthlint-doc-url: https://oauthlint.dev/rules/oauth-access-token-in-url
31
+ category: security
32
+ cwe: CWE-598
33
+ owasp: A05:2021
34
+ llm-prevalence: MEDIUM
35
+ technology:
36
+ - oauth
37
+ references:
38
+ - https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
39
+ - https://cwe.mitre.org/data/definitions/598.html
@@ -0,0 +1,82 @@
1
+ rules:
2
+ - id: auth.py.flow.open-redirect
3
+ languages:
4
+ - python
5
+ severity: ERROR
6
+ message: |
7
+ Untrusted request data flows into a Flask `redirect(...)` without
8
+ validation — an open redirect (CWE-601). An attacker can craft a link
9
+ to your trusted domain that bounces the victim to an attacker-controlled
10
+ site, enabling phishing and OAuth redirect/authorization-code abuse (the
11
+ victim trusts your URL, then lands on the attacker's page).
12
+
13
+ Never redirect to a raw `request.args`/`request.form`/`request.values`/
14
+ `request.cookies`/`request.headers` value. Build the destination with
15
+ `url_for(...)` (safe by construction), or validate the target against an
16
+ explicit allow-list / an `is_safe_url(...)`-style same-host check before
17
+ redirecting.
18
+ # Taint mode so indirection (dest = request.args['url']; return redirect(dest))
19
+ # and `.get(...)` accessors are caught, not just the direct form. Passing the
20
+ # value through url_for(...) or an is_safe_url(...)/allow-list check clears the
21
+ # taint. The sink focuses the redirect target argument (and a Location header).
22
+ mode: taint
23
+ pattern-sources:
24
+ - pattern: flask.request.args
25
+ - pattern: flask.request.form
26
+ - pattern: flask.request.values
27
+ - pattern: flask.request.cookies
28
+ - pattern: flask.request.headers
29
+ - pattern: request.args
30
+ - pattern: request.form
31
+ - pattern: request.values
32
+ - pattern: request.cookies
33
+ - pattern: request.headers
34
+ - pattern: request.args.get(...)
35
+ - pattern: request.form.get(...)
36
+ - pattern: request.values.get(...)
37
+ - pattern: request.cookies.get(...)
38
+ - pattern: request.headers.get(...)
39
+ - pattern: request.args[$K]
40
+ - pattern: request.form[$K]
41
+ - pattern: request.values[$K]
42
+ - pattern: request.cookies[$K]
43
+ - pattern: request.headers[$K]
44
+ pattern-sanitizers:
45
+ # `url_for(...)` builds the URL from a known endpoint — safe by construction.
46
+ - pattern: url_for(...)
47
+ - pattern: flask.url_for(...)
48
+ # Allow-list / same-host validators used as a guard:
49
+ # if is_safe_url(target): return redirect(target)
50
+ # A value used inside such an `if` body is treated as validated, so the
51
+ # guarded redirect does not fire.
52
+ - patterns:
53
+ - pattern: $V
54
+ - pattern-inside: |
55
+ if is_safe_url($V):
56
+ ...
57
+ - patterns:
58
+ - pattern: $V
59
+ - pattern-inside: |
60
+ if url_has_allowed_host_and_scheme($V, ...):
61
+ ...
62
+ pattern-sinks:
63
+ - patterns:
64
+ - pattern-either:
65
+ - pattern: redirect($SINK)
66
+ - pattern: redirect($SINK, ...)
67
+ - pattern: flask.redirect($SINK)
68
+ - pattern: flask.redirect($SINK, ...)
69
+ - pattern: 'Response(..., headers={..., "Location": $SINK, ...}, ...)'
70
+ - focus-metavariable: $SINK
71
+ metadata:
72
+ oauthlint-rule-id: AUTH-PY-FLOW-006
73
+ oauthlint-doc-url: https://oauthlint.dev/rules/py-flow-open-redirect
74
+ category: security
75
+ cwe: CWE-601
76
+ owasp: A01:2021
77
+ llm-prevalence: HIGH
78
+ technology:
79
+ - flask
80
+ references:
81
+ - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
82
+ - https://cwe.mitre.org/data/definitions/601.html
@@ -0,0 +1,118 @@
1
+ rules:
2
+ - id: auth.py.flow.ssrf
3
+ languages:
4
+ - python
5
+ severity: ERROR
6
+ message: |
7
+ Untrusted request data flows into an outbound HTTP request without
8
+ validation — a Server-Side Request Forgery (SSRF, CWE-918). An attacker
9
+ who controls the target URL can make your server reach internal-only
10
+ services (admin panels, databases, other microservices behind your
11
+ firewall) or the cloud metadata endpoint (e.g. http://169.254.169.254/),
12
+ stealing IAM/instance credentials and pivoting deeper into your network.
13
+
14
+ Never pass a raw `request.args`/`request.form`/`request.values`/
15
+ `request.json`/`request.cookies`/`request.headers` value to
16
+ `requests`/`urllib`/`httpx`/`aiohttp`. Validate the destination host
17
+ against an explicit allow-list (`is_allowed_url(...)` /
18
+ `validate_host(...)` / `url_has_allowed_host_and_scheme(...)`) before the
19
+ request, and reject link-local / private / metadata addresses.
20
+ # Taint mode so indirection (url = request.json['endpoint']; httpx.get(url))
21
+ # and `.get(...)` accessors are caught, not just the direct form. Passing the
22
+ # value through an allow-list / host validator clears the taint. The sink
23
+ # focuses the URL argument of each HTTP client call.
24
+ mode: taint
25
+ pattern-sources:
26
+ - pattern: flask.request.args
27
+ - pattern: flask.request.form
28
+ - pattern: flask.request.values
29
+ - pattern: flask.request.json
30
+ - pattern: flask.request.cookies
31
+ - pattern: flask.request.headers
32
+ - pattern: request.args
33
+ - pattern: request.form
34
+ - pattern: request.values
35
+ - pattern: request.json
36
+ - pattern: request.cookies
37
+ - pattern: request.headers
38
+ - pattern: request.args.get(...)
39
+ - pattern: request.form.get(...)
40
+ - pattern: request.values.get(...)
41
+ - pattern: request.json.get(...)
42
+ - pattern: request.cookies.get(...)
43
+ - pattern: request.headers.get(...)
44
+ - pattern: request.args[$K]
45
+ - pattern: request.form[$K]
46
+ - pattern: request.values[$K]
47
+ - pattern: request.json[$K]
48
+ - pattern: request.cookies[$K]
49
+ - pattern: request.headers[$K]
50
+ pattern-sanitizers:
51
+ # Allow-list / host validators used as a guard:
52
+ # if is_allowed_url(url): requests.get(url)
53
+ # A value used inside such an `if` body is treated as validated, so the
54
+ # guarded request does not fire. `by-side-effect` is unsupported on
55
+ # semgrep 1.157, so we use the `pattern-inside` if-guard approach.
56
+ - patterns:
57
+ - pattern: $V
58
+ - pattern-inside: |
59
+ if is_allowed_url($V):
60
+ ...
61
+ - patterns:
62
+ - pattern: $V
63
+ - pattern-inside: |
64
+ if validate_host($V):
65
+ ...
66
+ - patterns:
67
+ - pattern: $V
68
+ - pattern-inside: |
69
+ if url_has_allowed_host_and_scheme($V, ...):
70
+ ...
71
+ pattern-sinks:
72
+ - patterns:
73
+ - pattern-either:
74
+ - pattern: 'requests.get($URL, ...)'
75
+ - pattern: 'requests.post($URL, ...)'
76
+ - pattern: 'requests.put($URL, ...)'
77
+ - pattern: 'requests.delete($URL, ...)'
78
+ - pattern: 'requests.patch($URL, ...)'
79
+ - pattern: 'requests.head($URL, ...)'
80
+ - pattern: 'requests.request(..., $URL, ...)'
81
+ - pattern: 'urllib.request.urlopen($URL, ...)'
82
+ - pattern: urllib.request.urlopen($URL)
83
+ - pattern: 'httpx.get($URL, ...)'
84
+ - pattern: 'httpx.post($URL, ...)'
85
+ - pattern: 'httpx.put($URL, ...)'
86
+ - pattern: 'httpx.delete($URL, ...)'
87
+ - pattern: 'httpx.patch($URL, ...)'
88
+ - pattern: 'httpx.head($URL, ...)'
89
+ # httpx.Client()/aiohttp session calls. Excluding a `request`
90
+ # receiver keeps these from matching the `request.X.get(...)`
91
+ # SOURCE accessors (which would otherwise self-report).
92
+ - patterns:
93
+ - pattern-either:
94
+ - pattern: '$CLIENT.get($URL, ...)'
95
+ - pattern: '$CLIENT.post($URL, ...)'
96
+ - pattern: '$CLIENT.put($URL, ...)'
97
+ - pattern: '$CLIENT.delete($URL, ...)'
98
+ - pattern: '$CLIENT.patch($URL, ...)'
99
+ - pattern: '$CLIENT.head($URL, ...)'
100
+ - pattern: '$CLIENT.request(..., $URL, ...)'
101
+ - metavariable-regex:
102
+ metavariable: $CLIENT
103
+ regex: '^(?!request(\.|$)).*'
104
+ - focus-metavariable: $URL
105
+ metadata:
106
+ oauthlint-rule-id: AUTH-PY-FLOW-007
107
+ oauthlint-doc-url: https://oauthlint.dev/rules/py-flow-ssrf
108
+ category: security
109
+ cwe: CWE-918
110
+ owasp: API7:2023
111
+ llm-prevalence: HIGH
112
+ technology:
113
+ - flask
114
+ - requests
115
+ - httpx
116
+ references:
117
+ - https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
118
+ - https://cwe.mitre.org/data/definitions/918.html
@@ -0,0 +1,59 @@
1
+ rules:
2
+ - id: auth.rust.jwt.no-issuer-validation
3
+ languages:
4
+ - rust
5
+ severity: WARNING
6
+ message: |
7
+ A JWT is decoded with a `jsonwebtoken` `Validation` that never sets the
8
+ expected issuer, so `decode` accepts a token minted by ANY issuer. The
9
+ `jsonwebtoken` crate does not validate the `iss` claim unless you opt in,
10
+ so a token signed by an attacker-controlled or otherwise untrusted issuer
11
+ passes validation as long as the signature checks out. For OAuth/OIDC this
12
+ lets a token from the wrong authorization server be replayed against this
13
+ API.
14
+
15
+ Pin the issuer before decoding, e.g.
16
+ `validation.set_issuer(&["https://issuer.example.com"])` (or set
17
+ `validation.iss`), so only tokens whose `iss` claim matches your trusted
18
+ authorization server are accepted.
19
+ # Modeled on auth.rust.jwt.no-aud-validation: detect a `Validation`
20
+ # (`Validation::new(...)` or `Validation::default()`) that flows into
21
+ # `decode(...)` but never has its issuer pinned. Unlike `aud`/`exp` (default
22
+ # `true`, disabled via `validate_* = false`), the issuer check is OFF by
23
+ # default and must be opted into via `set_issuer(...)` / `validation.iss`,
24
+ # so the vulnerable shape is the ABSENCE of that call. We bind the validator
25
+ # to `$V` at construction, require a `decode` that uses it, and suppress the
26
+ # finding with `pattern-not-inside` when `set_issuer` / `validation.iss` is
27
+ # set on the same `$V`. Matching on the construction (not the `decode` call)
28
+ # mirrors the sibling rule's low-FP, AST-only profile.
29
+ patterns:
30
+ - pattern-either:
31
+ - pattern: let mut $V = Validation::new(...);
32
+ - pattern: let mut $V = Validation::default();
33
+ - pattern-not-inside: |
34
+ let mut $V = ...;
35
+ ...
36
+ $V.set_issuer(...);
37
+ ...
38
+ - pattern-not-inside: |
39
+ let mut $V = ...;
40
+ ...
41
+ $V.iss = $X;
42
+ ...
43
+ - pattern-inside: |
44
+ let mut $V = ...;
45
+ ...
46
+ decode($TOKEN, $KEY, &$V)
47
+ metadata:
48
+ oauthlint-rule-id: AUTH-RUST-JWT-005
49
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-jwt-no-issuer-validation
50
+ category: security
51
+ cwe: CWE-345
52
+ owasp: API2:2023
53
+ llm-prevalence: MEDIUM
54
+ technology:
55
+ - jsonwebtoken
56
+ references:
57
+ - https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.Validation.html#method.set_issuer
58
+ - https://cwe.mitre.org/data/definitions/345.html
59
+ - https://cwe.mitre.org/data/definitions/287.html
@@ -16,6 +16,11 @@ rules:
16
16
  # Matches only the literal `true`. `danger_accept_invalid_certs(false)` and
17
17
  # the method's absence are not flagged. `$B` is any builder expression.
18
18
  pattern: $B.danger_accept_invalid_certs(true)
19
+ # Safe, deterministic autofix: flip the boolean literal to `false`, which is
20
+ # the secure default and fully resolves the finding. `$B` (the builder
21
+ # expression) is preserved verbatim, so the surrounding chain is untouched —
22
+ # only `true` -> `false` changes.
23
+ fix: $B.danger_accept_invalid_certs(false)
19
24
  metadata:
20
25
  oauthlint-rule-id: AUTH-RUST-TLS-001
21
26
  oauthlint-doc-url: https://oauthlint.dev/rules/rust-tls-accept-invalid-certs
@@ -18,6 +18,11 @@ rules:
18
18
  # Matches only the literal `true`. `danger_accept_invalid_hostnames(false)`
19
19
  # and the method's absence are not flagged. `$B` is any builder expression.
20
20
  pattern: $B.danger_accept_invalid_hostnames(true)
21
+ # Safe, deterministic autofix: flip the boolean literal to `false`, which is
22
+ # the secure default and fully resolves the finding. `$B` (the builder
23
+ # expression) is preserved verbatim, so the surrounding chain is untouched —
24
+ # only `true` -> `false` changes.
25
+ fix: $B.danger_accept_invalid_hostnames(false)
21
26
  metadata:
22
27
  oauthlint-rule-id: AUTH-RUST-TLS-002
23
28
  oauthlint-doc-url: https://oauthlint.dev/rules/rust-tls-accept-invalid-hostnames