Architecture¶
Overview¶
adk-secure-sessions wraps ADK's DatabaseSessionService with transparent encryption via a custom SQLAlchemy TypeDecorator (EncryptedJSON). The EncryptionBackend protocol (PEP 544) defines the backend contract; currently only FernetBackend is supported, with generalized multi-backend dispatch planned for Epic 3. See ADR-007 for the migration decision record.
Color Legend
- Green — Implemented and tested
- Gray — Planned (see Roadmap)
graph TD
UC[User Code] --> ESS[EncryptedSessionService]
ESS -->|subclasses| DSS[ADK DatabaseSessionService]
DSS -->|ORM operations| TD[TypeDecorator — EncryptedJSON]
TD -->|encrypts/decrypts| FB[FernetBackend]
FB -.->|implements| EB[EncryptionBackend Protocol]
EB -.-> CB[Custom Backend — planned]
TD -->|reads/writes| DB[(SQLite / PostgreSQL / MySQL / MariaDB)]
style EB fill:#2e7d32,stroke:#1b5e20,color:#fff
style UC fill:#616161,stroke:#424242,color:#fff
style ESS fill:#2e7d32,stroke:#1b5e20,color:#fff
style DSS fill:#2e7d32,stroke:#1b5e20,color:#fff
style TD fill:#2e7d32,stroke:#1b5e20,color:#fff
style FB fill:#2e7d32,stroke:#1b5e20,color:#fff
style CB fill:#616161,stroke:#424242,color:#fff
style DB fill:#2e7d32,stroke:#1b5e20,color:#fff Data Flow¶
Encryption is transparent at the ORM boundary. The EncryptedJSON TypeDecorator intercepts SQLAlchemy's process_bind_param (write) and process_result_value (read), so DatabaseSessionService operates on plaintext dicts while the database stores only ciphertext.
sequenceDiagram
participant App as User Code
participant ESS as EncryptedSessionService
participant DSS as DatabaseSessionService
participant TD as EncryptedJSON TypeDecorator
participant DB as Database
Note over App,DB: create_session / append_event
App->>ESS: state dict
ESS->>DSS: delegates CRUD
DSS->>TD: process_bind_param(dict)
TD->>TD: json.dumps → encrypt → base64
TD->>DB: write TEXT (base64 ciphertext)
Note over App,DB: get_session
DB-->>TD: read TEXT (base64 ciphertext)
TD->>TD: base64 → decrypt → json.loads
TD-->>DSS: dict
DSS-->>ESS: Session with decrypted state
ESS-->>App: Session Encryption Boundary¶
Field-level encryption protects sensitive data while keeping metadata queryable.
| Data | Encrypted | Rationale |
|---|---|---|
state values (user_state, app_state, session_state) | Yes | Contains sensitive user/app data |
events (conversation history) | Yes | Contains user messages, tool outputs, PII |
session_id, app_name, user_id | No | Needed for lookups and filtering |
create_time, update_time | No | Needed for expiration and cleanup |
Package Structure¶
graph LR
subgraph protocols.py
EB2[EncryptionBackend Protocol]
end
subgraph services/
ESS2[EncryptedSessionService]
TD2[type_decorator.py — EncryptedJSON]
MOD2[models.py — Encrypted Models]
end
subgraph backends/
FB2[FernetBackend]
end
subgraph serialization.py
SER[envelope helpers]
end
ESS2 -->|depends on| EB2
ESS2 -->|uses| TD2
ESS2 -->|uses| MOD2
TD2 -->|sync callables from| FB2
TD2 -->|uses| SER
FB2 -->|implements| EB2
style EB2 fill:#2e7d32,stroke:#1b5e20,color:#fff
style ESS2 fill:#2e7d32,stroke:#1b5e20,color:#fff
style TD2 fill:#2e7d32,stroke:#1b5e20,color:#fff
style MOD2 fill:#2e7d32,stroke:#1b5e20,color:#fff
style FB2 fill:#2e7d32,stroke:#1b5e20,color:#fff
style SER fill:#2e7d32,stroke:#1b5e20,color:#fff Layer Rules¶
services/depends onprotocols.py(the contract), never on concrete backendsbackends/implementsprotocols.py— each backend is self-containedprotocols.pyhas zero dependencies (stdlibtypingonly)serialization.pyhandles the encrypt-on-write / decrypt-on-read boundary
Current State¶
Implemented:
protocols.py—EncryptionBackendprotocol withencrypt/decryptasync methods,@runtime_checkablebackends/fernet.py—FernetBackendusing Fernet symmetric encryption with PBKDF2 key derivationexceptions.py—SecureSessionErrorbase,EncryptionError,DecryptionError,SerializationError,ConfigurationErrorserialization.py— Envelope helpers (_build_envelope,_parse_envelope) and constants for the[version][backend_id][ciphertext]format (see Envelope Protocol Specification)services/type_decorator.py—EncryptedJSONTypeDecorator that transparently encrypts/decrypts at the SQLAlchemy ORM boundaryservices/models.py— Encrypted SQLAlchemy model classes replacing ADK'sDynamicJSONwithEncryptedJSONservices/encrypted_session.py—EncryptedSessionServicewrapping ADK'sDatabaseSessionServicewith:- All CRUD operations (
create_session,get_session,list_sessions,delete_session) delegated toDatabaseSessionService - Transparent encryption via
EncryptedJSONTypeDecorator on state and event data columns - Multi-database support (SQLite, PostgreSQL, MySQL, MariaDB) via
DatabaseSessionService's dialect handling - Current limitation:
EncryptedSessionServiceextracts sync encrypt/decrypt callables fromFernetBackend._fernet; custom backends that only implement theEncryptionBackendprotocol are not yet supported (tracked in Epic 3 TODO) __init__.py— Exports all public symbols (protocols, backends, exceptions, serialization functions, services, constants)
Planned (see Roadmap):
- Key rotation support
- AES-256-GCM encryption backend
- KMS backends (AWS, GCP, HashiCorp Vault)
Design Decisions¶
See the Architecture Decision Records for detailed rationale:
| ADR | Decision |
|---|---|
| ADR-000 | Strategy + direct implementation (partially superseded by ADR-007) |
| ADR-001 | typing.Protocol over ABC for backend interfaces |
| ADR-002 | Async-first design matching ADK's runtime |
| ADR-003 | Field-level encryption as default, not full-database |
| ADR-004 | Own schema, no coupling to ADK internals (narrowed by ADR-007) |
| ADR-005 | Focused exception hierarchy |
| ADR-007 | Architecture migration — DatabaseSessionService wrapping via TypeDecorator |