Skip to main content
interlace
Plugin: node-securityRules

require-storage-encryption

CWE-312

Quick Summary

AspectDetails
SeverityHigh (Data at Rest Exposure)
Auto-Fix❌ No (requires encryption logic)
CategorySecurity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForApplications storing PII or tokens

Vulnerability and Risk

Vulnerability: Cleartext storage of sensitive information occurs when data is written to persistent storage (files, databases, local storage) without being encrypted first.

Risk: If the storage medium is compromised (e.g., stolen device, unauthorized file access, backup leak), attackers can read sensitive data like passwords, session tokens, or PII directly.

Error Message Format

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

🔒 CWE-312 OWASP:M2 | Missing Storage Encryption detected | HIGH [DataAtRest]
   Fix: Wrap sensitive data in an encryption function before calling setItem/writeFile | https://cwe.mitre.org/data/definitions/312.html

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-312 OWASP:M2
Issue DescriptionSpecific vulnerabilityMissing Storage Encryption detected
Severity & ComplianceImpact assessmentHIGH [DataAtRest]
Fix InstructionActionable remediationWrap data in an encryption function
Technical TruthOfficial referenceCleartext Storage

Rule Details

This rule flags writeFile/writeFileSync/appendFile/appendFileSync calls that put a credential on disk without a provable encryption call around the value. Client storage (localStorage, sessionStorage, AsyncStorage) belongs to require-secure-credential-storage; the two rules used to share both receivers and reported every match twice.

Why This Matters

IssueImpactSolution
🕵️ Data ExposurePhysical access leads to data leakEncrypt data before writing to disk
🚀 ExfiltrationStored tokens can be stolen and reusedUse authenticated encryption (AES-GCM)
🔒 ComplianceFailure to meet GDPR/SOC2 requirementsImplement encryption at rest for all sensitive datasets

Configuration

This rule has no configuration options in the current version.

Examples

❌ Incorrect

// Writing sensitive data to a file without encryption
fs.writeFile('user_data.json', JSON.stringify(userData));

// Writing a token to disk in cleartext
fs.writeFileSync('session_token.txt', token);

✅ Correct

const x = 42;

Known False Negatives

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

Custom Storage Methods

Why: This rule specifically looks for setItem and writeFile. Custom wrappers or database save() methods are not analyzed.

Mitigation: Standardize on a few secure storage utilities and audit them centrally.

Weak Encryption

Why: This rule checks that the value provably comes out of a cryptographic encryption API. It does not verify the strength of the algorithm used.

Mitigation: Use a trusted crypto library and follow the Node Security Crypto Standard (planned).

References

Not a finding

This rule owns the filesystem. Client storage — localStorage, sessionStorage, AsyncStorage — belongs to require-secure-credential-storage. Both require evidence that what is being stored is a credential:

CodeWhy it is silent
fs.writeFile(sitemapPath, sitemap)A write, but nothing says a credential is in it.
fs.writeFileSync('session_token.txt', encrypt(token))Encrypted on the way out — where encrypt provably encrypts (below).
const key = fs.readFileSync(path.join(__dirname, './ssl.key'))A read, not a write — and reading a TLS key at startup is how TLS works. key on its own is deliberately not treated as evidence.

The write is recognised through the fs module however it is bound — fs.writeFileSync, fs.promises.writeFile, or a named import { writeFile } from 'node:fs/promises'.

What counts as encrypted is decided by where the call comes from, never by what it is called: the call resolves to an encryption API of a crypto module (node:crypto's publicEncrypt, a createCipheriv cipher's update/final, crypto-js, WebCrypto), or to a function declared in this file whose return value does. An encrypt imported from a project module (import { encrypt } from './vault'), ambient-declared, or declared nowhere is unproven, and unproven is not encrypted — with a credential named, it reports. Same contract as require-secure-credential-storage.

If it fires, either the filename or the value named a credential. If that name is misleading — a variable called tokenizer, say — renaming it is the better fix than a disable comment, because the next reader will make the same mistake the rule did.

Use this rule

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

oxlint-plugin-node-security re-exports eslint-plugin-node-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.