vimatech/laravel-secure-fields
Laravel package that adds transparent encryption for sensitive database fields. Protect PII, tokens, and secrets with automatic encrypt/decrypt through Eloquent casts, no schema changes or query rewrites, plus configurable keys, key rotation, and searchable blind indexes.
Choosing which data to protect, and admitting the tradeoff in writing. This package is only worth adopting if a roadmap item exists that says "PII at rest, but we still need to look records up." The moment you encrypt a field, you lose LIKE, ORDER BY, range queries, aggregates, joins, and reporting on that column — permanently, by design. Encrypted fields are queryable only by exact match against a companion hash column. That is a product decision, not an implementation detail: it constrains admin search, customer support tooling, fraud rules, and every BI dashboard built on that table. Decide which is acceptable before an engineer picks the package.
Feature scoping: "lookup-required PII" is the sweet spot. Best-fit fields are identifiers you must match exactly — users.phone, accounts.external_ref, national ID, email when used for login lookup. Worst-fit: anything the business filters, sorts, or reports on (amounts, dates, names in a directory), and anything needing prefix or fuzzy search. If a stakeholder asks for "encrypt everything," that request needs to be decomposed into exact-match fields (doable here) and analytical fields (needs a separate controlled store).
Roadmap sequencing that is not optional. Adoption implies four workstreams that must be scheduled, not discovered mid-sprint:
TEXT and adding indexed *_hash columns (with UNIQUE where the business rule requires uniqueness — a unique index on the encrypted column will never fire).Build vs buy vs native. Laravel ships encrypted casts that handle transparent encryption with no extra dependency. This package's added value is narrow and specific: dedicated keys separate from APP_KEY, a separate blind-index hash key, and a rotation command with unreadable-row reporting. If you only need "encrypted at rest, never searched," the framework already does it and you should not take the dependency. If you need searchable encryption and rotation, that's the gap this fills — at the cost of a single-maintainer, zero-star, zero-dependent dependency on a security-critical surface.
Risk acceptance as an explicit decision record. A security-critical package with zero stars and zero dependents should be pinned to an exact version, wrapped behind one internal adapter class, and reviewed diff-by-diff. A patch release (v2.0.1) changed only punctuation in exception messages and two lines of rotate output — behaviour, classes, and exit codes untouched — and still broke any health check grepping the text. That is the shape of the tail risk. Product should treat this as a bounded bet with a designed exit, not a settled architectural choice.
Compliance commitments you can now make — and ones you can't. You can credibly claim encryption at rest with dedicated, independently managed keys for named fields. You cannot claim fields are unreadable to application operators (values decrypt transparently into model attributes and therefore flow into toArray(), toJson(), API resources, queue payloads, log context, and exception traces by default). Masking in API resources, $hidden defaults, and ID-only job payloads are product requirements, not engineering preferences — without them, the encryption buys you database-dump protection and very little else.
Adopt when most of these are true:
TEXT, SQLite in CI)..env.example as empty strings, config:clear after env changes.Look elsewhere — or use native encrypted casts — when:
encrypted cast covers it with zero third-party surface.->update(), DB::table(), seeders, importers, upsert()) silently write plaintext past the casts; firstOrCreate/updateOrCreate on a secure attribute never matches and silently creates duplicates. Every one of these is a data-integrity incident that surfaces months later.A reasonable middle path: pilot on one field where you need both halves of the behaviour — encrypt at rest and exact-match search — and treat it as a bounded experiment with an adapter in place from day one. If the reporting constraints or the maintenance burden prove unacceptable, migrating to native casts is a one-file change.
To executives — one paragraph. We can encrypt specific sensitive customer fields in the database while keeping the app's code and normal lookups unchanged. The cost is that those fields stop supporting search-by-partial, sorting, and reporting, and we take on a small third-party dependency plus two extra secrets to manage. It protects against database dumps and backup leaks; it does not make data invisible to the application itself, so masking in APIs and discipline about logs and queues are required. Recommended scope: a short list of exact-match identifiers, piloted on one field, with an internal wrapper so we can swap it out.
To engineering — the operating rules. Encryption is a model-layer concern; everything below the model is raw bytes. Declare fields one way only (property or cast, never mixed). Never call firstOrCreate/updateOrCreate on a secure attribute — put that in the PR template. Route all hashing through one SecureSearch adapter so the hash key never appears in a controller. Assert three things in every test touching a secure field: plaintext on the model, ciphertext in the table, correct hash in the hash column — plus one negative test per endpoint that plaintext does not appear in the JSON body. Catch SecureFieldsException by class and assert exitCode(), never message text. Pin the exact version, read every patch diff, and run the package's suite against our Laravel/PHP/DB matrix before committing regulated data.
To security and audit — what we are and are not claiming. Dedicated encryption key and separate blind-index hash key, both in the secret manager, neither derived from APP_KEY; AES-256-GCM; versioned keys with a rehearsed rotation runbook and a non-zero "unreadable values" count treated as an incident signal. Known, accepted limitations: equality leakage on hashed fields, ciphertext visible to raw queries, replicas, and BI tools, and plaintext present in application memory by design. Document those limits explicitly rather than letting the word "encrypted" imply more than it delivers.
How can I help you explore Laravel packages today?