Unencrypted data at rest is accessible to anyone with storage access — a misconfigured S3 bucket, a stolen database volume, or a compromised hosting account exposes every user record without any additional decryption step. NIST 800-53 rev5 SC-28 (Protection of Information at Rest) requires encryption for sensitive data in storage; SC-8 covers transit encryption; IA-5(1) specifically requires password hashing with approved algorithms. FedRAMP rev5 SC-28 applies to all CUI and federal data stored in cloud systems. CWE-311 (Missing Encryption of Sensitive Data) covers both cases. MD5 and SHA-1 password hashing are broken — bcrypt and Argon2 remain the approved choices.
Low in this audit context because verifying encryption relies partly on documentation and provider configuration rather than direct code inspection, but the underlying risk of plaintext data exposure is severe.
Verify your database provider's encryption-at-rest setting and document it explicitly. Hash passwords with bcrypt (cost factor ≥12) or Argon2id — never SHA-1 or MD5.
// lib/auth/password.ts
import bcrypt from 'bcryptjs'
export async function hashPassword(plain: string): Promise<string> {
return bcrypt.hash(plain, 12) // cost factor 12 = ~250ms on modern hardware
}
export async function verifyPassword(plain: string, hash: string): Promise<boolean> {
return bcrypt.compare(plain, hash)
}
For Supabase: encryption at rest is enabled by default for all projects — document this in docs/security.md so auditors can confirm it. For fields requiring application-layer encryption (PII, health data), use AES-256-GCM via Node's built-in crypto module with a key stored in your secrets manager, not in source code.
ID: gov-fisma-fedramp.documentation-readiness.encryption-configured
Severity: low
What to look for: Enumerate all data storage locations (database, file storage, cache). For each, check whether encryption-at-rest is documented or configured. Quote the actual encryption configuration or provider documentation found. Verify HTTPS/TLS for data in transit. Check for password hashing algorithms used (bcrypt, argon2, scrypt — not MD5 or SHA-1 alone).
Pass criteria: Data in transit is encrypted (HTTPS/TLS 1.2+). Documentation mentions or verifies data-at-rest encryption for at least 1 storage location (either via provider like Supabase/AWS, database encryption, or application-level encryption). Passwords are hashed with a modern algorithm (bcrypt, argon2, or scrypt). Report the count of storage locations verified.
Fail criteria: Data in transit is not encrypted, or there's no mention of encryption for data at rest across any storage location.
Skip (N/A) when: Never — encryption is fundamental.
Detail on fail: Specify the gaps. Example: "Data in transit protected by HTTPS. No mention of encryption at rest — unclear whether sensitive data is encrypted in database."
Remediation: Ensure encryption is documented:
# Encryption
## Data In Transit
- All communication uses HTTPS/TLS 1.2+
- Hosted on Vercel, which provides automatic HTTPS via CDN
## Data At Rest
- Database: Supabase PostgreSQL, which provides encryption at rest
- Sensitive fields (passwords) are hashed with bcrypt
- API keys and secrets stored in environment variables (server-side only)
For sensitive fields, encrypt them explicitly:
// lib/encryption.ts
import crypto from 'crypto'
const algorithm = 'aes-256-gcm'
const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex')
export function encrypt(text: string): string {
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv(algorithm, key, iv)
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
const authTag = cipher.getAuthTag()
return iv.concat(encrypted).concat(authTag).toString('hex')
}
export function decrypt(encrypted: string): string {
const data = Buffer.from(encrypted, 'hex')
const iv = data.subarray(0, 16)
const encryptedData = data.subarray(16, data.length - 16)
const authTag = data.subarray(data.length - 16)
const decipher = crypto.createDecipheriv(algorithm, key, iv)
decipher.setAuthTag(authTag)
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()])
return decrypted.toString('utf8')
}