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.
Files changed (65) hide show
  1. package/dist/schema.d.ts.map +1 -1
  2. package/dist/schema.js +10 -4
  3. package/dist/schema.js.map +1 -1
  4. package/package.json +10 -3
  5. package/rules/cors/null-origin.yml +53 -0
  6. package/rules/cors/reflect-origin.yml +68 -0
  7. package/rules/flow/credentials-in-url.yml +37 -0
  8. package/rules/flow/secret-in-log.yml +95 -0
  9. package/rules/flow/timing-unsafe-compare.yml +62 -16
  10. package/rules/flow/weak-bcrypt-rounds.yml +41 -0
  11. package/rules/flow/weak-password-hash.yml +74 -0
  12. package/rules/go/cookie/insecure.yml +35 -0
  13. package/rules/go/cors/allow-all.yml +44 -0
  14. package/rules/go/crypto/bcrypt-low-cost.yml +37 -0
  15. package/rules/go/crypto/weak-cipher.yml +38 -0
  16. package/rules/go/crypto/weak-password-hash.yml +47 -0
  17. package/rules/go/flow/weak-rand.yml +61 -0
  18. package/rules/go/jwt/hardcoded-secret.yml +37 -0
  19. package/rules/go/jwt/none-algorithm.yml +38 -0
  20. package/rules/go/jwt/parse-unverified.yml +33 -0
  21. package/rules/go/jwt/unchecked-method.yml +72 -0
  22. package/rules/go/tls/insecure-skip-verify.yml +32 -0
  23. package/rules/go/tls/min-version.yml +36 -0
  24. package/rules/java/cookie/insecure.yml +31 -0
  25. package/rules/java/cors/allow-all.yml +47 -0
  26. package/rules/java/crypto/ecb-mode.yml +42 -0
  27. package/rules/java/crypto/insecure-random.yml +62 -0
  28. package/rules/java/crypto/weak-password-hash.yml +47 -0
  29. package/rules/java/jwt/hardcoded-secret.yml +43 -0
  30. package/rules/java/jwt/unsigned-jwt.yml +41 -0
  31. package/rules/java/session/fixation-disabled.yml +34 -0
  32. package/rules/java/tls/trust-all-certs.yml +48 -0
  33. package/rules/java/web/csrf-disabled.yml +35 -0
  34. package/rules/java/web/frame-options-disabled.yml +37 -0
  35. package/rules/java/web/permit-all.yml +36 -0
  36. package/rules/jwt/decode-without-verify.yml +56 -0
  37. package/rules/jwt/no-algorithms-allowlist.yml +38 -0
  38. package/rules/oauth/no-nonce.yml +55 -0
  39. package/rules/oauth/pkce-plain.yml +40 -0
  40. package/rules/py/cookie/insecure-flags.yml +41 -0
  41. package/rules/py/flow/csrf-exempt.yml +39 -0
  42. package/rules/py/flow/debug-enabled.yml +37 -0
  43. package/rules/py/flow/insecure-random-token.yml +51 -0
  44. package/rules/py/flow/requests-verify-disabled.yml +50 -0
  45. package/rules/py/flow/weak-password-hash.yml +64 -0
  46. package/rules/py/jwt/alg-none.yml +41 -0
  47. package/rules/py/jwt/hardcoded-secret.yml +40 -0
  48. package/rules/py/jwt/no-algorithms.yml +45 -0
  49. package/rules/py/jwt/no-verify.yml +37 -0
  50. package/rules/py/secret/django-hardcoded-key.yml +32 -0
  51. package/rules/py/secret/flask-hardcoded-key.yml +33 -0
  52. package/rules/rust/cookie/insecure.yml +35 -0
  53. package/rules/rust/cors/permissive.yml +38 -0
  54. package/rules/rust/crypto/bcrypt-low-cost.yml +40 -0
  55. package/rules/rust/crypto/weak-cipher.yml +41 -0
  56. package/rules/rust/crypto/weak-password-hash.yml +46 -0
  57. package/rules/rust/flow/timing-unsafe-compare.yml +77 -0
  58. package/rules/rust/jwt/disable-signature-validation.yml +31 -0
  59. package/rules/rust/jwt/hardcoded-secret.yml +41 -0
  60. package/rules/rust/jwt/no-aud-validation.yml +34 -0
  61. package/rules/rust/jwt/no-expiration-validation.yml +35 -0
  62. package/rules/rust/tls/accept-invalid-certs.yml +30 -0
  63. package/rules/rust/tls/accept-invalid-hostnames.yml +32 -0
  64. package/rules/secret/public-env-secret.yml +58 -0
  65. package/rules/session/hardcoded-secret.yml +42 -0
