oauthlint-rules 0.1.0 → 0.2.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/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +10 -4
- package/dist/schema.js.map +1 -1
- package/package.json +10 -3
- package/rules/cors/null-origin.yml +53 -0
- package/rules/cors/reflect-origin.yml +68 -0
- package/rules/flow/credentials-in-url.yml +37 -0
- package/rules/flow/secret-in-log.yml +95 -0
- package/rules/flow/timing-unsafe-compare.yml +62 -16
- package/rules/flow/weak-bcrypt-rounds.yml +41 -0
- package/rules/flow/weak-password-hash.yml +74 -0
- package/rules/go/cookie/insecure.yml +35 -0
- package/rules/go/cors/allow-all.yml +44 -0
- package/rules/go/crypto/bcrypt-low-cost.yml +37 -0
- package/rules/go/crypto/weak-cipher.yml +38 -0
- package/rules/go/crypto/weak-password-hash.yml +47 -0
- package/rules/go/flow/weak-rand.yml +61 -0
- package/rules/go/jwt/hardcoded-secret.yml +37 -0
- package/rules/go/jwt/none-algorithm.yml +38 -0
- package/rules/go/jwt/parse-unverified.yml +33 -0
- package/rules/go/jwt/unchecked-method.yml +72 -0
- package/rules/go/tls/insecure-skip-verify.yml +32 -0
- package/rules/go/tls/min-version.yml +36 -0
- package/rules/java/cookie/insecure.yml +31 -0
- package/rules/java/cors/allow-all.yml +47 -0
- package/rules/java/crypto/ecb-mode.yml +42 -0
- package/rules/java/crypto/insecure-random.yml +62 -0
- package/rules/java/crypto/weak-password-hash.yml +47 -0
- package/rules/java/jwt/hardcoded-secret.yml +43 -0
- package/rules/java/jwt/unsigned-jwt.yml +41 -0
- package/rules/java/session/fixation-disabled.yml +34 -0
- package/rules/java/tls/trust-all-certs.yml +48 -0
- package/rules/java/web/csrf-disabled.yml +35 -0
- package/rules/java/web/frame-options-disabled.yml +37 -0
- package/rules/java/web/permit-all.yml +36 -0
- package/rules/jwt/decode-without-verify.yml +56 -0
- package/rules/jwt/no-algorithms-allowlist.yml +38 -0
- package/rules/oauth/no-nonce.yml +55 -0
- package/rules/oauth/pkce-plain.yml +40 -0
- package/rules/py/cookie/insecure-flags.yml +41 -0
- package/rules/py/flow/csrf-exempt.yml +39 -0
- package/rules/py/flow/debug-enabled.yml +37 -0
- package/rules/py/flow/insecure-random-token.yml +51 -0
- package/rules/py/flow/requests-verify-disabled.yml +50 -0
- package/rules/py/flow/weak-password-hash.yml +64 -0
- package/rules/py/jwt/alg-none.yml +41 -0
- package/rules/py/jwt/hardcoded-secret.yml +40 -0
- package/rules/py/jwt/no-algorithms.yml +45 -0
- package/rules/py/jwt/no-verify.yml +37 -0
- package/rules/py/secret/django-hardcoded-key.yml +32 -0
- package/rules/py/secret/flask-hardcoded-key.yml +33 -0
- package/rules/rust/cookie/insecure.yml +35 -0
- package/rules/rust/cors/permissive.yml +38 -0
- package/rules/rust/crypto/bcrypt-low-cost.yml +40 -0
- package/rules/rust/crypto/weak-cipher.yml +41 -0
- package/rules/rust/crypto/weak-password-hash.yml +46 -0
- package/rules/rust/flow/timing-unsafe-compare.yml +77 -0
- package/rules/rust/jwt/disable-signature-validation.yml +31 -0
- package/rules/rust/jwt/hardcoded-secret.yml +41 -0
- package/rules/rust/jwt/no-aud-validation.yml +34 -0
- package/rules/rust/jwt/no-expiration-validation.yml +35 -0
- package/rules/rust/tls/accept-invalid-certs.yml +30 -0
- package/rules/rust/tls/accept-invalid-hostnames.yml +32 -0
- package/rules/secret/public-env-secret.yml +58 -0
- package/rules/session/hardcoded-secret.yml +42 -0
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,cAAc,yCAAuC,CAAC;AACnE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,cAAc,yCAAuC,CAAC;AACnE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAUxE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CpB,CAAC;AAEJ,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
|
package/dist/schema.js
CHANGED
|
@@ -11,8 +11,10 @@ export const SeveritySchema = z.enum(['INFO', 'WARNING', 'ERROR']);
|
|
|
11
11
|
* Claude Code, Copilot, Gemini Code Assist).
|
|
12
12
|
*/
|
|
13
13
|
export const OAuthLintMetadataSchema = z.object({
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// AUTH-<CATEGORY>-<NNN> for the JS/TS pack (e.g. AUTH-JWT-001); language
|
|
15
|
+
// packs add a language segment (e.g. AUTH-PY-JWT-001).
|
|
16
|
+
'oauthlint-rule-id': z.string().regex(/^AUTH-[A-Z]+(-[A-Z]+)*-\d{3}$/, {
|
|
17
|
+
message: 'oauthlint-rule-id must match AUTH-<CATEGORY>-<NNN> (optionally AUTH-<LANG>-<CATEGORY>-<NNN>)',
|
|
16
18
|
}),
|
|
17
19
|
'oauthlint-doc-url': z.string().url(),
|
|
18
20
|
category: z.literal('security'),
|
|
@@ -32,8 +34,12 @@ export const OAuthLintMetadataSchema = z.object({
|
|
|
32
34
|
const PatternEntrySchema = z.lazy(() => z.union([z.string(), z.record(z.string(), z.unknown()), z.array(PatternEntrySchema)]));
|
|
33
35
|
export const RuleSchema = z
|
|
34
36
|
.object({
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
// auth.<category>.<name> for the JS/TS pack; language packs prepend a
|
|
38
|
+
// language segment — auth.<lang>.<category>.<name> (e.g. auth.py.jwt.no-verify).
|
|
39
|
+
// The optional middle segment keeps every existing JS/TS id (and its
|
|
40
|
+
// published doc URL) untouched while making the scheme language-scalable.
|
|
41
|
+
id: z.string().regex(/^auth\.([a-z][a-z0-9]*\.)?[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/, {
|
|
42
|
+
message: 'Rule id must follow auth.<category>.<name> or auth.<lang>.<category>.<name> (kebab-case)',
|
|
37
43
|
}),
|
|
38
44
|
languages: z.array(z.string()).min(1),
|
|
39
45
|
severity: SeveritySchema,
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AAGnE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauthlint-rules",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
@@ -22,21 +22,28 @@
|
|
|
22
22
|
"keywords": [
|
|
23
23
|
"oauthlint",
|
|
24
24
|
"semgrep",
|
|
25
|
+
"semgrep-rules",
|
|
25
26
|
"oauth",
|
|
27
|
+
"oauth2",
|
|
26
28
|
"oidc",
|
|
27
29
|
"jwt",
|
|
28
30
|
"security",
|
|
31
|
+
"appsec",
|
|
29
32
|
"sast",
|
|
33
|
+
"static-analysis",
|
|
34
|
+
"linter",
|
|
35
|
+
"cwe",
|
|
36
|
+
"owasp",
|
|
30
37
|
"auth",
|
|
31
38
|
"ai-code"
|
|
32
39
|
],
|
|
33
40
|
"dependencies": {
|
|
34
|
-
"
|
|
41
|
+
"fast-glob": "3.3.3",
|
|
42
|
+
"yaml": "2.9.0",
|
|
35
43
|
"zod": "3.24.1"
|
|
36
44
|
},
|
|
37
45
|
"devDependencies": {
|
|
38
46
|
"@types/node": "22.10.5",
|
|
39
|
-
"fast-glob": "3.3.3",
|
|
40
47
|
"typescript": "5.7.3",
|
|
41
48
|
"vitest": "2.1.9"
|
|
42
49
|
},
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.cors.null-origin
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: ERROR
|
|
7
|
+
message: |
|
|
8
|
+
CORS is configured to allow the literal origin `'null'`. Sandboxed
|
|
9
|
+
iframes, documents loaded from `file://`, and certain cross-origin
|
|
10
|
+
redirects send `Origin: null`. Adding the string `'null'` to your
|
|
11
|
+
CORS policy therefore grants cross-origin access to ANY such context
|
|
12
|
+
— including an attacker's sandboxed iframe — which defeats the
|
|
13
|
+
same-origin policy. Combined with credentials this becomes a
|
|
14
|
+
CSRF / data-exfiltration primitive.
|
|
15
|
+
|
|
16
|
+
The `'null'` origin is not a safe sentinel and cannot be trusted:
|
|
17
|
+
it is not bound to any host. Remove it from the allowlist entirely.
|
|
18
|
+
|
|
19
|
+
Use an explicit allowlist of real, trusted origins instead:
|
|
20
|
+
- `cors({ origin: 'https://app.example.com', credentials: true })`
|
|
21
|
+
- `cors({ origin: ['https://app.example.com', 'https://admin.example.com'] })`
|
|
22
|
+
|
|
23
|
+
Never set `Access-Control-Allow-Origin` to the string `'null'` and
|
|
24
|
+
never include `'null'` in a CORS origin allowlist.
|
|
25
|
+
pattern-either:
|
|
26
|
+
# Manual header write: ACAO set to the literal string "null".
|
|
27
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", "null")
|
|
28
|
+
- pattern: $RES.header("Access-Control-Allow-Origin", "null")
|
|
29
|
+
- pattern: $RES.set("Access-Control-Allow-Origin", "null")
|
|
30
|
+
# express cors() middleware: origin is the literal string "null".
|
|
31
|
+
- pattern: 'cors({ ..., origin: "null", ... })'
|
|
32
|
+
# express cors() middleware: allowlist array literal that contains "null".
|
|
33
|
+
- pattern: 'cors({ ..., origin: [..., "null", ...], ... })'
|
|
34
|
+
# An allowlist array variable containing "null" that is then used as the
|
|
35
|
+
# cors() origin (handles the indirected `const allowed = [..., 'null']`).
|
|
36
|
+
- patterns:
|
|
37
|
+
- pattern: 'cors({ ..., origin: $ALLOWED, ... })'
|
|
38
|
+
- pattern-inside: |
|
|
39
|
+
$ALLOWED = [..., "null", ...];
|
|
40
|
+
...
|
|
41
|
+
metadata:
|
|
42
|
+
oauthlint-rule-id: AUTH-CORS-003
|
|
43
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/cors-null-origin
|
|
44
|
+
category: security
|
|
45
|
+
cwe: CWE-942
|
|
46
|
+
owasp: A05:2021
|
|
47
|
+
llm-prevalence: MEDIUM
|
|
48
|
+
technology:
|
|
49
|
+
- cors
|
|
50
|
+
- express
|
|
51
|
+
references:
|
|
52
|
+
- https://portswigger.net/web-security/cors
|
|
53
|
+
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.cors.reflect-origin
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: ERROR
|
|
7
|
+
message: |
|
|
8
|
+
CORS is configured to echo the request's `Origin` back as
|
|
9
|
+
`Access-Control-Allow-Origin`. Reflecting the incoming origin is
|
|
10
|
+
functionally identical to allowing EVERY origin: any site can make
|
|
11
|
+
cross-origin requests and read the responses. Combined with
|
|
12
|
+
credentials this becomes a CSRF / account-takeover primitive, because
|
|
13
|
+
the browser will attach the victim's cookies to the attacker-controlled
|
|
14
|
+
request.
|
|
15
|
+
|
|
16
|
+
This is distinct from a literal `*` wildcard — here the origin is
|
|
17
|
+
reflected dynamically, which silently defeats the same-origin policy
|
|
18
|
+
while looking "scoped" in code review.
|
|
19
|
+
|
|
20
|
+
Use an explicit allowlist of trusted origins instead:
|
|
21
|
+
- `cors({ origin: 'https://app.example.com', credentials: true })`
|
|
22
|
+
- `cors({ origin: ['https://app.example.com', 'https://admin.example.com'] })`
|
|
23
|
+
- a callback that validates against an allowlist before calling
|
|
24
|
+
`cb(null, true)`.
|
|
25
|
+
|
|
26
|
+
Never set `Access-Control-Allow-Origin` to `req.headers.origin`, never
|
|
27
|
+
use `origin: true`, and never write a callback that unconditionally
|
|
28
|
+
returns `cb(null, true)`.
|
|
29
|
+
pattern-either:
|
|
30
|
+
# Manual header write: ACAO set to the request's origin in any form
|
|
31
|
+
# (req.headers.origin, req.header('origin'), req.get('Origin'), ...).
|
|
32
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", $REQ.headers.origin)
|
|
33
|
+
- pattern: $RES.header("Access-Control-Allow-Origin", $REQ.headers.origin)
|
|
34
|
+
- pattern: $RES.set("Access-Control-Allow-Origin", $REQ.headers.origin)
|
|
35
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", $REQ.headers["origin"])
|
|
36
|
+
- pattern: $RES.header("Access-Control-Allow-Origin", $REQ.headers["origin"])
|
|
37
|
+
- pattern: $RES.set("Access-Control-Allow-Origin", $REQ.headers["origin"])
|
|
38
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", $REQ.header("origin"))
|
|
39
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", $REQ.get("origin"))
|
|
40
|
+
- pattern: $RES.setHeader("Access-Control-Allow-Origin", $REQ.get("Origin"))
|
|
41
|
+
# express cors() middleware: origin: true echoes the request origin back.
|
|
42
|
+
- pattern: 'cors({ ..., origin: true, ... })'
|
|
43
|
+
# express cors() middleware: a callback that unconditionally allows
|
|
44
|
+
# every origin (cb(null, true) regardless of the origin argument).
|
|
45
|
+
- pattern: 'cors({ ..., origin: ($O, $CB) => $CB(null, true), ... })'
|
|
46
|
+
- patterns:
|
|
47
|
+
- pattern: |
|
|
48
|
+
cors({ ..., origin: ($O, $CB) => { ... }, ... })
|
|
49
|
+
- pattern-inside: |
|
|
50
|
+
cors({ ..., origin: ($O, $CB) => { ... return $CB(null, true); }, ... })
|
|
51
|
+
- patterns:
|
|
52
|
+
- pattern: |
|
|
53
|
+
cors({ ..., origin: function ($O, $CB) { ... }, ... })
|
|
54
|
+
- pattern-inside: |
|
|
55
|
+
cors({ ..., origin: function ($O, $CB) { ... return $CB(null, true); }, ... })
|
|
56
|
+
metadata:
|
|
57
|
+
oauthlint-rule-id: AUTH-CORS-002
|
|
58
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/cors-reflect-origin
|
|
59
|
+
category: security
|
|
60
|
+
cwe: CWE-942
|
|
61
|
+
owasp: A05:2021
|
|
62
|
+
llm-prevalence: MEDIUM
|
|
63
|
+
technology:
|
|
64
|
+
- cors
|
|
65
|
+
- express
|
|
66
|
+
references:
|
|
67
|
+
- https://portswigger.net/web-security/cors
|
|
68
|
+
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.flow.credentials-in-url
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: ERROR
|
|
7
|
+
message: |
|
|
8
|
+
A secret credential is placed in a URL query string. URLs leak into
|
|
9
|
+
server access logs, reverse-proxy logs, browser history, and the
|
|
10
|
+
`Referer` header (sent to every third-party CDN, analytics, and ad
|
|
11
|
+
script on the destination page) — so any of `password`, `client_secret`,
|
|
12
|
+
`api_key`, `apikey`, or `secret` in the query is a leaked credential.
|
|
13
|
+
|
|
14
|
+
Send credentials in the POST request body or in the `Authorization`
|
|
15
|
+
header. Never in the URL.
|
|
16
|
+
|
|
17
|
+
OWASP ASVS V3.2 / CWE-598 explicitly forbid credentials in URL
|
|
18
|
+
parameters.
|
|
19
|
+
# `access_token` is deliberately NOT matched: passing it as a query
|
|
20
|
+
# parameter to a provider's userinfo/API endpoint is a legitimate (and
|
|
21
|
+
# sometimes mandated) server-side OAuth pattern — flagging it produces
|
|
22
|
+
# false positives on real provider integrations. Bearer tokens in URLs
|
|
23
|
+
# are still covered by auth.jwt.in-url for JWT-shaped tokens.
|
|
24
|
+
pattern-either:
|
|
25
|
+
# Credential name in a query string (literal or template-built URL).
|
|
26
|
+
- pattern-regex: '[?&](?:password|client_secret|api_key|apikey|secret)='
|
|
27
|
+
# URLSearchParams / params builder: .set('client_secret', ...) / .append('secret', ...)
|
|
28
|
+
- pattern-regex: '\.(?:set|append)\s*\(\s*[''"`](?:password|client_secret|api_key|apikey|secret)[''"`]'
|
|
29
|
+
metadata:
|
|
30
|
+
oauthlint-rule-id: AUTH-FLOW-009
|
|
31
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/flow-credentials-in-url
|
|
32
|
+
category: security
|
|
33
|
+
cwe: CWE-598
|
|
34
|
+
owasp: API2:2023
|
|
35
|
+
llm-prevalence: HIGH
|
|
36
|
+
references:
|
|
37
|
+
- https://owasp.org/www-project-application-security-verification-standard/
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.flow.secret-in-log
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: WARNING
|
|
7
|
+
message: |
|
|
8
|
+
A secret-shaped value (`password`, `token`, `secret`, `apiKey`,
|
|
9
|
+
`accessToken`, `refreshToken`, `privateKey`, `clientSecret`, …) is
|
|
10
|
+
being passed to a logging call (`console.*` or `logger.*`). Logs are
|
|
11
|
+
routinely written to files, shipped to aggregators (Datadog, Splunk,
|
|
12
|
+
CloudWatch) and read by people who should never see the raw secret —
|
|
13
|
+
this is a textbook credential leak.
|
|
14
|
+
|
|
15
|
+
Never log secrets. Redact or mask them before logging
|
|
16
|
+
(`token.slice(0, 4) + '…'`), log a non-sensitive identifier instead
|
|
17
|
+
(a user id, a key id), or drop the field entirely.
|
|
18
|
+
# FP control: we only fire when a logging call receives an *identifier*
|
|
19
|
+
# whose name ends with a secret word. The argument may sit at any position
|
|
20
|
+
# (`$...A, $SECRET, $...B`). The `metavariable-pattern` with a bare-identifier
|
|
21
|
+
# `pattern-regex` forces `$SECRET` to be an identifier (or, in the template
|
|
22
|
+
# arms, an interpolated identifier) — NOT a string literal or member access,
|
|
23
|
+
# so status messages like `console.log('password updated')`,
|
|
24
|
+
# `console.log('reset token sent')` and `console.log(user.id)` are excluded.
|
|
25
|
+
# The outer `metavariable-regex` then requires the identifier name itself to
|
|
26
|
+
# match a secret word (re-using the suffix list from
|
|
27
|
+
# auth.flow.timing-unsafe-compare).
|
|
28
|
+
pattern-either:
|
|
29
|
+
# console.log/info/debug/warn/error(... secretVar ...) — secret at any position
|
|
30
|
+
- patterns:
|
|
31
|
+
- pattern-either:
|
|
32
|
+
- pattern: console.log($...A, $SECRET, $...B)
|
|
33
|
+
- pattern: console.info($...A, $SECRET, $...B)
|
|
34
|
+
- pattern: console.debug($...A, $SECRET, $...B)
|
|
35
|
+
- pattern: console.warn($...A, $SECRET, $...B)
|
|
36
|
+
- pattern: console.error($...A, $SECRET, $...B)
|
|
37
|
+
- metavariable-pattern:
|
|
38
|
+
metavariable: $SECRET
|
|
39
|
+
pattern-regex: ^[A-Za-z_$][A-Za-z0-9_$]*$
|
|
40
|
+
- metavariable-regex:
|
|
41
|
+
metavariable: $SECRET
|
|
42
|
+
regex: (?i)^(?:password|passwd|pwd|secret|token|apikey|api_key|accesstoken|refreshtoken|privatekey|clientsecret)$
|
|
43
|
+
# logger.<level>(... secretVar ...)
|
|
44
|
+
- patterns:
|
|
45
|
+
- pattern: $LOG.$LEVEL($...A, $SECRET, $...B)
|
|
46
|
+
- metavariable-regex:
|
|
47
|
+
metavariable: $LOG
|
|
48
|
+
regex: (?i)^.*log(?:ger)?$
|
|
49
|
+
- metavariable-regex:
|
|
50
|
+
metavariable: $LEVEL
|
|
51
|
+
regex: ^(?:log|info|debug|warn|warning|error|trace|fatal|verbose|silly)$
|
|
52
|
+
- metavariable-pattern:
|
|
53
|
+
metavariable: $SECRET
|
|
54
|
+
pattern-regex: ^[A-Za-z_$][A-Za-z0-9_$]*$
|
|
55
|
+
- metavariable-regex:
|
|
56
|
+
metavariable: $SECRET
|
|
57
|
+
regex: (?i)^(?:password|passwd|pwd|secret|token|apikey|api_key|accesstoken|refreshtoken|privatekey|clientsecret)$
|
|
58
|
+
# Template-literal interpolation: console.log(`token=${token}`)
|
|
59
|
+
- patterns:
|
|
60
|
+
- pattern-either:
|
|
61
|
+
- pattern: console.log(`...${$SECRET}...`)
|
|
62
|
+
- pattern: console.info(`...${$SECRET}...`)
|
|
63
|
+
- pattern: console.debug(`...${$SECRET}...`)
|
|
64
|
+
- pattern: console.warn(`...${$SECRET}...`)
|
|
65
|
+
- pattern: console.error(`...${$SECRET}...`)
|
|
66
|
+
- metavariable-pattern:
|
|
67
|
+
metavariable: $SECRET
|
|
68
|
+
pattern-regex: ^[A-Za-z_$][A-Za-z0-9_$]*$
|
|
69
|
+
- metavariable-regex:
|
|
70
|
+
metavariable: $SECRET
|
|
71
|
+
regex: (?i)^(?:password|passwd|pwd|secret|token|apikey|api_key|accesstoken|refreshtoken|privatekey|clientsecret)$
|
|
72
|
+
- patterns:
|
|
73
|
+
- pattern: $LOG.$LEVEL(`...${$SECRET}...`)
|
|
74
|
+
- metavariable-regex:
|
|
75
|
+
metavariable: $LOG
|
|
76
|
+
regex: (?i)^.*log(?:ger)?$
|
|
77
|
+
- metavariable-regex:
|
|
78
|
+
metavariable: $LEVEL
|
|
79
|
+
regex: ^(?:log|info|debug|warn|warning|error|trace|fatal|verbose|silly)$
|
|
80
|
+
- metavariable-pattern:
|
|
81
|
+
metavariable: $SECRET
|
|
82
|
+
pattern-regex: ^[A-Za-z_$][A-Za-z0-9_$]*$
|
|
83
|
+
- metavariable-regex:
|
|
84
|
+
metavariable: $SECRET
|
|
85
|
+
regex: (?i)^(?:password|passwd|pwd|secret|token|apikey|api_key|accesstoken|refreshtoken|privatekey|clientsecret)$
|
|
86
|
+
metadata:
|
|
87
|
+
oauthlint-rule-id: AUTH-FLOW-008
|
|
88
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/flow-secret-in-log
|
|
89
|
+
category: security
|
|
90
|
+
cwe: CWE-532
|
|
91
|
+
owasp: API8:2023
|
|
92
|
+
llm-prevalence: HIGH
|
|
93
|
+
references:
|
|
94
|
+
- https://cwe.mitre.org/data/definitions/532.html
|
|
95
|
+
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
|
|
@@ -38,14 +38,37 @@ rules:
|
|
|
38
38
|
- pattern-not: 'typeof $X !== $Y'
|
|
39
39
|
- pattern-not: 'typeof $X == $Y'
|
|
40
40
|
- pattern-not: 'typeof $X != $Y'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
- pattern-not:
|
|
41
|
+
# A secret-named value compared to a *literal* is never the
|
|
42
|
+
# timing target: a literal baked into source is already public,
|
|
43
|
+
# so there is no secret to leak byte-by-byte. This covers demo
|
|
44
|
+
# passwords (`pw !== "password"`), feature flags
|
|
45
|
+
# (`idToken === false`) and status checks — the dominant FP
|
|
46
|
+
# class on next-auth in real-world validation. `"..."` matches
|
|
47
|
+
# any string literal (including the empty string).
|
|
48
|
+
- pattern-not: '$X === "..."'
|
|
49
|
+
- pattern-not: '$X !== "..."'
|
|
50
|
+
- pattern-not: '$X == "..."'
|
|
51
|
+
- pattern-not: '$X != "..."'
|
|
52
|
+
- pattern-not: '"..." === $X'
|
|
53
|
+
- pattern-not: '"..." !== $X'
|
|
54
|
+
- pattern-not: '"..." == $X'
|
|
55
|
+
- pattern-not: '"..." != $X'
|
|
56
|
+
- pattern-not: '$X === true'
|
|
57
|
+
- pattern-not: '$X !== true'
|
|
58
|
+
- pattern-not: '$X == true'
|
|
59
|
+
- pattern-not: '$X != true'
|
|
60
|
+
- pattern-not: '$X === false'
|
|
61
|
+
- pattern-not: '$X !== false'
|
|
62
|
+
- pattern-not: '$X == false'
|
|
63
|
+
- pattern-not: '$X != false'
|
|
64
|
+
- pattern-not: 'true === $X'
|
|
65
|
+
- pattern-not: 'true !== $X'
|
|
66
|
+
- pattern-not: 'true == $X'
|
|
67
|
+
- pattern-not: 'true != $X'
|
|
68
|
+
- pattern-not: 'false === $X'
|
|
69
|
+
- pattern-not: 'false !== $X'
|
|
70
|
+
- pattern-not: 'false == $X'
|
|
71
|
+
- pattern-not: 'false != $X'
|
|
49
72
|
- pattern-not: '$X.length === $N'
|
|
50
73
|
- pattern-not: '$X.length !== $N'
|
|
51
74
|
- pattern-not: '$X.length == $N'
|
|
@@ -71,14 +94,37 @@ rules:
|
|
|
71
94
|
- pattern-not: 'typeof $X !== $Y'
|
|
72
95
|
- pattern-not: 'typeof $X == $Y'
|
|
73
96
|
- pattern-not: 'typeof $X != $Y'
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
- pattern-not:
|
|
97
|
+
# A secret-named value compared to a *literal* is never the
|
|
98
|
+
# timing target: a literal baked into source is already public,
|
|
99
|
+
# so there is no secret to leak byte-by-byte. This covers demo
|
|
100
|
+
# passwords (`pw !== "password"`), feature flags
|
|
101
|
+
# (`idToken === false`) and status checks — the dominant FP
|
|
102
|
+
# class on next-auth in real-world validation. `"..."` matches
|
|
103
|
+
# any string literal (including the empty string).
|
|
104
|
+
- pattern-not: '$X === "..."'
|
|
105
|
+
- pattern-not: '$X !== "..."'
|
|
106
|
+
- pattern-not: '$X == "..."'
|
|
107
|
+
- pattern-not: '$X != "..."'
|
|
108
|
+
- pattern-not: '"..." === $X'
|
|
109
|
+
- pattern-not: '"..." !== $X'
|
|
110
|
+
- pattern-not: '"..." == $X'
|
|
111
|
+
- pattern-not: '"..." != $X'
|
|
112
|
+
- pattern-not: '$X === true'
|
|
113
|
+
- pattern-not: '$X !== true'
|
|
114
|
+
- pattern-not: '$X == true'
|
|
115
|
+
- pattern-not: '$X != true'
|
|
116
|
+
- pattern-not: '$X === false'
|
|
117
|
+
- pattern-not: '$X !== false'
|
|
118
|
+
- pattern-not: '$X == false'
|
|
119
|
+
- pattern-not: '$X != false'
|
|
120
|
+
- pattern-not: 'true === $X'
|
|
121
|
+
- pattern-not: 'true !== $X'
|
|
122
|
+
- pattern-not: 'true == $X'
|
|
123
|
+
- pattern-not: 'true != $X'
|
|
124
|
+
- pattern-not: 'false === $X'
|
|
125
|
+
- pattern-not: 'false !== $X'
|
|
126
|
+
- pattern-not: 'false == $X'
|
|
127
|
+
- pattern-not: 'false != $X'
|
|
82
128
|
- pattern-not: '$X.length === $N'
|
|
83
129
|
- pattern-not: '$X.length !== $N'
|
|
84
130
|
- pattern-not: '$X.length == $N'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.flow.weak-bcrypt-rounds
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: WARNING
|
|
7
|
+
message: |
|
|
8
|
+
A bcrypt cost factor below 10 was used. A low work factor makes the
|
|
9
|
+
hash cheap to compute, which lets an attacker brute-force stolen
|
|
10
|
+
password hashes far too quickly. OWASP recommends a bcrypt cost of at
|
|
11
|
+
least 10, and ≥ 12 for new applications, tuned so a single hash takes
|
|
12
|
+
roughly 250ms on your hardware.
|
|
13
|
+
|
|
14
|
+
Common LLM-generated mistake: `bcrypt.hash(pw, 8)` or
|
|
15
|
+
`bcrypt.genSalt(5)` because the literal "looks fast enough". Raise the
|
|
16
|
+
cost factor to 12 or higher.
|
|
17
|
+
patterns:
|
|
18
|
+
- pattern-either:
|
|
19
|
+
- pattern: bcrypt.hash($PW, $N)
|
|
20
|
+
- pattern: bcrypt.hash($PW, $N, ...)
|
|
21
|
+
- pattern: bcrypt.hashSync($PW, $N)
|
|
22
|
+
- pattern: bcrypt.hashSync($PW, $N, ...)
|
|
23
|
+
- pattern: bcrypt.genSalt($N)
|
|
24
|
+
- pattern: bcrypt.genSalt($N, ...)
|
|
25
|
+
- pattern: bcrypt.genSaltSync($N)
|
|
26
|
+
- pattern: bcrypt.genSaltSync($N, ...)
|
|
27
|
+
- metavariable-comparison:
|
|
28
|
+
metavariable: $N
|
|
29
|
+
comparison: $N < 10
|
|
30
|
+
metadata:
|
|
31
|
+
oauthlint-rule-id: AUTH-FLOW-007
|
|
32
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/flow-weak-bcrypt-rounds
|
|
33
|
+
category: security
|
|
34
|
+
cwe: CWE-916
|
|
35
|
+
owasp: A02:2021
|
|
36
|
+
llm-prevalence: MEDIUM
|
|
37
|
+
technology:
|
|
38
|
+
- bcrypt
|
|
39
|
+
- bcryptjs
|
|
40
|
+
references:
|
|
41
|
+
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.flow.weak-password-hash
|
|
3
|
+
languages:
|
|
4
|
+
- javascript
|
|
5
|
+
- typescript
|
|
6
|
+
severity: ERROR
|
|
7
|
+
message: |
|
|
8
|
+
A password is being hashed with a fast, general-purpose hash
|
|
9
|
+
(MD5/SHA-1/SHA-256/SHA-512 via `crypto.createHash`). These functions
|
|
10
|
+
are designed to be FAST, so an attacker who steals the database can
|
|
11
|
+
brute-force billions of candidate passwords per second on commodity
|
|
12
|
+
GPUs. Salting alone does not fix this.
|
|
13
|
+
|
|
14
|
+
Use a dedicated, deliberately-slow password hashing function instead:
|
|
15
|
+
`bcrypt`, `argon2` (argon2id), or `scrypt` (`crypto.scrypt`). These add
|
|
16
|
+
a tunable work factor that makes large-scale brute-forcing infeasible.
|
|
17
|
+
# Anchored on the *name* of the hashed value: we only flag when the value
|
|
18
|
+
# passed to `.update(...)` is named like a password. This avoids the
|
|
19
|
+
# common FP class — checksums, file digests (`createHash('sha256')
|
|
20
|
+
# .update(fileBuffer)`), and HMAC signatures (`createHmac`) are NOT
|
|
21
|
+
# password hashing and must not be flagged.
|
|
22
|
+
pattern-either:
|
|
23
|
+
# Chained form: createHash('md5').update(password).digest(...)
|
|
24
|
+
- patterns:
|
|
25
|
+
- pattern-either:
|
|
26
|
+
- pattern: crypto.createHash($ALGO).update($PW).digest(...)
|
|
27
|
+
- pattern: createHash($ALGO).update($PW).digest(...)
|
|
28
|
+
- pattern: crypto.createHash($ALGO).update($PW, ...).digest(...)
|
|
29
|
+
- pattern: createHash($ALGO).update($PW, ...).digest(...)
|
|
30
|
+
- metavariable-regex:
|
|
31
|
+
metavariable: $ALGO
|
|
32
|
+
regex: ^(["'])(?i)(md5|sha1|sha-1|sha224|sha256|sha-256|sha384|sha512|sha-512)\1$
|
|
33
|
+
- metavariable-pattern:
|
|
34
|
+
metavariable: $PW
|
|
35
|
+
patterns:
|
|
36
|
+
- pattern: $PWNAME
|
|
37
|
+
- metavariable-regex:
|
|
38
|
+
metavariable: $PWNAME
|
|
39
|
+
regex: (?i)^.*(password|passwd|pwd)$
|
|
40
|
+
# Two-step form:
|
|
41
|
+
# const h = crypto.createHash('sha256'); h.update(password); h.digest(...)
|
|
42
|
+
- patterns:
|
|
43
|
+
- pattern-either:
|
|
44
|
+
- pattern: $H.update($PW)
|
|
45
|
+
- pattern: $H.update($PW, ...)
|
|
46
|
+
- metavariable-pattern:
|
|
47
|
+
metavariable: $PW
|
|
48
|
+
patterns:
|
|
49
|
+
- pattern: $PWNAME
|
|
50
|
+
- metavariable-regex:
|
|
51
|
+
metavariable: $PWNAME
|
|
52
|
+
regex: (?i)^.*(password|passwd|pwd)$
|
|
53
|
+
- pattern-either:
|
|
54
|
+
- pattern-inside: |
|
|
55
|
+
$H = crypto.createHash($ALGO);
|
|
56
|
+
...
|
|
57
|
+
- pattern-inside: |
|
|
58
|
+
$H = createHash($ALGO);
|
|
59
|
+
...
|
|
60
|
+
- metavariable-regex:
|
|
61
|
+
metavariable: $ALGO
|
|
62
|
+
regex: ^(["'])(?i)(md5|sha1|sha-1|sha224|sha256|sha-256|sha384|sha512|sha-512)\1$
|
|
63
|
+
metadata:
|
|
64
|
+
oauthlint-rule-id: AUTH-FLOW-006
|
|
65
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/flow-weak-password-hash
|
|
66
|
+
category: security
|
|
67
|
+
cwe: CWE-916
|
|
68
|
+
owasp: A02:2021
|
|
69
|
+
llm-prevalence: HIGH
|
|
70
|
+
technology:
|
|
71
|
+
- node:crypto
|
|
72
|
+
references:
|
|
73
|
+
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
74
|
+
- https://cwe.mitre.org/data/definitions/916.html
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.cookie.insecure
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
A session/auth `http.Cookie` is created with a security attribute
|
|
8
|
+
explicitly disabled (`Secure: false` or `HttpOnly: false`). With
|
|
9
|
+
`Secure: false` the cookie is sent over plain HTTP, so a network
|
|
10
|
+
attacker can read the session token. With `HttpOnly: false` the cookie
|
|
11
|
+
is readable from JavaScript, so any XSS can steal it. For OAuth/OIDC
|
|
12
|
+
this exposes session and token cookies to theft and hijacking.
|
|
13
|
+
|
|
14
|
+
Set `Secure: true` and `HttpOnly: true` on auth cookies, and add an
|
|
15
|
+
appropriate `SameSite` mode (for example `SameSite: http.SameSiteLaxMode`).
|
|
16
|
+
# Matches only the literal `false`. `Secure: true`, `HttpOnly: true`, and
|
|
17
|
+
# the absence of the field are not flagged. Works for `http.Cookie{...}`
|
|
18
|
+
# and `&http.Cookie{...}` (the composite literal matches inside the
|
|
19
|
+
# address-of).
|
|
20
|
+
patterns:
|
|
21
|
+
- pattern-either:
|
|
22
|
+
- pattern: 'http.Cookie{..., Secure: false, ...}'
|
|
23
|
+
- pattern: 'http.Cookie{..., HttpOnly: false, ...}'
|
|
24
|
+
metadata:
|
|
25
|
+
oauthlint-rule-id: AUTH-GO-COOKIE-001
|
|
26
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-cookie-insecure
|
|
27
|
+
category: security
|
|
28
|
+
cwe: CWE-614
|
|
29
|
+
owasp: A05:2021
|
|
30
|
+
llm-prevalence: HIGH
|
|
31
|
+
technology:
|
|
32
|
+
- net/http
|
|
33
|
+
references:
|
|
34
|
+
- https://pkg.go.dev/net/http#Cookie
|
|
35
|
+
- https://cwe.mitre.org/data/definitions/614.html
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: auth.go.cors.allow-all
|
|
3
|
+
languages:
|
|
4
|
+
- go
|
|
5
|
+
severity: ERROR
|
|
6
|
+
message: |
|
|
7
|
+
CORS is configured to allow every origin with the wildcard `*`. Any
|
|
8
|
+
website can then make cross-origin requests to this endpoint, defeating
|
|
9
|
+
the same-origin policy (CWE-942). This is a common AI-generated mistake —
|
|
10
|
+
`AllowAllOrigins: true`, `AllowedOrigins: []string{"*"}`, or a raw
|
|
11
|
+
`Access-Control-Allow-Origin: *` header is pasted in to "make the browser
|
|
12
|
+
call work" and the intended scope is never added. Combined with
|
|
13
|
+
credentials this becomes an account-takeover primitive that leaks
|
|
14
|
+
OAuth/OIDC tokens cross-origin.
|
|
15
|
+
|
|
16
|
+
Restrict CORS to an explicit allowlist of trusted origins instead, for
|
|
17
|
+
example `AllowOrigins: []string{"https://app.example.com"}` (gin-contrib),
|
|
18
|
+
`AllowedOrigins: []string{"https://app.example.com"}` (rs/cors), or
|
|
19
|
+
`w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com")`.
|
|
20
|
+
# Covers gin-contrib/cors (`cors.Config{..., AllowAllOrigins: true, ...}`),
|
|
21
|
+
# rs/cors (`cors.Options{..., AllowedOrigins: []string{..., "*", ...}, ...}`
|
|
22
|
+
# whose list literal contains the wildcard `"*"`), and the raw header
|
|
23
|
+
# `$W.Header().Set("Access-Control-Allow-Origin", "*")`. Only the wildcard /
|
|
24
|
+
# `AllowAllOrigins: true` is flagged — explicit origins such as
|
|
25
|
+
# `"https://app.example.com"` are not.
|
|
26
|
+
pattern-either:
|
|
27
|
+
- pattern: 'cors.Config{..., AllowAllOrigins: true, ...}'
|
|
28
|
+
- pattern: 'cors.Options{..., AllowedOrigins: []string{..., "*", ...}, ...}'
|
|
29
|
+
- pattern: '$W.Header().Set("Access-Control-Allow-Origin", "*")'
|
|
30
|
+
metadata:
|
|
31
|
+
oauthlint-rule-id: AUTH-GO-CORS-001
|
|
32
|
+
oauthlint-doc-url: https://oauthlint.dev/rules/go-cors-allow-all
|
|
33
|
+
category: security
|
|
34
|
+
cwe: CWE-942
|
|
35
|
+
owasp: A05:2021
|
|
36
|
+
llm-prevalence: HIGH
|
|
37
|
+
technology:
|
|
38
|
+
- gin
|
|
39
|
+
- rs-cors
|
|
40
|
+
- net/http
|
|
41
|
+
references:
|
|
42
|
+
- https://github.com/gin-contrib/cors
|
|
43
|
+
- https://github.com/rs/cors
|
|
44
|
+
- https://cwe.mitre.org/data/definitions/942.html
|