oauthlint-rules 0.5.0 → 0.5.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.5.0",
3
+ "version": "0.5.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",
@@ -6,8 +6,8 @@ rules:
6
6
  severity: INFO
7
7
  message: |
8
8
  An auth-looking cookie is being set with a `maxAge` greater than
9
- 30 days (the threshold is 30 × 24 × 60 × 60 × 1000 = 2_592_000_000
10
- milliseconds). Long-lived session cookies expand the blast radius
9
+ 30 days. The threshold is 30 × 24 × 60 × 60 × 1000 = 2_592_000_000
10
+ milliseconds. Long-lived session cookies expand the blast radius
11
11
  of any single token theft and bypass server-side revocation if
12
12
  the application doesn't validate freshness on every request.
13
13
 
@@ -5,11 +5,11 @@ rules:
5
5
  - typescript
6
6
  severity: ERROR
7
7
  message: |
8
- `Math.random()` is being assigned to (or used to compute) a value
9
- whose name indicates it is security-sensitive a token, a CSRF
10
- value, an OAuth `state`, a session id, a nonce, or a verification
11
- code. `Math.random()` is NOT cryptographically secure and is
12
- predictable enough for an attacker with enough samples to recover
8
+ `Math.random()` produces a value whose name marks it as
9
+ security-sensitive. The value is assigned to (or computed for) a token,
10
+ a CSRF value, an OAuth `state`, a session id, a nonce, or a
11
+ verification code. `Math.random()` is NOT cryptographically secure and
12
+ is predictable enough for an attacker with enough samples to recover
13
13
  the seed.
14
14
 
15
15
  Use `crypto.randomBytes(N)` (Node) or `crypto.getRandomValues()`
@@ -5,14 +5,15 @@ rules:
5
5
  - typescript
6
6
  severity: ERROR
7
7
  message: |
8
- An OAuth/OIDC credential taken from the request an authorization
9
- `code`, an `access_token` / `refresh_token` / `id_token`, a bearer
10
- `token`, a `client_secret`, or the raw `Authorization` header flows
11
- into a logging call (`console.*` or `logger.*`). Logs are written to
12
- files, shipped to aggregators (Datadog, Splunk, CloudWatch) and read by
13
- people and systems that should never see live credentials. A leaked
14
- authorization code or token can be replayed to impersonate the user or
15
- complete the OAuth exchange (CWE-532).
8
+ An OAuth/OIDC credential from the request flows into a logging call.
9
+ The tainted value is an authorization `code`, an `access_token` /
10
+ `refresh_token` / `id_token`, a bearer `token`, a `client_secret`, or
11
+ the raw `Authorization` header, and the sink is a `console.*` or
12
+ `logger.*` call. Logs are written to files, shipped to aggregators
13
+ (Datadog, Splunk, CloudWatch) and read by people and systems that should
14
+ never see live credentials. A leaked authorization code or token can be
15
+ replayed to impersonate the user or complete the OAuth exchange
16
+ (CWE-532).
16
17
 
17
18
  Never log the raw credential. Redact or mask it before logging
18
19
  (`token.slice(0, 4) + '…'`), log a non-sensitive identifier instead
@@ -5,12 +5,13 @@ rules:
5
5
  - typescript
6
6
  severity: WARNING
7
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.
8
+ A secret-shaped value is passed to a logging call. The logged
9
+ identifier is named like a credential (`password`, `token`, `secret`,
10
+ `apiKey`, `accessToken`, `refreshToken`, `privateKey`, `clientSecret`,
11
+ …) and the sink is a `console.*` or `logger.*` call. Logs are routinely
12
+ written to files, shipped to aggregators (Datadog, Splunk, CloudWatch)
13
+ and read by people who should never see the raw secret. This is a
14
+ textbook credential leak.
14
15
 
15
16
  Never log secrets. Redact or mask them before logging
16
17
  (`token.slice(0, 4) + '…'`), log a non-sensitive identifier instead
@@ -4,14 +4,15 @@ rules:
4
4
  - go
5
5
  severity: ERROR
6
6
  message: |
