Skip to main content
interlace
Plugin: express-securityRules

no-sensitive-data-in-query

Disallow reading sensitive-named parameters (password, token, secret, ...) from req.query

Keywords: sensitive query string, credentials in URL, CWE-598, GET request secrets, password in query, api key in url, access logs, Referer leak, browser history, express security

Disallow reading sensitive-named parameters (password, token, secret, apiKey, ...) from req.query.

CWE: CWE-598
OWASP: A04:2021 – Insecure Design

Detects sensitive data carried in the URL query string: reading sensitive-named parameters from req.query — via member access (req.query.password) or destructuring (const { password } = req.query). Query strings land in access logs, proxy logs, browser history and the Referer header of every outbound link, so secrets must travel in a POST body or an Authorization header instead. This rule is part of eslint-plugin-express-security and provides LLM-optimized error messages.

🚨 Security rule | 💡 Provides LLM-optimized guidance | ⚠️ Set to error in recommended

Quick Summary

AspectDetails
CWE ReferenceCWE-598 (Sensitive Query Strings)
Severity🟠 MEDIUM (credential exposure through logs and history)
Auto-Fix❌ Not available (💡 suggestion: move the value to the POST body)
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForExpress / Koa route handlers, especially GET routes

Vulnerability and Risk

Vulnerability: A GET request like /login?username=alice&password=hunter2 works — and silently copies the password into every access log, proxy log, CDN log, the browser history, and the Referer header of any resource the response page loads.

Risk:

  • Log exposure: Anyone with read access to web-server or proxy logs harvests credentials in bulk.
  • Referer leakage: Third-party scripts and outbound links on the response page receive the full query string.
  • History and caching: Secrets persist in browser history, bookmarks, and intermediary caches long after the session ends.

Rule Details

The rule fires on the AST shape <req>.query.<name> (request objects req / request / ctx) and on object-pattern destructuring from <req>.query. Sensitivity is decided by tokenizing the parameter name (camelCase and snake_case both split) and matching whole tokens — so api_token and accessToken match token, while author does NOT match auth and cardinality does NOT match card.

Default sensitive terms: password, token, secret, apiKey, api_key, auth, credential, ssn, card (token-matched, trivial plurals included).

Error Message Format

The rule provides LLM-optimized error messages with actionable security guidance:

🔒 CWE-598 | Sensitive Data in Query String (CWE-598) | MEDIUM
   Query parameter 'password' is sensitive. Query strings are stored in access logs, proxy logs, browser history and leak via the Referer header.
   Fix: Move the value to the POST request body (req.body) or an Authorization header | https://cwe.mitre.org/data/definitions/598.html

Configuration

OptionTypeDefaultDescription
sensitiveParamsstring[][]Additional sensitive parameter names (extends the defaults)
extraPatternsstring[][]Additional regex patterns (case-insensitive) matched against raw names
allowedParamsstring[][]Parameter names explicitly allowed in the query (exact, case-insensitive)

Example Configuration

{
  "rules": {
    "express-security/no-sensitive-data-in-query": [
      "error",
      {
        "sensitiveParams": ["pin", "session_id"],
        "extraPatterns": ["^x-"],
        "allowedParams": ["token"]
      }
    ]
  }
}

Examples

❌ Incorrect

// ❌ Credentials via GET query string
app.get('/login', async (req, res) => {
  const { username, password } = req.query;
  await authenticate(username, password);
});

// ❌ API token in the query string
app.get('/api/export', async (req, res) => {
  const apiToken = req.query.api_token;
  res.json(await exportAccountData(apiToken));
});

// ❌ Computed string-literal access is flagged too
const secret = req.query['secret'];

✅ Correct

// ✅ Secrets travel in a POST body
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  await authenticate(username, password);
});

// ✅ Non-sensitive query fields are fine
const page = req.query.page;

// ✅ Route params are not query strings
const token = req.params.token;

// ✅ Dedicated verification-link route with allowedParams: ['token']
app.get('/verify-email', async (req, res) => {
  await verifyEmailToken(req.query.token);
  res.redirect('/verified');
});

Security Impact

VulnerabilityCWEOWASPCVSSImpact
Sensitive Query Strings598A04:20216.5Credential exposure via logs
Information Exposure200A01:20215.3Secrets in history / Referer
Insertion into Log File532A09:20215.5Long-lived credential records

Why This Matters

Real-World Exploits

Query-string credential leaks are a staple of post-incident reports: access tokens in URLs have been harvested from CDN logs, analytics pipelines and Referer headers at major providers — the OAuth implicit flow was deprecated largely because tokens rode in URLs. Logs are typically retained for months and are readable by far more people than the credential store.

Prevention Strategy

  1. POST for secrets: Any credential, token or PII goes in the request body over HTTPS.
  2. Authorization header: Bearer tokens belong in headers, which are not logged by default.
  3. Explicit allowlist: Single-use, short-lived tokens on dedicated verification routes can be permitted via allowedParams — keep the list minimal.

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic / computed access

Why: The parameter name is not statically known.

// ❌ NOT DETECTED
const value = req.query[paramName];

Aliased query objects

Why: Only direct <req>.query shapes are matched.

// ❌ NOT DETECTED
const q = req.query;
const password = q.password;

Non-standard request names

Why: Only req / request / ctx are recognized as request objects.

// ❌ NOT DETECTED
const password = incoming.query.password;

Further Reading

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.