@@ -0,0 +1,77 @@
1
+ rules:
2
+ - id: auth.rust.flow.timing-unsafe-compare
3
+ languages:
4
+ - rust
5
+ severity: WARNING
6
+ message: |
7
+ A secret-shaped value (`password`, `token`, `secret`, `apikey`,
8
+ `hmac`, `signature`, `mac`, `digest`) is being compared with `==` /
9
+ `!=`. Rust's `PartialEq` for slices and strings short-circuits on the
10
+ first differing byte, so the comparison time leaks the length of the
11
+ matching prefix — a classic timing-attack vector (CWE-208).
12
+
13
+ Use a constant-time comparison instead: `subtle::ConstantTimeEq`
14
+ (`a.ct_eq(b).into()`) or the `constant_time_eq` crate
15
+ (`constant_time_eq(a, b)`). For password verification, use a verifier
16
+ that compares in constant time for you (`argon2::Argon2::verify_password`,
17
+ `bcrypt::verify`, `scrypt`).
18
+ # We want to catch `provided_token == expected_token` but NOT the many
19
+ # legitimate non-secret comparisons that just happen to involve a
20
+ # variable whose name ends with one of our secret-looking suffixes:
21
+ # token.len() == 32 (length check)
22
+ # secret == "" (presence check, literal)
23
+ # token.is_empty() (presence check)
24
+ # token == None / Some(_) (option check)
25
+ # username == other (non-secret names)
26
+ # The pattern-not clauses below carve those out. A secret-named value
27
+ # compared to a *literal* is never the timing target: a literal baked
28
+ # into source is already public, so there is nothing to leak byte by
29
+ # byte.
30
+ pattern-either:
31
+ - patterns:
32
+ - pattern-either:
33
+ - pattern: $A == $B
34
+ - pattern: $A != $B
35
+ - metavariable-regex:
36
+ metavariable: $A
37
+ regex: (?i)^.*(password|passwd|secret|token|apikey|api_key|hmac|signature|mac|digest)$
38
+ - pattern-not: $X == "..."
39
+ - pattern-not: $X != "..."
40
+ - pattern-not: '"..." == $X'
41
+ - pattern-not: '"..." != $X'
42
+ - pattern-not: $X == None
43
+ - pattern-not: $X != None
44
+ - pattern-not: None == $X
45
+ - pattern-not: None != $X
46
+ - pattern-not: $X.len() == $N
47
+ - pattern-not: $X.len() != $N
48
+ - patterns:
49
+ - pattern-either:
50
+ - pattern: $A == $B
51
+ - pattern: $A != $B
52
+ - metavariable-regex:
53
+ metavariable: $B
54
+ regex: (?i)^.*(password|passwd|secret|token|apikey|api_key|hmac|signature|mac|digest)$
55
+ - pattern-not: $X == "..."
56
+ - pattern-not: $X != "..."
57
+ - pattern-not: '"..." == $X'
58
+ - pattern-not: '"..." != $X'
59
+ - pattern-not: $X == None
60
+ - pattern-not: $X != None
61
+ - pattern-not: None == $X
62
+ - pattern-not: None != $X
63
+ - pattern-not: $X.len() == $N
64
+ - pattern-not: $X.len() != $N
65
+ metadata:
66
+ oauthlint-rule-id: AUTH-RUST-FLOW-001
67
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-flow-timing-unsafe-compare
68
+ category: security
69
+ cwe: CWE-208
70
+ owasp: API2:2023
71
+ llm-prevalence: MEDIUM
72
+ technology:
73
+ - std
74
+ references:
75
+ - https://cwe.mitre.org/data/definitions/208.html
76
+ - https://docs.rs/subtle/latest/subtle/
77
+ - https://docs.rs/constant_time_eq/latest/constant_time_eq/
@@ -0,0 +1,31 @@
1
+ rules:
2
+ - id: auth.rust.jwt.disable-signature-validation
3
+ languages:
4
+ - rust
5
+ severity: ERROR
6
+ message: |
7
+ `Validation::insecure_disable_signature_validation()` turns off JWT
8
+ signature verification. Once disabled, `decode` accepts any token —
9
+ including ones forged or tampered with by an attacker — because the
10
+ cryptographic signature is never checked. For OAuth/OIDC this lets an
11
+ attacker mint arbitrary access tokens and identities.
12
+
13
+ Never disable signature validation. Build the validator with the
14
+ expected algorithm, e.g. `Validation::new(Algorithm::HS256)` (or the
15
+ RS/ES algorithm your issuer uses), and verify the token through
16
+ `decode::<Claims>(token, &key, &validation)`.
17
+ # Matches the insecure opt-out only. A normal `Validation` and a standard
18
+ # `decode(...)` are not flagged. `$V` is any validation expression.
19
+ pattern: $V.insecure_disable_signature_validation()
20
+ metadata:
21
+ oauthlint-rule-id: AUTH-RUST-JWT-001
22
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-jwt-disable-signature-validation
23
+ category: security
24
+ cwe: CWE-347
25
+ owasp: API2:2023
26
+ llm-prevalence: MEDIUM
27
+ technology:
28
+ - jsonwebtoken
29
+ references:
30
+ - https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.Validation.html#method.insecure_disable_signature_validation
31
+ - https://cwe.mitre.org/data/definitions/347.html
@@ -0,0 +1,41 @@
1
+ rules:
2
+ - id: auth.rust.jwt.hardcoded-secret
3
+ languages:
4
+ - rust
5
+ severity: ERROR
6
+ message: |
7
+ A JWT HMAC signing/verification key is hardcoded as a literal in a call
8
+ to jsonwebtoken's `EncodingKey::from_secret` / `DecodingKey::from_secret`.
9
+ Anyone who can read the source or git history can forge or tamper with
10
+ tokens, which is a complete authentication bypass.
11
+
12
+ Load the secret at runtime from the environment or a secret manager
13
+ instead, e.g. `let key = std::env::var("JWT_SECRET")?;` followed by
14
+ `EncodingKey::from_secret(key.as_bytes())`. Never commit signing keys to
15
+ source control.
16
+ # Only literals in key position are flagged: a byte-string literal
17
+ # `b"..."`, or a string literal coerced with `"...".as_ref()` /
18
+ # `"...".as_bytes()`. Because the literal must appear directly in the
19
+ # argument, `from_secret(secret.as_bytes())` (a variable) and
20
+ # `from_secret(std::env::var("JWT_SECRET")?.as_bytes())` are NOT matched.
21
+ pattern-either:
22
+ - pattern: jsonwebtoken::EncodingKey::from_secret(b"...")
23
+ - pattern: jsonwebtoken::DecodingKey::from_secret(b"...")
24
+ - pattern: EncodingKey::from_secret(b"...")
25
+ - pattern: DecodingKey::from_secret(b"...")
26
+ - pattern: EncodingKey::from_secret("...".as_ref())
27
+ - pattern: DecodingKey::from_secret("...".as_ref())
28
+ - pattern: EncodingKey::from_secret("...".as_bytes())
29
+ - pattern: DecodingKey::from_secret("...".as_bytes())
30
+ metadata:
31
+ oauthlint-rule-id: AUTH-RUST-JWT-002
32
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-jwt-hardcoded-secret
33
+ category: security
34
+ cwe: CWE-798
35
+ owasp: API2:2023
36
+ llm-prevalence: HIGH
37
+ technology:
38
+ - jsonwebtoken
39
+ references:
40
+ - https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.EncodingKey.html#method.from_secret
41
+ - https://cwe.mitre.org/data/definitions/798.html
@@ -0,0 +1,34 @@
1
+ rules:
2
+ - id: auth.rust.jwt.no-aud-validation
3
+ languages:
4
+ - rust
5
+ severity: WARNING
6
+ message: |
7
+ JWT audience (`aud`) validation is disabled by setting
8
+ `validate_aud: false` on the `jsonwebtoken` `Validation`. With the
9
+ audience check turned off, a token minted for a different service is
10
+ accepted by `decode`, so an attacker can replay a token issued for
11
+ another audience against this API.
12
+
13
+ Keep `validate_aud` at its default `true` and declare the audience you
14
+ expect via `validation.set_audience(&["my-api"])`, so only tokens whose
15
+ `aud` claim matches your service are accepted.
16
+ # Flags the assignment `$V.validate_aud = false`. `validate_aud: true` and an
17
+ # omitted field are not flagged. The struct-literal form
18
+ # `Validation { validate_aud: false, .. }` is a known false negative:
19
+ # Semgrep's Rust parser cannot match a field inside a struct literal with a
20
+ # rest (`..`), and a text regex would false-positive on comments — kept
21
+ # AST-only for precision.
22
+ pattern: $V.validate_aud = false
23
+ metadata:
24
+ oauthlint-rule-id: AUTH-RUST-JWT-004
25
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-jwt-no-aud-validation
26
+ category: security
27
+ cwe: CWE-287
28
+ owasp: API2:2023
29
+ llm-prevalence: MEDIUM
30
+ technology:
31
+ - jsonwebtoken
32
+ references:
33
+ - https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.Validation.html#structfield.validate_aud
34
+ - https://cwe.mitre.org/data/definitions/287.html
@@ -0,0 +1,35 @@
1
+ rules:
2
+ - id: auth.rust.jwt.no-expiration-validation
3
+ languages:
4
+ - rust
5
+ severity: ERROR
6
+ message: |
7
+ JWT expiration validation is turned off by setting `validate_exp: false`
8
+ on the `jsonwebtoken` `Validation`. With `exp` checking disabled, `decode`
9
+ accepts tokens that have already expired, so a leaked or stolen access
10
+ token stays usable forever. For OAuth/OIDC this defeats token lifetimes
11
+ and revocation-by-expiry, letting an attacker replay old tokens.
12
+
13
+ Leave `validate_exp` at its default `true` so expired tokens are
14
+ rejected. Build the validator with `Validation::new(Algorithm::HS256)`
15
+ (or your issuer's algorithm) and do not turn off `validate_exp`.
16
+ # Matches the assignment `$V.validate_exp = false` (the idiomatic way to
17
+ # turn the check off after `Validation::new`). `validate_exp: true` and the
18
+ # field's absence (default true) are not flagged. The struct-literal form
19
+ # `Validation { validate_exp: false, .. }` is a known false negative:
20
+ # Semgrep's Rust frontend cannot match a field inside a struct literal with
21
+ # a rest (`..`), and a text regex would false-positive on comments — so we
22
+ # keep this AST-only for precision.
23
+ pattern: $V.validate_exp = false
24
+ metadata:
25
+ oauthlint-rule-id: AUTH-RUST-JWT-003
26
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-jwt-no-expiration-validation
27
+ category: security
28
+ cwe: CWE-613
29
+ owasp: API2:2023
30
+ llm-prevalence: MEDIUM
31
+ technology:
32
+ - jsonwebtoken
33
+ references:
34
+ - https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.Validation.html#structfield.validate_exp
35
+ - https://cwe.mitre.org/data/definitions/613.html
@@ -0,0 +1,30 @@
1
+ rules:
2
+ - id: auth.rust.tls.accept-invalid-certs
3
+ languages:
4
+ - rust
5
+ severity: ERROR
6
+ message: |
7
+ A reqwest client is built with `danger_accept_invalid_certs(true)`, which
8
+ turns off TLS certificate validation. Any attacker who can intercept the
9
+ connection can present any certificate and read or tamper with the
10
+ traffic — a man-in-the-middle hole. For OAuth/OIDC this leaks
11
+ authorization codes, access tokens, and client secrets in transit.
12
+
13
+ Never accept invalid certificates. Leave validation on (the default). To
14
+ trust a private CA in development, add it explicitly with
15
+ `ClientBuilder::add_root_certificate(cert)` instead.
16
+ # Matches only the literal `true`. `danger_accept_invalid_certs(false)` and
17
+ # the method's absence are not flagged. `$B` is any builder expression.
18
+ pattern: $B.danger_accept_invalid_certs(true)
19
+ metadata:
20
+ oauthlint-rule-id: AUTH-RUST-TLS-001
21
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-tls-accept-invalid-certs
22
+ category: security
23
+ cwe: CWE-295
24
+ owasp: A02:2021
25
+ llm-prevalence: HIGH
26
+ technology:
27
+ - reqwest
28
+ references:
29
+ - https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.danger_accept_invalid_certs
30
+ - https://cwe.mitre.org/data/definitions/295.html
@@ -0,0 +1,32 @@
1
+ rules:
2
+ - id: auth.rust.tls.accept-invalid-hostnames
3
+ languages:
4
+ - rust
5
+ severity: ERROR
6
+ message: |
7
+ A reqwest client is built with `danger_accept_invalid_hostnames(true)`,
8
+ which turns off TLS hostname verification. The certificate chain is still
9
+ checked, but a certificate valid for any other domain is accepted for this
10
+ connection — so an attacker holding a valid certificate for a host they
11
+ control can intercept the connection and read or tamper with the traffic,
12
+ a man-in-the-middle hole. For OAuth/OIDC this leaks authorization codes,
13
+ access tokens, and client secrets in transit.
14
+
15
+ Never accept invalid hostnames. Leave hostname verification on (the
16
+ default). To trust a private CA in development, add it explicitly with
17
+ `ClientBuilder::add_root_certificate(cert)` instead.
18
+ # Matches only the literal `true`. `danger_accept_invalid_hostnames(false)`
19
+ # and the method's absence are not flagged. `$B` is any builder expression.
20
+ pattern: $B.danger_accept_invalid_hostnames(true)
21
+ metadata:
22
+ oauthlint-rule-id: AUTH-RUST-TLS-002
23
+ oauthlint-doc-url: https://oauthlint.dev/rules/rust-tls-accept-invalid-hostnames
24
+ category: security
25
+ cwe: CWE-297
26
+ owasp: A02:2021
27
+ llm-prevalence: HIGH
28
+ technology:
29
+ - reqwest
30
+ references:
31
+ - https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.danger_accept_invalid_hostnames
32
+ - https://cwe.mitre.org/data/definitions/297.html
@@ -0,0 +1,58 @@
1
+ rules:
2
+ - id: auth.secret.public-env-secret
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: ERROR
7
+ message: |
8
+ A secret is being read from an environment variable whose name carries a
9
+ client-public prefix. Bundlers inline these variables into the browser
10
+ JavaScript at build time:
11
+
12
+ - Next.js / webpack: NEXT_PUBLIC_*
13
+ - Create React App: REACT_APP_*
14
+ - Gatsby: GATSBY_*
15
+ - SvelteKit / generic: PUBLIC_*
16
+ - Vite: VITE_* (via import.meta.env)
17
+
18
+ Anything carried by such a variable ships to every visitor — a secret,
19
+ token, password, or private key placed here is published, not protected.
20
+
21
+ Keep the secret in a server-only variable (no public prefix) and read it
22
+ only in server code (API routes, server actions, loaders). If a value is
23
+ genuinely safe to expose (publishable key, client_id), name it so.
24
+ # We deliberately match only the suffixes that are NEVER legitimately
25
+ # public — SECRET, PASSWORD, PRIVATE(_KEY). `API_KEY` / `TOKEN` are
26
+ # excluded on purpose: many services ship *publishable* client keys and
27
+ # public tokens under a public prefix (Stripe publishable key, Mapbox
28
+ # token, Algolia search key, Inkeep widget key), so flagging those would
29
+ # be a false positive on legitimate code. The trade-off is recall on a
30
+ # genuinely-secret `NEXT_PUBLIC_*_API_KEY`; precision wins here.
31
+ pattern-either:
32
+ # process.env.<PUBLIC_PREFIX>...<SECRET_SUFFIX>
33
+ - patterns:
34
+ - pattern: process.env.$VAR
35
+ - metavariable-regex:
36
+ metavariable: $VAR
37
+ regex: ^(NEXT_PUBLIC|REACT_APP|GATSBY|PUBLIC)_.*(SECRET|PRIVATE|PASSWORD).*$
38
+ # import.meta.env.<VITE_PREFIX>...<SECRET_SUFFIX>
39
+ - patterns:
40
+ - pattern: import.meta.env.$VAR
41
+ - metavariable-regex:
42
+ metavariable: $VAR
43
+ regex: ^VITE_.*(SECRET|PRIVATE|PASSWORD).*$
44
+ metadata:
45
+ oauthlint-rule-id: AUTH-SECRET-002
46
+ oauthlint-doc-url: https://oauthlint.dev/rules/secret-public-env-secret
47
+ category: security
48
+ cwe: CWE-200
49
+ owasp: A02:2021
50
+ llm-prevalence: HIGH
51
+ technology:
52
+ - next.js
53
+ - vite
54
+ - create-react-app
55
+ references:
56
+ - https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser
57
+ - https://vitejs.dev/guide/env-and-mode#env-variables
58
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
@@ -0,0 +1,42 @@
1
+ rules:
2
+ - id: auth.session.hardcoded-secret
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: ERROR
7
+ message: |
8
+ An `express-session` / `cookie-session` `secret` is set to a hard-coded
9
+ string literal (the infamous `secret: 'keyboard cat'` is the canonical
10
+ AI-generated example). This key signs the session cookie: anyone who
11
+ reads it from your source or git history can forge arbitrary session
12
+ cookies and impersonate any user.
13
+
14
+ Load the secret from the environment (`process.env.SESSION_SECRET`) or a
15
+ secret manager, and rotate it out of source control. Add a placeholder
16
+ to `.env.example` so contributors know it is required.
17
+ # AST patterns target the `secret:` key inside session()/cookieSession()
18
+ # options. The metavariable-pattern requires $S to be a quoted literal, so
19
+ # `process.env.SESSION_SECRET`, `config.sessionSecret`, and `loadSecret()`
20
+ # never match (they are not string literals).
21
+ patterns:
22
+ - pattern-either:
23
+ - pattern: 'session({secret: $S, ...})'
24
+ - pattern: 'cookieSession({secret: $S, ...})'
25
+ - pattern: 'expressSession({secret: $S, ...})'
26
+ - metavariable-pattern:
27
+ metavariable: $S
28
+ patterns:
29
+ - pattern-regex: ^['"].*['"]$
30
+ metadata:
31
+ oauthlint-rule-id: AUTH-SESSION-003
32
+ oauthlint-doc-url: https://oauthlint.dev/rules/session-hardcoded-secret
33
+ category: security
34
+ cwe: CWE-798
35
+ owasp: A07:2021
36
+ llm-prevalence: HIGH
37
+ technology:
38
+ - express-session
39
+ - cookie-session
40
+ references:
41
+ - https://github.com/expressjs/session#secret
42
+ - https://cwe.mitre.org/data/definitions/798.html