Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Encrypt Bundle Laravel Package

nowo-tech/doctrine-encrypt-bundle

A Symfony bundle for transparent field-level encryption in Doctrine entities. Protect sensitive database columns with encrypted DBAL types, PHP attributes, and configurable key management—ideal for GDPR compliance and secure application data.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Verdict up front: this is a thin, credible wiring layer over well-known crypto libraries, not a novel security product. Adopt only as a commodity convenience for encrypting a small number of non-queryable columns on a Symfony + Doctrine stack — and only after a short spike and a maintainer conversation. Do not treat it as a platform capability.

Architecture fit

  • The mechanism is Doctrine lifecycle interception (encrypt on preFlush/onFlush, decrypt on postLoad). That fits Symfony/Doctrine applications where entities are the only write path, but it is structurally incompatible with native SQL, DB views, reporting extracts, BI tools and any ETL that writes directly to the table.
  • The bundle is Symfony-only (bundle + DI extension + compiler pass semantics). The brief frames this as "Laravel/PHP"; if the target application is Laravel/Eloquent, this is a non-fit and the evaluation should stop. If the fleet is mixed, expect two different encryption approaches to be maintained in parallel — a consistency and audit problem.
  • Ciphertext-on-read means the plaintext exists in PHP memory and in the ORM identity map / second-level cache. This is an acceptable threat model against disk/backup theft and over-broad DB credentials, and largely ineffective against a compromised application host. That distinction must be stated to security and legal up front.

Integration feasibility

  • Low engineering effort: composer require, one config block, annotate/attribute the target fields, set key material in the environment. Realistically a 1–2 day spike to running, 2–3 weeks to production-grade with migration, rotation rehearsal and tests.
  • The hard part is not the bundle, it is the data model surgery: encrypted values are variable-length and non-deterministic (unique constraints, LIKE, ORDER BY, range filters, joins on the field, and GROUP BY all stop working). Every existing query touching the field must be found and redesigned or dropped.
  • Escape hatches are real: DQL bulk UPDATE/DELETE, iterate()/scalar hydration, entity getReference proxies, and fixture loaders can bypass or break the listener. Audit these before committing.

Technical risk

  • Adoption risk (highest): 6 stars, 0 dependents, small maintainer surface. There is no evidence of an external security review, a disclosure policy, or a long support horizon. The last release date is future-dated relative to normal release cadence, which itself warrants a check on repo/tag integrity.
  • Dependency risk: Halite and Defuse are both mature but low-activity; libsodium (Halite) is the safer primitive. MysqlAes should be treated as a convenience for legacy/DB-side parity only — key material lives with the database, default block mode choices are weak, and it weakens the "app holds the key" separation that makes the whole approach worth doing.
  • Rotation risk: "multiple keys, key rotation" is the single most important claim and the least verifiable from the description. Rotation without a documented re-encrypt command, mixed-key read support, and a rollback plan is a latent outage.
  • Compliance risk: field-level encryption supports GDPR Art. 32 ("appropriate technical measures") but is not pseudonymisation under Art. 4(5) unless keys are held separately from data, and it does not discharge access control, retention or DPA obligations. Expect to correct over-optimistic expectations from stakeholders.

Key questions to put to the maintainer (and to our own security reviewer)

  1. Is encryption deterministic? If not, is there a blind-index helper for equality lookups, or is that explicitly out of scope?
  2. What exactly does rotation do — read-time fallback, bin/console bulk re-encrypt, or per-record lazy rewrap? Is it resumable and idempotent?
  3. What is the key-version storage format on the row? Can we migrate keys without a schema change?
  4. Supported matrix: Doctrine ORM 2.x vs 3.x, DBAL 3 vs 4, PHP 8.1→8.4, Symfony 6.4 LTS → 8. Is there CI proving it?
  5. Behaviour with embedded value objects, JSON/ArrayType columns, entity inheritance, and second-level cache?
  6. What happens on a missing/rotated key at read time — exception, null, or silent passthrough? Fail-closed semantics must be confirmed.
  7. Security posture: disclosure contact, CVE process, SBOM, dependency update policy, and who owns this repo in 12 months?
  8. Are there published performance numbers for the three ciphers at, say, 100k rows?

Integration Approach

Stack fit

  • Good fit: Symfony 6.4/7.x/8 application, Doctrine ORM as the sole write path, PHP 8.1+, existing secrets management (env vars via Vault/SOPS/KMS-injected), and a small set of PII columns with no search requirements (e.g. national ID, IBAN, health notes, free-text case notes).
  • Poor fit: Laravel/Eloquent; read-heavy analytic schemas; anything needing equality search, sorting or uniqueness on the sensitive value without a blind index; multi-service databases where non-PHP consumers read the same tables.

