oauthlint-rules 0.2.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oauthlint-rules",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",
@@ -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\(\s*['"][^'"]*(?i:token|jwt|bearer|credential|api[_-]?key)[^'"]*['"]
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 referenced inside ANY `if`
17
- # condition — that's the validation. The deep-expression operator
18
- # `<... state ...>` makes this order-agnostic and helper-aware, so
19
- # `if (req.query.state !== stored)`, `if (stored === req.query.state)`, and
20
- # `if (!verifyState(req.query.state))` are all treated as safe (the previous
21
- # exact-shape patterns only matched one operand order → false positives).
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