Skip to main content
interlace
Plugin: browser-securityRules

no-sensitive-indexeddb

Prevent storing sensitive data in IndexedDB.

No Sensitive Indexeddb

⚠️ Security Issue

PropertyValue
CWECWE-922: Insecure Storage of Sensitive Information
OWASPA02:2021 - Cryptographic Failures
CVSS7.5 (High)
SeverityHIGH

📋 Description

IndexedDB is accessible via JavaScript and can be stolen through XSS attacks. Sensitive data like passwords, tokens, and API keys should not be stored in client-side databases.

The rule reports a sensitive name written to IndexedDB, matched as whole words (plurals folded, so passwords matches password; tokenizer does not match token):

  • the store name in db.createObjectStore(name);
  • a field name in the record of store.add({ … }) / store.put({ … });
  • the out-of-line record key in store.put(value, key) / store.add(value, key);
  • with the idb wrapper, the store name and record of db.put(storeName, value, key) and the key in its third argument.

.add / .put count only when the receiver resolves to an objectStore() / createObjectStore() call (or an idb database) in the file: jobQueue.add({ password }) is a job queue, not IndexedDB.

The vocabulary is sensitiveTerms — by default every non-bearer secret word plus the bearer-credential words (token, jwt, session, …). Setting sensitiveTerms replaces the whole list, bearer words included; additionalPatterns is appended to whatever the list is.

❌ Incorrect

// Creating object store for sensitive data
db.createObjectStore('passwords');
db.createObjectStore('secrets');
db.createObjectStore('apiKeys');

// Storing sensitive data
const store = db.transaction('vault', 'readwrite').objectStore('vault');
store.add({ password: userPassword });
store.put({ apiKey: key });
store.put(value, 'password'); // out-of-line key

✅ Correct

// Store non-sensitive data
db.createObjectStore('preferences');
db.createObjectStore('cachedData');

// Store user preferences
const prefs = db.transaction('prefs', 'readwrite').objectStore('prefs');
prefs.add({ theme: 'dark', language: 'en' });

// Not IndexedDB: an unrelated receiver
jobQueue.add({ password: tempPassword });

// A replaced vocabulary replaces everything
// { "sensitiveTerms": ["pin"] }
db.createObjectStore('accessToken'); // not reported under this config

🛠️ Options

OptionTypeDefaultDescription
allowInTestsbooleantrueSkip this rule in *.test.* / *.spec.* files
sensitiveTermsstring[]["password","passwd","pwd","passphrase","secret","api key","apikey","private key","secret key","encryption key","signing key","client secret","credit card","card number","cvv","cvc","ssn","social security","seed phrase","mnemonic","recovery code","jwt","token","bearer","auth","authorization","session","sid","credential","credentials"]Whole-word terms that mark a stored field as sensitive. Replaces the default vocabulary.
additionalPatternsstring[][]Extra terms that name a secret, added to the built-in vocabulary rather than replacing it. Matched as whole words against the store or key name, never as a substring — so token does not match tokenizer. Use this for domain secrets the built-in list cannot know about, e.g. entitlementGrant.
{
  "rules": {
    "browser-security/no-sensitive-indexeddb": [
      "error",
      {
        "allowInTests": true
      }
    ]
  }
}

Known False Negatives

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

Dynamic Store Names

Why: Computed names not analyzed.

// ❌ NOT DETECTED - Dynamic name
const name = 'passwords';
db.createObjectStore(name);

Mitigation: Use static store names. Avoid sensitive naming.

Nested Sensitive Data

Why: Deep object properties not traced.

// ❌ NOT DETECTED - Nested data
store.put({ user: { password: pwd } });

Mitigation: Never store sensitive data in IndexedDB.

Library Wrappers

Why: IndexedDB wrappers not recognized.

// ❌ NOT DETECTED - Dexie wrapper
db.secrets.add({ apiKey: key });

Mitigation: Apply rule to wrapper implementations.

Use this rule

npm install -D oxlint-plugin-browser-security
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-browser-security"],  "rules": {    "browser-security/no-sensitive-indexeddb": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-browser-security'],    rules: {      'browser-security/no-sensitive-indexeddb': 'error',    },  },});

oxlint-plugin-browser-security re-exports eslint-plugin-browser-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.

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.