Interlace ESLint
ESLint Interlace
Browser SecurityRules

no-sensitive-localstorage

Detects storage of sensitive data (tokens, passwords, PII) in localStorage. This rule is part of [`eslint-plugin-browser-security`](https://www.npmjs.com/packag

Keywords: localStorage, sessionStorage, tokens, CWE-922, security, XSS, sensitive data

Detects storage of sensitive data (tokens, passwords, PII) in localStorage. This rule is part of eslint-plugin-browser-security.

⚠️ This rule errors by default in the recommended config.

Quick Summary

AspectDetails
CWE ReferenceCWE-922 (Insecure Storage of Sensitive Information)
Severity🔴 High
Auto-Fix❌ No (requires architecture change)
CategorySecurity
Best ForSPAs, frontend apps handling authentication

Vulnerability and Risk

Vulnerability: localStorage is accessible to any JavaScript running on the page. If an XSS vulnerability exists, attackers can steal tokens.

Risk:

  • XSS attacks can steal all localStorage data
  • Tokens persist beyond session (unlike cookies)
  • No protection against malicious browser extensions
  • Shared across tabs (potential for leakage)

Why localStorage is Dangerous for Tokens

Examples

❌ Incorrect

// Storing tokens - VULNERABLE to XSS
localStorage.setItem('token', jwtToken);
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);

// Storing sensitive user data - VULNERABLE
localStorage.setItem(
  'user',
  JSON.stringify({
    email: 'user@example.com',
    ssn: '123-45-6789',
    password: 'secret',
  }),
);

// Session storage has same issues
sessionStorage.setItem('authToken', token);

✅ Correct

// Use httpOnly cookies for tokens (set by server)
// Server response:
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

// Store only non-sensitive preferences
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'en');

// For tokens that must be in JS, use memory-only storage
class TokenStore {
  #token = null; // Private field, not persisted

  setToken(token) {
    this.#token = token;
  }

  getToken() {
    return this.#token;
  }
}

// Or use a closure
const tokenStore = (() => {
  let token = null;
  return {
    set: (t) => {
      token = t;
    },
    get: () => token,
  };
})();

Options

OptionTypeDefaultDescription
allowInTestsbooleanfalseAllow in test files
sensitiveKeysstring[]['token', 'password', 'secret', 'key', 'auth', 'credential', 'ssn', 'credit']Keys considered sensitive
{
  "rules": {
    "browser-security/no-sensitive-localstorage": [
      "error",
      {
        "sensitiveKeys": ["token", "password", "apiKey", "secret"]
      }
    ]
  }
}

Best Practices

1. Use httpOnly Cookies for Tokens

// Backend sets the cookie
res.cookie('accessToken', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 3600000,
});

// Frontend just makes requests (cookie sent automatically)
fetch('/api/protected', {
  credentials: 'include',
});

2. Use In-Memory Storage

// Token disappears on page refresh (good for security)
let accessToken = null;

function setToken(token) {
  accessToken = token;
}

function getToken() {
  return accessToken;
}

Comparison: Storage Options

MethodXSS Safe?Persists?Use For
httpOnly Cookie✅ Yes✅ YesAuth tokens
Memory variable✅ Yes❌ NoTemporary tokens
sessionStorage❌ NoTab onlyNon-sensitive data
localStorage❌ No✅ YesOnly user preferences

Known False Negatives

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

Dynamic Key Names

Why: Computed key names are not analyzed.

// ❌ NOT DETECTED - Dynamic key
const key = 'accessToken';
localStorage.setItem(key, value);

Mitigation: Use consistent naming. Configure sensitiveKeys pattern.

Nested Sensitive Data

Why: Sensitive data inside objects may not be detected.

// ❌ NOT DETECTED - Nested sensitive data
localStorage.setItem('user', JSON.stringify({ token: jwt }));

Mitigation: Never store objects containing tokens.

Wrapper Functions

Why: Custom storage wrappers are not recognized.

// ❌ NOT DETECTED - Wrapper function
storageHelper.save('token', jwt); // Uses localStorage internally

Mitigation: Apply rule to wrapper implementations.

IndexedDB

Why: Different API not covered by this rule.

// ❌ NOT DETECTED - IndexedDB
db.put({ token: jwt });

Mitigation: Use no-sensitive-indexeddb rule.

Resources

On this page