odandb/doctrine-ciphersweet-encryption-bundle
A Doctrine bundle that adds CipherSweet field-level encryption to your entities, letting you store sensitive data encrypted while keeping it searchable through blind indexes. Attribute-based config makes setup simple and secure.
Architecture fit. This is a Symfony bundle, not a Laravel package — it wires CipherSweet field-level encryption into Doctrine ORM entities via Doctrine event subscribers (onFlush/postLoad/postFlush). The underlying crypto (CipherSweet, blind indexes, key provider) is framework-agnostic, but the delivery mechanism is Symfony's bundle + DI container. For a Laravel-first estate this is an integration project, not a composer require. For a Symfony or mixed Symfony/Doctrine service, it fits cleanly into existing entity lifecycles with no application-layer code changes for basic encrypted fields.
Integration feasibility. Feasible for greenfield and for services already using Doctrine attributes for encrypted fields. The real effort is not installing the bundle; it is the surrounding work:
| Workstream | Effort | Notes |
|---|---|---|
| Key management | High | CipherSweet needs a KeyProvider; must decide on KMS/envelope encryption, rotation policy, per-tenant keys |
| Blind index design | Medium–High | Every searchable/filterable/unique field needs a blind index + schema column; this constrains future query design |
| Schema/column sizing | Medium | Ciphertext + blind index columns expand row size; affects indexes, sort order, uniqueness |
| Data migration | High | Existing plaintext rows must be encrypted backfill-style; dual-read/dual-write window required |
| Query audit | Medium | Any WHERE/ORDER BY/LIKE on encrypted fields breaks silently or errors; must be enumerated up front |
Technical risk. Version 0.13.0 is a 0.x pre-1.0 release with no BC guarantees. Two changes carry real, if moderate, risk:
symfony/property-info → symfony/type-info. This is the component used to resolve entity property types so the bundle knows how to encrypt/decrypt a field. Swapping the type-resolution engine is a behaviorally sensitive change: type inference edge cases (nullable, union types, typed collections, custom Doctrine types, inherited properties) are exactly where encryption silently mis-applies or throws at runtime. symfony/type-info is a newer component with a PHP 8.2+ floor and a different maturity curve than property-info. Expect a possible jump in minimum PHP/Symfony constraints — verify composer.json before committing.Additional risks: no documented migration/upgrade notes in the changelog; a single maintainer handles both PRs (bus factor); and encrypted-data packages are not forgiving of regressions — a decryption bug is a data-integrity incident, not a UI bug.
Key questions for the maintainer / vendor.
composer.json constraints — minimum PHP, Symfony, and Doctrine ORM versions? Is ORM 2.x still supported?type-info swap behaviorally identical for nullable, union, inherited, and custom-Doctrine-type properties? Any known edge cases?Recommendation: adoptable for a Symfony/Doctrine service with a named owner and a staging round-trip test; not a drop-in for a Laravel codebase without a wrapper decision. Treat 0.13.0 as a pinned dependency, not a floating ^0.13.
Stack fit. Determine first which of three situations you're in:
| Situation | Path |
|---|---|
| Symfony + Doctrine service | Native fit — install bundle, configure KeyProvider, annotate entities |
| Laravel + Eloquent service | No fit. Use paragonie/ciphersweet directly in model casts/mutators, or a Laravel encryption package; this bundle will not help |
| Laravel + Doctrine (e.g. via a Doctrine bridge) | Possible but off the happy path; register the subscriber manually and own the wiring |
Do not let "it's a PHP package" collapse these into one decision — the install instructions and the support burden are different in each.
Migration path. For existing plaintext data, sequence a controlled backfill rather than a big-bang cutover:
For greenfield, skip 1–4 but still validate blind-index query patterns against the product's actual search requirements before schema freeze — this is the most common source of rework.
Compatibility. Because 0.13.0 changes type resolution, pin exactly and test the upgrade in a branch:
If your platform is still on PHP 8.1 or Symfony 6.3-or-earlier, this release may simply be unavailable to you — confirm before planning the sprint.
Sequencing.
Rough sizing for a mid-size service: 1–2 sprints for greenfield with clean queries; 3–5 sprints for a migration with existing searchable data and key-management build-out.
Maintenance. At 0.x with a single active maintainer and two commits in this release, expect to own the upgrade cadence yourself — budget a recurring dependency-review slot each release rather than assuming upstream will carry compatibility. Pin exact versions and upgrade deliberately; never float the constraint on a package that sits in the data-integrity path.
Support. Internal expertise must exist for both halves: CipherSweet (key providers, blind indexes, ciphertext formats) and Doctrine's event lifecycle. There is no realistic path to "the framework team supports it." Name a single owning team and an escalation contact. Also establish an exit plan — because ciphertext is written into your database, you cannot casually swap this package out; the cost of replacing it is a full decrypt-and-re-encrypt migration. Factor that lock-in into the initial decision.
Scaling.
LIKE, and aggregate-by-encrypted-field are gone. This is a product constraint, not just a technical one — surface it to whoever owns search and reporting before, not after, commit.Failure modes. Rank by severity:
| Failure | Impact | Mitigation |
|---|---|---|
| Lost/rotated-wrong key | Catastrophic — ciphertext unrecoverable | KMS, envelope encryption, encrypted backups, tested restore drill |
| Decryption regression after upgrade | Runtime errors or silent garbage on read | Round-trip test suite in CI against production-shaped data; staged rollout |
| Query on encrypted field | Broken search or errors in production | Pre-merge query audit; automated detection of queries against encrypted columns |
| Mis-typed property skipped by encryption | Silent plaintext leakage — worst case, invisible | Assert-on-write that expected fields are non-plaintext; spot audits; test matrix covering nullable/union/embedded properties |
| Blind-index drift (key change without reindex) | Searches silently return no results | Tie index keys to key version; reindex on rotation |
| Migration half-complete | Mixed plaintext/ciphertext rows | Idempotent backfill + completeness check before dropping plaintext |
The silent-plaintext-leakage row is the one to design around explicitly. It produces no error, no alert, and no user complaint — only a compliance finding months later.
Ramp-up. Allocate roughly two to three weeks for a competent engineer to become productive, split across CipherSweet concepts (blind indexes, key providers, ciphertext format), Doctrine event-subscriber behavior and its flush-ordering subtleties, and the key-management runbook. The encryption model is the easy part; the operational discipline around keys and migration is what takes the time. Pair this with a written runbook covering key rotation, restore-from-backup, and the "a field is unexpectedly plaintext" incident response — and rehearse the restore at least once before go-live.
How can I help you explore Laravel packages today?