7
- An OAuth/OIDC credential taken from the HTTP request an authorization
8
- `code`, an `access_token` / `refresh_token` / `id_token`, a bearer
9
- `token`, a `client_secret`, or the raw `Authorization` header flows
10
- into a logging call (`log.*`, `slog.*`, `fmt.Print*`, or a `logger.*`
11
- method). Logs are written to files, shipped to aggregators (Datadog,
12
- Splunk, CloudWatch) and read by people and systems that should never see
13
- live credentials. A leaked authorization code or token can be replayed
14
- to impersonate the user or complete the OAuth exchange (CWE-532).
7
+ An OAuth/OIDC credential from the HTTP request flows into a logging call.
8
+ The tainted value is an authorization `code`, an `access_token` /
9
+ `refresh_token` / `id_token`, a bearer `token`, a `client_secret`, or
10
+ the raw `Authorization` header, and the sink is a `log.*`, `slog.*`,
11
+ `fmt.Print*`, or `logger.*` call. Logs are written to files, shipped to
12
+ aggregators (Datadog, Splunk, CloudWatch) and read by people and systems
13
+ that should never see live credentials. A leaked authorization code or
14
+ token can be replayed to impersonate the user or complete the OAuth
15
+ exchange (CWE-532).
15
16
 
16
17
  Never log the raw credential. Redact or mask it before logging, log a
17
18
  non-sensitive identifier instead (a user id, a key id), or drop the
@@ -4,9 +4,9 @@ rules:
4
4
  - go
5
5
  severity: ERROR
6
6
  message: |
7
- A security-sensitive value its name indicates a token, secret, key,
8
- password, nonce, OTP, or salt is being generated with the `math/rand`
9
- package. `math/rand` is a deterministic PRNG: its output is predictable
7
+ A security-sensitive value is being generated with the `math/rand`
8
+ package. Its name indicates a token, secret, key, password, nonce, OTP,
9
+ or salt. `math/rand` is a deterministic PRNG: its output is predictable
10
10
  and an attacker who observes enough values can recover the seed and
11
11
  forecast every future token. For OAuth/OIDC this means forgeable
12
12
  `state` values, guessable authorization codes, and predictable refresh
@@ -4,12 +4,12 @@ rules:
4
4
  - go
5
5
  severity: WARNING
6
6
  message: |
7
- A JWT parser is configured with `jwt.WithoutClaimsValidation()`, which
8
- turns OFF the standard registered-claims validation golang-jwt performs by
9
- default — the `exp` (expiry), `nbf` (not-before) and `iat` (issued-at)
10
- checks. With validation disabled an expired or not-yet-valid token still
11
- parses successfully, so a stolen or long-expired token is accepted as if
12
- it were current (CWE-613).
7
+ A JWT parser turns off registered-claims validation with
8
+ `jwt.WithoutClaimsValidation()`. That option disables the `exp`
9
+ (expiry), `nbf` (not-before) and `iat` (issued-at) checks golang-jwt
10
+ performs by default. With validation disabled an expired or
11
+ not-yet-valid token still parses successfully, so a stolen or
12
+ long-expired token is accepted as if it were current (CWE-613).
13
13
 
14
14
  Remove `jwt.WithoutClaimsValidation()` and let golang-jwt validate the
15
15
  time-based claims. If a specific claim must be relaxed, scope it narrowly
@@ -4,12 +4,12 @@ rules:
4
4
  - go
5
5
  severity: ERROR
6
6
  message: |
7
- A `Keyfunc` passed to `jwt.Parse`/`jwt.ParseWithClaims` returns the
8
- verification key WITHOUT first checking `token.Method` (the signing
9
- algorithm). This enables an algorithm-confusion attack: if the server
10
- verifies RS256 tokens with an RSA public key, an attacker can forge a
11
- token signed with HS256 using that public key as the HMAC secret, and the
12
- library will accept it — a complete authentication bypass (CWE-347).
7
+ A JWT `Keyfunc` returns the verification key without checking `token.Method`, enabling algorithm confusion.
8
+ It is passed to `jwt.Parse`/`jwt.ParseWithClaims` and hands back the key
9
+ without first asserting the signing algorithm. If the server verifies
10
+ RS256 tokens with an RSA public key, an attacker can forge an HS256 token
11
+ using that public key as the HMAC secret, and the library will accept it:
12
+ a complete authentication bypass (CWE-347).
13
13
 
