ADR-003: Field-Level Encryption by Default¶
Status: Accepted Date: 2026-02-01 Deciders: adk-secure-sessions maintainers
Context¶
There are two fundamentally different approaches to encrypting session data:
-
Full-database encryption (SQLCipher): Encrypts the entire database file. Everything is opaque on disk. Transparent to the application layer.
-
Field-level encryption (Fernet, KMS): Encrypts individual values within the database. Session metadata (app_name, user_id, timestamps) can remain in plaintext for querying, while sensitive state values are encrypted.
Both are valid. The choice affects what users can query, how key management works, and which compliance frameworks are satisfied.
Decision¶
Field-level encryption is the default approach. The default backend (Fernet) encrypts session state values and event data while leaving session metadata queryable.
What Gets Encrypted¶
| Data | Encrypted | Rationale |
|---|---|---|
state values (user_state, app_state) | Yes | Contains sensitive user/app data |
events (conversation history) | Yes | Contains user messages, tool outputs, PII |
session_id | No | Needed for lookups |
app_name | No | Needed for filtering/routing |
user_id | No | Needed for filtering/routing |
create_time, update_time | No | Needed for expiration/cleanup queries |
Encryption Boundary¶
┌──────────────────────────────────────────────┐
│ Database Row │
│ │
│ session_id: "abc-123" (plaintext) │
│ app_name: "my_agent" (plaintext) │
│ user_id: "user_456" (plaintext) │
│ update_time: "2026-02-01T..." (plaintext) │
│ state: "gAAAAA...Bx9k=" (encrypted) │
│ events: "gAAAAA...Mw2f=" (encrypted) │
└──────────────────────────────────────────────┘
Why Not Encrypt Everything¶
- Queryability: ADK's
list_sessionsfilters byapp_nameanduser_id. Encrypting these would require loading and decrypting every session to filter — O(n) instead of O(1). - Operational needs: Timestamps are needed for session expiration, cleanup cron jobs, and monitoring dashboards.
- Compliance alignment: HIPAA, SOC 2, and PCI-DSS require encryption of sensitive data (PHI, PII, cardholder data), not necessarily operational metadata. Encrypting everything often exceeds requirements while degrading usability.
Full-Database Encryption as an Option¶
SQLCipher support is planned as an alternative backend for users who want everything encrypted regardless. This is additive — users choose based on their threat model:
- Field-level (default): Protects sensitive data, preserves queryability
- SQLCipher: Protects everything, requires passphrase management, no query on encrypted fields
Consequences¶
What becomes easier¶
- Session listing/filtering: Metadata queries work without decryption
- Monitoring: Operational dashboards can count sessions, check timestamps
- Migration: Only state/event columns change; schema remains queryable
- Debugging: Session metadata visible for troubleshooting without decryption keys
What becomes harder¶
- Metadata leakage: An attacker with database access can see who has sessions, when they were active, and which app they used. They cannot see conversation content or state values.
- User ID sensitivity: If
user_iditself is PII (e.g., an email address), users should use opaque identifiers. This is a best practice regardless of encryption.
Trade-offs¶
- Field-level encryption adds per-field overhead (Fernet prefix, HMAC, IV per value) vs SQLCipher's single-file overhead. For typical session sizes this is negligible.
- Key management is simpler with field-level (one key encrypts values) vs SQLCipher (passphrase must be provided at connection time).
Alternatives Considered¶
Full-Database Encryption Only¶
Not chosen as default. SQLCipher requires native library installation (sqlcipher3), has no async driver, and prevents querying encrypted fields. It's a better fit as an opt-in backend for maximum security requirements.
Encrypt Everything Including Metadata¶
Rejected. Would break ADK's list_sessions(app_name=..., user_id=...) contract. We'd need to implement our own indexing layer, effectively building a custom encrypted database. Out of scope for a session encryption library.
Let Users Choose Per-Field¶
Considered for future. A field selector (encrypt_fields=["state", "events"]) would give granular control. The current default covers the common case. This can be added as a non-breaking enhancement.