Encrypted Outputs

Oxid automatically encrypts sensitive output values at rest using AES-256 via PostgreSQL's pgcrypto extension. Zero configuration required.

Overview

When using the PostgreSQL backend, any output marked with sensitive = true is automatically encrypted before being written to the database. This protects secrets like database passwords, API keys, and connection strings from exposure through direct database access.

outputs.tf
output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true    # This output will be encrypted at rest
}

output "vpc_id" {
  value = aws_vpc.main.id
  # Not sensitive - stored in plaintext
}

How It Works

Encryption

When Oxid persists a sensitive output during apply:

  1. The output value is serialized to JSON
  2. An encryption key is derived from the SHA-256 hash of your OXID_DATABASE_URL
  3. The value is encrypted using pgp_sym_encrypt (AES-256) via pgcrypto
  4. The encrypted blob is stored in the outputs table

Decryption

When you read a sensitive output (via oxid output or oxid query), Oxid transparently decrypts it using the same key derived from your connection string. No extra flags or configuration needed.

Key derivation

Key = SHA-256(OXID_DATABASE_URL)

The encryption key is derived deterministically from your database connection URL. This means:

  • Anyone with the same connection URL can decrypt the outputs
  • If you change the connection URL, existing encrypted outputs become unreadable
  • The key is never stored - it is derived at runtime

Zero Configuration

Encryption is enabled automatically when all three conditions are met:

  • OXID_DATABASE_URL is set (PostgreSQL backend)
  • The pgcrypto extension is available
  • The output is marked sensitive = true

No flags, no config files, no key management. It just works.

NoteOxid installs the pgcrypto extension automatically during init. If your database user lacks the privilege, install it manually as a superuser: CREATE EXTENSION IF NOT EXISTS pgcrypto;

SQLite Behavior

The SQLite backend does not support encryption. Sensitive outputs are stored in plaintext in the local .oxid/oxid.db file.

WarningIf you use SQLite with sensitive outputs, ensure the .oxid/ directory has appropriate file permissions and is not committed to version control.
# Secure your local state file
chmod 600 .oxid/oxid.db
echo ".oxid/" >> .gitignore

What Gets Encrypted

Only output values with sensitive = true are encrypted. Specifically:

  • The value column in the outputs table

The following are NOT encrypted:

  • Resource attributes (even if they contain secrets)
  • Non-sensitive outputs
  • Resource history entries
  • Run metadata
TipTo protect sensitive resource attributes, use PostgreSQL's built-in row-level security or encrypt your database volume at the infrastructure level.

Verifying Encryption

You can verify that outputs are encrypted by querying the database directly:

-- Direct database query (shows encrypted blob)
SELECT name, value FROM outputs WHERE sensitive = true;
-- value column contains binary pgcrypto ciphertext

-- Through Oxid (transparent decryption)
oxid output --json
-- Shows the actual decrypted values

Rotating the Encryption Key

If you need to rotate the encryption key (e.g., changing the database URL), you must re-apply to re-encrypt all outputs with the new key:

# 1. Read current outputs while you still have the old URL
oxid output --json > /tmp/outputs-backup.json

# 2. Update the connection URL
export OXID_DATABASE_URL="postgresql://user:new-pass@new-host:5432/oxid_state"

# 3. Re-apply to re-encrypt outputs with the new key
oxid apply