14
14
  Always assert the signing method inside the keyfunc before returning the
15
15
  key, e.g.:
@@ -4,12 +4,12 @@ rules:
4
4
  - go
5
5
  severity: ERROR
6
6
  message: |
7
- A `tls.Config` is created with `InsecureSkipVerify: true`, which disables
8
- verification of the server's certificate chain and host name. Any
9
- attacker who can intercept the connection can present any certificate
10
- and read or tamper with the traffic a classic man-in-the-middle hole.
11
- For OAuth/OIDC this leaks authorization codes, access tokens, and client
12
- secrets in transit.
7
+ A `tls.Config` sets `InsecureSkipVerify: true`, disabling TLS certificate verification.
8
+ This turns off verification of the server's certificate chain and host
9
+ name, so any attacker who can intercept the connection can present any
10
+ certificate and read or tamper with the traffic: a classic
11
+ man-in-the-middle hole. For OAuth/OIDC this leaks authorization codes,
12
+ access tokens, and client secrets in transit.
13
13
 
14
14
  Never set `InsecureSkipVerify: true`. Leave verification on (the default).
15
15
  To trust a private CA in development, set `RootCAs` to a `*x509.CertPool`
@@ -5,10 +5,10 @@ rules:
5
5
  - typescript
6
6
  severity: WARNING
7
7
  message: |
8
- `jwt.verify(token, key, { ignoreExpiration: true })` from `jsonwebtoken`
9
- disables the `exp` (expiry) claim check, so an expired token is accepted
10
- as valid forever. A stolen or long-old token then never stops working,
11
- defeating the whole point of short-lived access tokens.
8
+ `ignoreExpiration: true` in a `jsonwebtoken` `verify()` call disables the
9
+ `exp` claim check. An expired token is then accepted as valid forever, so
10
+ a stolen or long-old token never stops working, defeating the whole point
11
+ of short-lived access tokens.
12
12
 
13
13
  Remove `ignoreExpiration: true` so the `exp` claim is enforced, and set a
