oauthlint-rules 0.2.0 → 0.2.2
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/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauthlint-rules",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"homepage": "https://oauthlint.dev",
|
|
59
59
|
"bugs": "https://github.com/Auspeo/oauthlint/issues",
|
|
60
60
|
"license": "MIT",
|
|
61
|
-
"author": "
|
|
61
|
+
"author": "Auspeo (https://github.com/Auspeo)",
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsc",
|
|
64
64
|
"test": "vitest",
|
|
@@ -43,16 +43,26 @@ rules:
|
|
|
43
43
|
# express cors() middleware: a callback that unconditionally allows
|
|
44
44
|
# every origin (cb(null, true) regardless of the origin argument).
|
|
45
45
|
- pattern: 'cors({ ..., origin: ($O, $CB) => $CB(null, true), ... })'
|
|
46
|
+
# A block-body callback that returns `cb(null, true)` is only dangerous
|
|
47
|
+
# when it does so UNCONDITIONALLY — i.e. it ignores its origin argument.
|
|
48
|
+
# A callback that inspects `$O` inside an `if` (e.g. `if (allow.has(origin))
|
|
49
|
+
# return cb(null, true)`) is the allowlist pattern this rule recommends, so
|
|
50
|
+
# the `pattern-not` below suppresses it. Without that guard the rule flagged
|
|
51
|
+
# the exact safe shape its own message tells you to use.
|
|
46
52
|
- patterns:
|
|
47
53
|
- pattern: |
|
|
48
54
|
cors({ ..., origin: ($O, $CB) => { ... }, ... })
|
|
49
55
|
- pattern-inside: |
|
|
50
56
|
cors({ ..., origin: ($O, $CB) => { ... return $CB(null, true); }, ... })
|
|
57
|
+
- pattern-not: |
|
|
58
|
+
cors({ ..., origin: ($O, $CB) => { ... if (<... $O ...>) { ... } ... }, ... })
|
|
51
59
|
- patterns:
|
|
52
60
|
- pattern: |
|
|
53
61
|
cors({ ..., origin: function ($O, $CB) { ... }, ... })
|
|
54
62
|
- pattern-inside: |
|
|
55
63
|
cors({ ..., origin: function ($O, $CB) { ... return $CB(null, true); }, ... })
|
|
64
|
+
- pattern-not: |
|
|
65
|
+
cors({ ..., origin: function ($O, $CB) { ... if (<... $O ...>) { ... } ... }, ... })
|
|
56
66
|
metadata:
|
|
57
67
|
oauthlint-rule-id: AUTH-CORS-002
|
|
58
68
|
oauthlint-doc-url: https://oauthlint.dev/rules/cors-reflect-origin
|
|
@@ -21,10 +21,22 @@ rules:
|
|
|
21
21
|
# NOT match the bare words `auth`/`access`/`refresh`/`session` because they
|
|
22
22
|
# appear as substrings of innocuous keys (author_filter, auto_refresh,
|
|
23
23
|
# access_count, sidebar_session) and produced false positives.
|
|
24
|
+
# A write to web storage (`setItem(k, v)` or `store[k] = v`) is flagged when
|
|
25
|
+
# EITHER the key is a token-named string literal OR the value is a token-named
|
|
26
|
+
# identifier. The latter catches `localStorage.setItem(TOKEN_KEY, token)`,
|
|
27
|
+
# where the key is a variable — the most common real-world shape. We match
|
|
28
|
+
# only the strong words `token`/`jwt`/`bearer`/`credential`/`api_key` (never
|
|
29
|
+
# bare `auth`/`access`/`refresh`/`session`, which appear in innocuous keys
|
|
30
|
+
# like `author_filter`, `auto_refresh`, `access_count`).
|
|
24
31
|
pattern-either:
|
|
32
|
+
# `setItem(key, value)` where a strong token word appears in EITHER the key
|
|
33
|
+
# or the value. Matching the whole call as one range (rather than separate
|
|
34
|
+
# key/value patterns) catches `setItem(TOKEN_KEY, token)` — the common shape
|
|
35
|
+
# where the key is a variable — without double-counting literal-key lines.
|
|
25
36
|
- patterns:
|
|
26
37
|
- pattern-regex: |-
|
|
27
|
-
(?:window\.|globalThis\.|self\.)?(?:localStorage|sessionStorage)\.setItem\(
|
|
38
|
+
(?:window\.|globalThis\.|self\.)?(?:localStorage|sessionStorage)\.setItem\([^)]*?(?i:token|jwt|bearer|credential|api[_-]?key)
|
|
39
|
+
# Bracket assignment with a token-named string-literal key.
|
|
28
40
|
- patterns:
|
|
29
41
|
- pattern-regex: |-
|
|
30
42
|
(?:window\.|globalThis\.|self\.)?(?:localStorage|sessionStorage)\[\s*['"][^'"]*(?i:token|jwt|bearer|credential|api[_-]?key)[^'"]*['"]\s*\]\s*=
|
|
@@ -13,24 +13,46 @@ rules:
|
|
|
13
13
|
Compare the received `state` to the value you stored before
|
|
14
14
|
redirecting (session, signed cookie, or Redis). Reject the callback
|
|
15
15
|
if it's missing or doesn't match.
|
|
16
|
-
# Suppress when the received `state` is
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
16
|
+
# Suppress when the received `state` is validated. Two shapes count as
|
|
17
|
+
# validation:
|
|
18
|
+
# 1. The read appears directly inside an `if` condition, e.g.
|
|
19
|
+
# `if (req.query.state !== stored)`. The deep-expression operator
|
|
20
|
+
# `<... state ...>` makes this order-agnostic and helper-aware, so
|
|
21
|
+
# `if (stored === req.query.state)` and `if (!verifyState(req.query.state))`
|
|
22
|
+
# are all safe (the previous exact-shape patterns only matched one
|
|
23
|
+
# operand order → false positives).
|
|
24
|
+
# 2. The read is captured into a variable that is later compared, e.g.
|
|
25
|
+
# `const state = url.searchParams.get("state"); ...; if (state !== stored)`.
|
|
26
|
+
# This is the most common real-world shape (the callback reads state
|
|
27
|
+
# once, then validates the local) and the inline-`if` suppressors alone
|
|
28
|
+
# miss it. The sequence patterns below match the read assigned to `$S`
|
|
29
|
+
# followed (after any statements) by an `if` that references `$S`.
|
|
22
30
|
pattern-either:
|
|
23
31
|
- patterns:
|
|
24
32
|
- pattern-either:
|
|
25
33
|
- pattern: '$REQ.query.state'
|
|
26
34
|
- pattern: '$REQ.body.state'
|
|
27
35
|
- pattern: '$URL.searchParams.get("state")'
|
|
36
|
+
# (1) read used directly inside an `if` condition
|
|
28
37
|
- pattern-not-inside: |
|
|
29
38
|
if (<... $REQ.query.state ...>) { ... }
|
|
30
39
|
- pattern-not-inside: |
|
|
31
40
|
if (<... $REQ.body.state ...>) { ... }
|
|
32
41
|
- pattern-not-inside: |
|
|
33
42
|
if (<... $URL.searchParams.get("state") ...>) { ... }
|
|
43
|
+
# (2) read captured into a local that is later validated in an `if`
|
|
44
|
+
- pattern-not-inside: |
|
|
45
|
+
$S = $REQ.query.state;
|
|
46
|
+
...
|
|
47
|
+
if (<... $S ...>) { ... }
|
|
48
|
+
- pattern-not-inside: |
|
|
49
|
+
$S = $REQ.body.state;
|
|
50
|
+
...
|
|
51
|
+
if (<... $S ...>) { ... }
|
|
52
|
+
- pattern-not-inside: |
|
|
53
|
+
$S = $URL.searchParams.get("state");
|
|
54
|
+
...
|
|
55
|
+
if (<... $S ...>) { ... }
|
|
34
56
|
metadata:
|
|
35
57
|
oauthlint-rule-id: AUTH-OAUTH-007
|
|
36
58
|
oauthlint-doc-url: https://oauthlint.dev/rules/oauth-no-state-validation
|