PCI-DSS 4.0 Req 3.5 mandates encryption of stored account data; Req 3.3 prohibits storage of sensitive authentication data post-authorization. A database without encryption at rest means any storage-layer compromise — a stolen disk, a snapshot shared to the wrong account, or a cloud misconfiguration — exposes cardholder data in plaintext. NIST SP 800-53 SC-28 and OWASP A02 (Cryptographic Failures) both treat unencrypted data stores as high-severity findings. For managed services like RDS or Supabase, missing storage_encrypted = true means the infrastructure configuration actively opts out of protection that is trivially available.
Critical because an unencrypted database volume means a storage-layer breach yields plaintext cardholder data with no decryption barrier, satisfying PCI-DSS Req 3.5 as a direct violation.
Enable encryption at rest before the database instance is created — this cannot be changed on a running RDS instance without a snapshot-and-restore cycle. Store the encryption key in a managed key service, never in the codebase. Reference terraform/main.tf for infrastructure changes.
resource "aws_db_instance" "main" {
storage_encrypted = true
kms_key_id = aws_kms_key.db.arn
}
For Supabase, encryption at rest is managed automatically but verify it in the project dashboard under Settings > Infrastructure. For self-managed PostgreSQL, add column-level encryption via pgcrypto as a supplementary control where field-level sensitivity is highest.
ID: ecommerce-pci.cardholder-data.database-encryption
Severity: critical
What to look for: Count all database instances referenced in infrastructure code (Terraform, CloudFormation, docker-compose, Kubernetes manifests). For each instance, check for encryption-at-rest configuration. For managed services (RDS, Cloud SQL, Azure SQL, Supabase), look for storage_encrypted = true or equivalent in service configs. For self-managed databases, look for Transparent Data Encryption (TDE), file-level encryption, or encrypted volume configs. Count the number of encryption key references (KMS, vault, etc.).
Pass criteria: Database encryption at rest is enabled on at least 1 database instance. For managed services, storage_encrypted or equivalent is set to true in infrastructure code. For self-managed databases, encryption is enabled and keys are stored separately from the database (at least 1 KMS or key management reference found). Report the count: X of Y database instances have encryption enabled.
Fail criteria: Database has no encryption at rest enabled, or encryption is disabled explicitly (e.g., storage_encrypted = false). Do NOT pass when encryption is mentioned only in comments or documentation but not in actual infrastructure configuration.
Skip (N/A) when: No database instances found in the project (no database dependencies in package.json, no ORM config, no database connection strings).
Detail on fail: Specify the database type and missing encryption. Example: "PostgreSQL database configured without Transparent Data Encryption (TDE). Encryption at rest not enabled. 0 of 1 instances encrypted." or "RDS instance created with StorageEncrypted: false in terraform/main.tf line 42"
Cross-reference: See ecommerce-pci.cardholder-data.cde-isolated (CDE isolation), ecommerce-pci.cardholder-data.no-default-credentials (credential management).
Remediation: Enable encryption at rest on your database. For Supabase/PostgreSQL:
-- Supabase manages TDE automatically, but verify in dashboard
-- For self-managed PostgreSQL with pgcrypto
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE sensitive_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
encrypted_value BYTEA,
created_at TIMESTAMP DEFAULT NOW()
);
-- For AWS RDS
-- Enable in Terraform:
resource "aws_db_instance" "main" {
storage_encrypted = true
kms_key_id = aws_kms_key.db.arn
}
Verify encryption is enabled in your database service dashboard. Store encryption keys in a key management service (AWS KMS, Azure Key Vault, Google Cloud KMS), never in your codebase.