14
14
  sane `expiresIn` when signing (`jwt.sign(payload, key, { expiresIn: '15m'
@@ -5,9 +5,9 @@ rules:
5
5
  - typescript
6
6
  severity: ERROR
7
7
  message: |
8
- OAuth implicit flow (`response_type=token` or `response_type=id_token
9
- token`) is deprecated by OAuth 2.0 Security BCP (RFC 9700) and the
10
- OAuth 2.1 working draft. The access token leaks into the URL
8
+ OAuth implicit flow is deprecated by the OAuth 2.0 Security BCP (RFC 9700)
9
+ and the OAuth 2.1 draft. It is triggered by `response_type=token` or
10
+ `response_type=id_token token`. The access token leaks into the URL
11
11
  fragment, browser history, and referrer headers, and there is no
12
12
  refresh-token mechanism.
13
13
 
@@ -5,9 +5,9 @@ rules:
5
5
  - typescript
6
6
  severity: WARNING
7
7
  message: |
8
- An OAuth `expires_in` (or comparable token-lifetime field) is being
9
- set to a literal value greater than 86_400 seconds i.e. longer
10
- than 24 hours. Long-lived access tokens make every token theft
8
+ An OAuth token-lifetime field is set to a literal value longer than
9
+ 24 hours. The value of `expires_in` (or a comparable field) exceeds
10
+ 86_400 seconds. Long-lived access tokens make every token theft
11
11
  catastrophic because they remain valid for days or weeks; the
12
12
  industry standard is 15-60 minutes for access tokens, paired with
13
13
  a refresh-token rotation flow for longer sessions.
@@ -5,10 +5,10 @@ rules:
5
5
  - typescript
6
6
  severity: WARNING
7
7
  message: |
8
- OAuth 2.0 authorization request looks like a public client (SPA,
9
- mobile, or native app) but does not include a `code_challenge`
10
- parameter. Without PKCE, the authorization code can be intercepted
11
- and exchanged by an attacker.
8
+ OAuth authorization request from a public client omits the PKCE `code_challenge` parameter.
9
+ The request looks like a public client (SPA, mobile, or native app) yet
10
+ carries no `code_challenge`. Without PKCE, the authorization code can be
11
+ intercepted and exchanged by an attacker.
12
12
 
13
13
  RFC 8252 §6 mandates PKCE for native/SPA clients. RFC 9700 (OAuth 2.0
14
14
  Security BCP) recommends PKCE for ALL clients, including confidential
@@ -5,17 +5,16 @@ rules:
5
5
  - typescript
6
6
  severity: WARNING
7
7
  message: |
8
- An OAuth/OIDC token is being written to `localStorage` /
9
- `sessionStorage` under a token-named key (`access_token`,
10
- `refresh_token`, `id_token`, …). Web storage is readable by any
11
- script on the origin, so any XSS — including a compromised
12
- third-party dependency can exfiltrate the token via
13
- `getItem(...)`. There is no browser-side mitigation, unlike
8
+ An OAuth/OIDC token is written to `localStorage` / `sessionStorage`, readable by any script on the origin.
9
+ The key is token-named (`access_token`, `refresh_token`, `id_token`, …),
10
+ and web storage is exposed to every script on the page, so any XSS,
11
+ including a compromised third-party dependency, can exfiltrate the token
12
+ via `getItem(...)`. There is no browser-side mitigation, unlike
14
13
  `HttpOnly` cookies.
15
14
 
16
15
  Keep access/refresh/id tokens in memory only, or have the server
17
16
  issue them in a `Secure; HttpOnly; SameSite=Strict` cookie.
18
- `sessionStorage` is no safer than `localStorage` against XSS it
17
+ `sessionStorage` is no safer than `localStorage` against XSS: it
19
18
  grants the same attacker capability.
20
19
 
21
20
  See CWE-922: Insecure Storage of Sensitive Information.
@@ -4,14 +4,15 @@ rules:
4
4
  - python
5
5
  severity: ERROR
6
6
  message: |
7
- Flask-CORS is configured with `supports_credentials=True` together with a
8
- wildcard origin (`origins="*"`, `origins=["*"]`, or since Flask-CORS
9
- defaults to `*` no `origins` argument at all). The CORS spec forbids the
7
+ Flask-CORS allows any origin while credentials are enabled.
8
+ This configuration pairs `supports_credentials=True` with a wildcard
9
+ origin (`origins="*"`, `origins=["*"]`, or no `origins` argument at all,
10
+ since Flask-CORS defaults to `*`). The CORS spec forbids the
10
11
  `Access-Control-Allow-Origin: *` + `Access-Control-Allow-Credentials: true`
11
- combination, so browsers will block it; the dangerous "fix" is to leave the
12
- wildcard in place while keeping credentials on, which exposes credentialed
13
- cross-origin access to ANY website (CWE-942). For OAuth/OIDC this leaks
14
- cookies, session tokens and CSRF protections cross-origin.
12
+ combination, so browsers will block it; the dangerous "fix" is to leave
13
+ the wildcard in place while keeping credentials on, which exposes
14
+ credentialed cross-origin access to ANY website (CWE-942). For OAuth/OIDC
15
+ this leaks cookies, session tokens and CSRF protections cross-origin.
15
16
 
16
17
  Credentialed requests must use an explicit allow-list of trusted origins,
17
18
  e.g. `CORS(app, origins=["https://app.example.com"], supports_credentials=True)`.
@@ -4,11 +4,12 @@ rules:
4
4
  - python
5
5
  severity: ERROR
6
6
  message: |
7
- A security-sensitive value (token, secret, password, OTP, nonce, API key,
8
- reset/verification code) is being generated with the `random` module.
9
- `random` is a pseudo-random number generator seeded from predictable state
10
- and is NOT cryptographically secure its output can be predicted or
11
- reproduced by an attacker, defeating the secret entirely.
7
+ A security-sensitive value is being generated with the `random` module.
8
+ The value is a token, secret, password, OTP, nonce, API key, or
9
+ reset/verification code. `random` is a pseudo-random number generator
10
+ seeded from predictable state and is NOT cryptographically secure: its
11
+ output can be predicted or reproduced by an attacker, defeating the
12
+ secret entirely.
12
13
 
13
14
  Use the `secrets` module or `os.urandom` instead:
14
15
  `secrets.token_urlsafe(32)`, `secrets.token_hex(16)`,
@@ -4,14 +4,15 @@ rules:
4
4
  - python
5
5
  severity: ERROR
6
6
  message: |
7
- An OAuth/OIDC credential taken from the request an authorization
8
- `code`, an `access_token` / `refresh_token` / `id_token`, a bearer
9
- `token`, a `client_secret`, or the raw `Authorization` header flows
10
- into a logging call (`print`, `logging.*`, or a `logger.*`). Logs are
11
- written to files, shipped to aggregators (Datadog, Splunk, CloudWatch)
12
- and read by people and systems that should never see live credentials. A
13
- leaked authorization code or token can be replayed to impersonate the
14
- user or complete the OAuth exchange (CWE-532).
7
+ An OAuth/OIDC credential from the request flows into a logging call.
8
+ The tainted value is an authorization `code`, an `access_token` /
9
+ `refresh_token` / `id_token`, a bearer `token`, a `client_secret`, or
10
+ the raw `Authorization` header, and the sink is `print`, `logging.*`,
11
+ or a `logger.*` call. Logs are written to files, shipped to aggregators
12
+ (Datadog, Splunk, CloudWatch) and read by people and systems that should
13
+ never see live credentials. A leaked authorization code or token can be
14
+ replayed to impersonate the user or complete the OAuth exchange
15
+ (CWE-532).
15
16
 
16
17
  Never log the raw credential. Redact or mask it before logging, log a
17
18
  non-sensitive identifier instead (a user id, a key id), or drop the field
@@ -4,15 +4,16 @@ rules:
4
4
  - python
5
5
  severity: ERROR
6
6
  message: |
7
- A JWT is decoded with an `algorithms` allowlist that mixes a symmetric
8
- HMAC algorithm (HS256/HS384/HS512) with an asymmetric one (RS*/ES*/PS*).
9
- This enables the "algorithm confusion" attack: an attacker who knows your
10
- RSA/EC PUBLIC key can sign a forged token with HMAC, using that public key
11
- string as the shared secret. Because HS* is also accepted, PyJWT verifies
12
- the forgery with the public key as the HMAC secret and treats it as valid
13
- a complete authentication bypass.
7
+ A JWT is decoded with an `algorithms` allowlist that mixes an HMAC algorithm with an asymmetric one.
8
+ The list combines a symmetric HMAC algorithm (HS256/HS384/HS512) with an
9
+ asymmetric one (RS*/ES*/PS*). This enables the "algorithm confusion"
10
+ attack: an attacker who knows your RSA/EC PUBLIC key can sign a forged
11
+ token with HMAC, using that public key string as the shared secret.
12
+ Because HS* is also accepted, PyJWT verifies the forgery with the public
13
+ key as the HMAC secret and treats it as valid: a complete authentication
14
+ bypass.
14
15
 
15
- Allow only ONE algorithm family the one you actually use. If you issue
16
+ Allow only ONE algorithm family, the one you actually use. If you issue
16
17
  RS256 tokens, pin `jwt.decode(token, public_key, algorithms=["RS256"])`
17
18
  and never also accept an HS* algorithm with the same verification key.
18
19
 
@@ -4,9 +4,9 @@ rules:
4
4
  - python
5
5
  severity: ERROR
6
6
  message: |
7
- `OAUTHLIB_INSECURE_TRANSPORT` is being set, which disables oauthlib's
8
- HTTPS requirement for OAuth flows (`requests-oauthlib`, Authlib's
9
- requests integration, Django OAuth Toolkit). oauthlib raises
7
+ `OAUTHLIB_INSECURE_TRANSPORT` is set, disabling oauthlib's HTTPS
8
+ requirement for OAuth flows. This affects `requests-oauthlib`, Authlib's
9
+ requests integration, and Django OAuth Toolkit. oauthlib raises
10
10
  `InsecureTransportError` to stop you exchanging codes and tokens over
11
11
  cleartext; setting this variable silences that guard, so authorization
12
12
  codes, `client_secret`, and access/refresh tokens travel over plain
@@ -4,11 +4,12 @@ rules:
4
4
  - rust
5
5
  severity: ERROR
6
6
  message: |
7
- A password is being hashed with a fast, general-purpose digest (MD5 via
8
- the `md5` crate, or SHA-1/SHA-256/SHA-512 via the RustCrypto `sha1`/`sha2`
9
- crates). These algorithms are designed to be fast, which makes offline
10
- brute-force and rainbow-table attacks cheap they are NOT suitable for
11
- storing passwords (CWE-916).
7
+ A password is hashed with a fast, general-purpose digest unsuitable for
8
+ password storage. The digest is MD5 (via the `md5` crate) or
9
+ SHA-1/SHA-256/SHA-512 (via the RustCrypto `sha1`/`sha2` crates). These
10
+ algorithms are designed to be fast, which makes offline brute-force and
11
+ rainbow-table attacks cheap; they are NOT suitable for storing passwords
12
+ (CWE-916).
12
13
 
13
14
  Use a dedicated, slow password-hashing function with a per-password salt
14
15
  and a tunable work factor: Argon2 (`argon2` crate,
@@ -4,13 +4,14 @@ rules:
4
4
  - rust
5
5
  severity: ERROR
6
6
  message: |
7
- A `jsonwebtoken` `Validation` accepts a list of algorithms that MIXES an
8
- HMAC family (`Algorithm::HS256`/`HS384`/`HS512`) with an asymmetric family
9
- (`Algorithm::RS*`/`ES*`/`PS*`). This is the "algorithm confusion" attack:
10
- when both families are accepted, an attacker takes your RSA/EC PUBLIC key
11
- (which is not secret) and signs a forged token with HS*, using the public
12
- key bytes as the HMAC shared secret. `decode` then verifies that forged
13
- token as valid, letting the attacker mint arbitrary identities and claims.
7
+ A `jsonwebtoken` `Validation` accepts both HMAC and asymmetric algorithms,
8
+ enabling algorithm confusion. The accepted-algorithm list MIXES an HMAC
9
+ family (`Algorithm::HS256`/`HS384`/`HS512`) with an asymmetric family
10
+ (`Algorithm::RS*`/`ES*`/`PS*`). When both families are accepted, an
11
+ attacker takes your RSA/EC PUBLIC key (which is not secret) and signs a
12
+ forged token with HS*, using the public key bytes as the HMAC shared
13
+ secret. `decode` then verifies that forged token as valid, letting the
14
+ attacker mint arbitrary identities and claims.
14
15
 
15
16
  Pin `validation.algorithms` to a SINGLE family you actually use, e.g.
16
17
  `validation.algorithms = vec![Algorithm::RS256];` when your issuer signs
@@ -4,10 +4,11 @@ rules:
4
4
  - rust
5
5
  severity: ERROR
6
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.
7
+ A JWT HMAC signing/verification key is hardcoded as a literal. It is
8
+ passed directly to jsonwebtoken's `EncodingKey::from_secret` /
9
+ `DecodingKey::from_secret`. Anyone who can read the source or git history
10
+ can forge or tamper with tokens, which is a complete authentication
11
+ bypass.
11
12
 
12
13
  Load the secret at runtime from the environment or a secret manager
13
14
  instead, e.g. `let key = std::env::var("JWT_SECRET")?;` followed by
@@ -4,13 +4,13 @@ rules:
4
4
  - rust
5
5
  severity: WARNING
6
6
  message: |
7
- A JWT is decoded with a `jsonwebtoken` `Validation` that never sets the
8
- expected issuer, so `decode` accepts a token minted by ANY issuer. The
9
- `jsonwebtoken` crate does not validate the `iss` claim unless you opt in,
10
- so a token signed by an attacker-controlled or otherwise untrusted issuer
11
- passes validation as long as the signature checks out. For OAuth/OIDC this
12
- lets a token from the wrong authorization server be replayed against this
13
- API.
7
+ A JWT is decoded with a `jsonwebtoken` `Validation` that never sets the expected issuer.
8
+ Because the issuer is not pinned, `decode` accepts a token minted by ANY
9
+ issuer: the `jsonwebtoken` crate does not validate the `iss` claim unless
10
+ you opt in, so a token signed by an attacker-controlled or otherwise
11
+ untrusted issuer passes validation as long as the signature checks out.
12
+ For OAuth/OIDC this lets a token from the wrong authorization server be
13
+ replayed against this API.
14
14
 
15
15
  Pin the issuer before decoding, e.g.
16
16
  `validation.set_issuer(&["https://issuer.example.com"])` (or set
@@ -5,9 +5,9 @@ rules:
5
5
  - typescript
6
6
  severity: ERROR
7
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
8
+ An `express-session` / `cookie-session` `secret` is a hard-coded string
9
+ literal. The infamous `secret: 'keyboard cat'` is the canonical
10
+ AI-generated example. This key signs the session cookie: anyone who
11
11
  reads it from your source or git history can forge arbitrary session
12
12
  cookies and impersonate any user.
13
13
 
@@ -5,11 +5,12 @@ rules:
5
5
  - typescript
6
6
  severity: WARNING
7
7
  message: |
8
- The user is being marked as logged in (`req.session.user = ...`,
9
- `req.session.userId = ...`, etc.) WITHOUT first regenerating the
10
- session id. This opens the door to session-fixation attacks: an
11
- attacker who plants a known session id in the victim's browser
12
- before login retains access after authentication succeeds.
8
+ The user is marked as logged in without first regenerating the session id.
9
+ The identity is written onto the session (`req.session.user = ...`,
10
+ `req.session.userId = ...`, etc.) before the session id is rotated. This
11
+ opens the door to session-fixation attacks: an attacker who plants a
12
+ known session id in the victim's browser before login retains access
13
+ after authentication succeeds.
13
14
 
14
15
  Call `req.session.regenerate(cb)` (or your framework's equivalent)
15
16
  between authenticating the credentials and writing the user
@@ -5,16 +5,17 @@ rules:
5
5
  - typescript
6
6
  severity: ERROR
7
7
  message: |
8
- Disabling TLS certificate validation (`rejectUnauthorized: false` or
9
- `NODE_TLS_REJECT_UNAUTHORIZED=0`) makes the connection accept ANY
10
- certificate, including self-signed or attacker-supplied ones. This
11
- removes the protection TLS provides against man-in-the-middle attacks:
12
- anyone able to intercept the network path can present a forged
13
- certificate, then read and modify the traffic (credentials, tokens, data).
8
+ TLS certificate validation is disabled for this connection.
9
+ Setting `rejectUnauthorized: false` or `NODE_TLS_REJECT_UNAUTHORIZED=0`
10
+ makes the connection accept ANY certificate, including self-signed or
11
+ attacker-supplied ones. This removes the protection TLS provides against
12
+ man-in-the-middle attacks: anyone able to intercept the network path can
13
+ present a forged certificate, then read and modify the traffic
14
+ (credentials, tokens, data).
14
15
 
15
16
  Keep certificate validation enabled. If the server uses a private or
16
17
  self-signed CA, supply that CA explicitly instead of turning validation
17
- off e.g. `new https.Agent({ ca: fs.readFileSync('ca.pem') })`. See
18
+ off, e.g. `new https.Agent({ ca: fs.readFileSync('ca.pem') })`. See
18
19
  CWE-295 and the Node.js TLS docs.
19
20
  # Matches an options object literal that contains `rejectUnauthorized: false`,
20
21
  # wherever it appears (https.request, new https.Agent, tls.connect, axios