no-sensitive-indexeddb
Prevent storing sensitive data in IndexedDB.
No Sensitive Indexeddb
⚠️ Security Issue
| Property | Value |
|---|---|
| CWE | CWE-922: Insecure Storage of Sensitive Information |
| OWASP | A02:2021 - Cryptographic Failures |
| CVSS | 7.5 (High) |
| Severity | HIGH |
📋 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
idbwrapper, the store name and record ofdb.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
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
sensitiveTerms | string[] | ["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. |
additionalPatterns | string[] | [] | 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.
📚 Related Resources
Use this rule
npm install -D oxlint-plugin-browser-security{ "jsPlugins": ["oxlint-plugin-browser-security"], "rules": { "browser-security/no-sensitive-indexeddb": "error" }}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.