Fernet (AES-128) encrypts every API key at the field level before it touches the database. Key rotation, environment variables, and what happens if your DB is breached.
FERNET_KEY) on the host โ never in the database โ so a database-only breach exposes only ciphertext, not plaintext secrets.Database encryption comes in multiple forms, and they offer radically different security guarantees. Disk-level encryption (e.g., LUKS, BitLocker) protects data on stolen physical media but provides no protection against a running attacker with database credentials โ the database server decrypts everything transparently. Transport encryption (TLS) protects data in transit between application and database, but again, data sits as plaintext in the database itself. Field-level encryption (FLE) is different: the application encrypts specific field values before they ever reach the database, so the database stores ciphertext โ and a database-layer compromise (SQL injection, stolen DB credentials, database server breach) yields only encrypted data.
For FlowSquad specifically, the most sensitive data is API keys: OpenAI keys, Anthropic keys, Jira API tokens, GitHub OAuth secrets, SAML certificates, and Confluence tokens. These secrets are worth significantly more to an attacker than the application data they protect โ a stolen $0.01/1k-token OpenAI key can run up thousands of dollars in fraudulent API usage within hours. FLE ensures that even if your FlowSquad PostgreSQL instance is fully compromised, no API key material is recoverable without the host-level Fernet key.
FlowSquad uses Python's cryptography library, which implements the Fernet specification: AES-128-CBC symmetric encryption with PKCS7 padding, combined with HMAC-SHA256 for authentication. The Fernet token format is: Version(1) || Timestamp(8) || IV(16) || Ciphertext(n) || HMAC(32) โ all base64url-encoded. The HMAC component is critical: it detects any tampering with the ciphertext in the database and raises an exception before decryption is attempted, preventing padding oracle attacks.
FlowSquad applies field-level encryption to 14 field types across its data models. These are annotated in the ORM layer with a custom EncryptedField descriptor that transparently encrypts on write and decrypts on read. Application code above the model layer never handles ciphertext directly.
| Field / Model | Data Stored | Encryption |
|---|---|---|
| LLM Provider API Key | OpenAI / Anthropic / Azure API keys | Fernet AES-128 |
| Jira API Token | Jira Cloud / Server API token | Fernet AES-128 |
| Confluence API Token | Confluence REST API token | Fernet AES-128 |
| Git OAuth Secret | GitHub / GitLab / Bitbucket OAuth app secret | Fernet AES-128 |
| Git Personal Access Token | PAT for repository access | Fernet AES-128 |
| SAML Certificate | IdP signing certificate (PEM) | Fernet AES-128 |
| VectorDB API Key | Pinecone API key (if used) | Fernet AES-128 |
| SMTP Password | Email notification SMTP credentials | Fernet AES-128 |
| Webhook Secret | Git provider webhook HMAC secrets | Fernet AES-128 |
| SSO Client Secret | OAuth 2.0 client secret for OIDC | Fernet AES-128 |
FlowSquad's key rotation leverages Python cryptography's MultiFernet class, which accepts a list of Fernet keys and tries them in order during decryption. The rotation process proceeds in three phases and requires zero downtime:
Phase 1 โ Dual-key mode (5 minutes): The new key is added as the primary key in FERNET_KEY, and the old key is added as FERNET_KEY_PREVIOUS. The application creates a MultiFernet instance with both keys. New writes use the new key; existing ciphertext from the old key is still decryptable.
Phase 2 โ Background re-encryption (2โ3 minutes): An admin triggers the "Rotate Encryption" background job from Admin โ Security โ Key Rotation. The job reads every encrypted field, decrypts with MultiFernet (which handles both old and new keys), and re-writes using the new key only. Progress is shown in a real-time progress bar.
Phase 3 โ Old key removal: Once the job completes, FERNET_KEY_PREVIOUS is removed from the environment. All ciphertext in the database is now encrypted with the new key only. The old key is securely destroyed per the organization's key management policy.
A database-only breach โ where an attacker gains read access to all tables and rows but does not have access to the application host or its environment variables โ yields exclusively ciphertext for all 14 encrypted field types. The Fernet format does not expose the key length, algorithm parameters, or any plaintext prefix. Without the FERNET_KEY environment variable, the ciphertext is computationally infeasible to decrypt โ AES-128 brute-force at current compute rates would require longer than the heat death of the universe.
Breach scenario verdict: A database dump from a compromised FlowSquad PostgreSQL instance exposes: user email addresses (not encrypted โ used for login), hashed passwords (bcrypt, not decryptable), product names and generated content (not encrypted by design), and Fernet ciphertext for all 14 secret field types. No API keys, OAuth secrets, or certificates are recoverable. An attacker cannot pivot from a DB breach to LLM API abuse or code repository access.
Fernet uses AES-128-CBC. While AES-256 has a larger key space, AES-128 has no known practical vulnerabilities and is considered computationally secure by NIST through at least 2030. The effective security of the Fernet key comes from the randomness of the 128-bit key material, not from brute-force resistance differences between 128 and 256 bits at current compute levels. FlowSquad's Enterprise tier supports AES-256 via a configurable cipher upgrade (roadmap Q2 2026).
The FERNET_KEY should be stored in a secrets manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) and injected as an environment variable at container startup. It should never be committed to source control, logged, or written to disk in plaintext. FlowSquad's deployment Helm chart includes a SecretKeyRef reference for Kubernetes deployments.
No. User passwords are hashed with bcrypt (cost factor 12), not encrypted. Hashing is the correct mechanism for passwords because it is one-way โ FlowSquad never needs to recover a plaintext password, only to verify a candidate password against the stored hash. Fernet encryption is used for secrets that must be recoverable (API keys, tokens) โ a fundamentally different use case.
Database migrations (applied automatically or via manage.py migrate) never touch encrypted field values โ they only modify schema structure (add/remove columns, change indices). Encrypted field values are written and read by application code only, not by migration scripts. This means the Fernet key environment variable continuity across upgrades is the only requirement for encrypted fields to remain decryptable post-upgrade.
Direct HSM integration (via PKCS#11) is on the Enterprise roadmap for Q3 2026. In the meantime, cloud HSM-backed secrets managers (AWS KMS + Secrets Manager, Azure Key Vault) can be used as the source of the FERNET_KEY environment variable, providing HSM-grade key protection for the Fernet key material itself without requiring direct PKCS#11 integration in FlowSquad's application code.