Migration path

  1. Spike (3–5 days, one engineer + security reviewer): stand up the bundle in a scratch branch against a copy of a real entity, measure query impact, and empirically answer questions 1, 2 and 6 above. Output: a one-page go/no-go memo.
  2. Wrap before you wire: define an internal FieldEncryptor interface and adapt the bundle behind it, so the vendor choice is reversible. This is the single highest-value TPM decision here — it caps the blast radius if the project is abandoned.
  3. Pilot one entity, 1–2 columns, in a non-production environment first, with the encryption key sourced from the same secret store used in production (never a committed default).
  4. Widen column width (plan 2–4× current length for base64 + nonce/tag overhead) in a separate, reversible migration before the encrypting release.
  5. Backfill via a resumable batch command with progress logging and a dry-run mode; run under dual-write/dual-read or an off-peak window.
  6. Only then consider a second entity. Treat each additional field as a mini-project, not a config line.

Compatibility

  • Pin the bundle, Doctrine ORM/DBAL and Symfony minor versions explicitly in composer.json; the low dependents count means version-combination bugs will not have been found by anyone else.
  • Keep a compatibility smoke test in CI (round-trip encrypt/decrypt on each supported cipher) so a transitive Doctrine upgrade cannot silently break hydration.

Sequencing (dependency order) security sign-off on threat model → vendor spike + maintainer Q&A → internal FieldEncryptor abstraction → schema width migration → pilot entity in staging → backfill tooling + rotation rehearsal → production pilot → review gate → expand.

Operational Impact

Maintenance

  • Ongoing cost is low in code terms (a few config lines) but real in process: dependency watch on the bundle, libsodium/Halite and Doctrine ORM; periodic re-verification that the listener still fires correctly after ORM upgrades. Budget roughly a few days per year plus the cost of each new field.
  • The bus-factor is effectively 1–2 people (maintainer + our internal adopter). Ensure at least two engineers on our side can explain and operate it.

Support and ownership

  • There is no commercial support and no evident community mass. Escalation path is "fork it or replace it." That is acceptable for a thin adapter, unacceptable for anything touching regulated data without an internal owner of record.
  • Assign a named internal owner (application security or platform) for key custody and rotation — this is our obligation regardless of the package's quality.

Scaling

  • Crypto cost is trivial: AES-GCM/libsodium run at hundreds of MB/s; at realistic field sizes (KBs) the overhead is noise next to query time. The real scaling limits are (a) rotation/backfill job duration and lock contention on large tables, and (b) the DB-side MysqlAes option, which pushes decrypt work into the database and should be avoided at volume.
  • Column bloat matters for large tables: a 4× width increase on a 100M-row table is a genuine storage and buffer-pool conversation.

Failure modes to instrument

  • Missing/invalid key at read → decryption exception surfacing as 500s. Confirm fail-closed behaviour and add a startup health check that decrypts a known probe value.
  • Key rotation mid-run → mixed-version records. Require dual-key read support and a resumable re-encrypt command; never rotate without a rehearsal on a production-shaped copy.
  • Bypass paths (native SQL, bulk DQL, fixtures, CDC/ETL) writing plaintext into encrypted columns → silent data corruption. Add a DB constraint or a nightly integrity job that detects non-ciphertext-shaped values in the protected columns.
  • Key loss = total data loss on those columns. Keys must be in escrow, backed up separately from the database, and included in the DR runbook and restore drill. This is the single largest operational risk and the one most often skipped.
  • Log/telemetry leakage: ensure decrypted values never land in query logs, error trackers or session dumps; note that second-level cache stores plaintext.

Ramp-up

  • One engineer for the spike; ~1–2 engineers for a half-sprint for the pilot; add a security reviewer for the threat model and a DBA for the width migration and backfill. Team education is small but must cover: what this protects against, what it does not, and the rule that sensitive fields are never used in WHERE/ORDER BY without a blind index.
  • Ramp-up blocker if the team is Laravel-based: none of this transfers, and the effort should be redirected to a Laravel-appropriate solution (custom Eloquent cast + KMS envelope encryption).

Recommendation: conditional adopt — pilot on one low-volume, non-queryable field behind an internal abstraction, with an explicit exit criterion (if the maintainer cannot answer the rotation and fail-closed questions satisfactorily within the spike, build the equivalent in-house; it is roughly a one-week job over libsodium) and with key custody, escrow and rotation rehearsal treated as gates, not follow-ups.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource