# Any PII stored locally is encrypted

- **Pattern:** `ab-001307` (`extension-data-privacy.privacy-disclosures.local-pii-encrypted`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001307
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001307)

## Why it matters

Plaintext PII in `chrome.storage.local`, `chrome.storage.sync`, `localStorage`, or `IndexedDB` is readable by any code with access to the extension's storage context — including a compromised extension update, a malicious dependency, or a script that gains access via a content script bridge. GDPR Art. 32 requires appropriate technical measures to protect personal data; storing an email address or user profile in cleartext fails that standard. CWE-312 (Cleartext Storage of Sensitive Information) and OWASP 2021 A02 (Cryptographic Failures) both target this exact pattern. NIST SP 800-53 SC-28 mandates data at rest protection for sensitive records.

## Severity rationale

High because plaintext PII in extension storage is readable by any compromised extension update or malicious dependency that gains access to the storage context — encryption is the only mitigation that survives a supply chain compromise.

## Remediation

Encrypt PII before writing it to any storage mechanism. Use TweetNaCl.js for symmetric encryption:

```js
import nacl from 'tweetnacl';
import { encodeBase64, decodeUTF8 } from 'tweetnacl-util';

const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
const keyMaterial = await deriveKey(masterPassword); // scrypt or PBKDF2

const encrypted = nacl.secretbox(
  decodeUTF8('user@example.com'),
  nonce,
  keyMaterial
);

chrome.storage.local.set({
  encryptedEmail: encodeBase64(encrypted),
  emailNonce: encodeBase64(nonce)
});
```

For lighter-weight cases, `crypto-js` provides AES-256 with a simpler API. Never store the encryption key alongside the ciphertext in the same storage location.

## Detection

- **ID:** `local-pii-encrypted`
- **Severity:** `high`
- **What to look for:** First, determine whether the extension stores any PII locally at all (via `chrome.storage.local`, `chrome.storage.sync`, `localStorage`, or `IndexedDB`). PII examples: email address, full name, username, profile data, preferences linked to identity, browsing history associated with a user. API keys and service credentials alone are NOT PII. If PII is stored locally, check whether it is encrypted before storage.
- **Skip (N/A) when:** The extension does not store any PII locally — it either sends data to external servers without local persistence, or only stores non-PII data such as API keys or anonymous settings. Evaluate skip BEFORE pass/fail: if no local PII storage exists, mark skip regardless of encryption patterns.
- **Pass criteria:** Enumerate all local storage writes that contain PII fields. Confirmed PII is stored locally AND 100% of PII fields are encrypted (e.g., using TweetNaCl.js, libsodium.js, or similar). At minimum, sensitive fields are obfuscated or hashed.
- **Fail criteria:** Confirmed PII found stored in plaintext in `chrome.storage.local`, `chrome.storage.sync`, `localStorage`, or `IndexedDB`. No encryption or hashing applied.
- **Detail on fail:** Specify the plaintext PII. Example: `"User email stored in chrome.storage.local without encryption: chrome.storage.local.set({ userEmail: 'user@example.com' })"` or `"User profile data cached as plaintext JSON in localStorage."`
- **Remediation:** Encrypt sensitive data before storage:

  ```js
  import nacl from 'tweetnacl';

  const secretKey = ...; // Store securely, e.g., derived from master password

  const plaintext = 'user@example.com';
  const encrypted = nacl.secretbox(
    nacl.util.decodeUTF8(plaintext),
    nonce,
    secretKey
  );

  chrome.storage.local.set({ encryptedEmail: nacl.util.encodeBase64(encrypted) });
  ```

  Or use a library like `crypto-js` for simpler AES encryption.

## External references

- cwe CWE-312
- owasp A02
- gdpr Art. 32
- nist SC-28

Taxons: cryptography-and-secrets, privacy-consent

HTML version: https://auditbuffet.com/patterns/ab-001307
