oauthlint-rules 0.1.1 → 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 +9 -2
- 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
|
@@ -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
|