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

Symfony Password Exposed Bundle Laravel Package

divineomega/symfony-password-exposed-bundle

Symfony bundle that checks whether a password has appeared in a data breach, using the haveibeenpwned.com API with configurable PSR-18 client, PSR-6 cache, and PSR-17 factories. Supports PHP 7.4+ and Symfony 4.4 through 8.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Verdict up front: low-confidence dependency. This is a Symfony bundle, not a framework-agnostic PHP library, so for a Laravel-primary estate it is not directly installable — the value must be re-derived from its underlying core library (jord-jd/password_exposed) or reimplemented. Signal quality is weak (1 star, 0 dependents, single maintainer, README-only docs), and the actual business logic is small enough that dependency risk likely exceeds build cost.

Architecture fit

  • The bundle is a thin DI/config/HTTP-client wrapper around the HIBP Pwned Passwords range API (k-anonymity: SHA-1 prefix lookup). It ships a PasswordExposedCheckerInterface, a Symfony PasswordExposed validation constraint, and YAML config for enable, http_client, cache, cache_lifetime, and PSR-7 request_factory/uri_factory.
  • Conceptually it fits any auth surface — registration, password reset, password change, forced rotation, admin user provisioning — as a pre-hash gate: check the plaintext before hashing/storage. That placement matters: it must sit before persistence, not as an audit pass afterwards.
  • Framework fit is the blocker. The extension points (cache.app, Symfony HttpClient, ContainerBuilder) map to Symfony services and Symfony config; Laravel exposes a different container, Cache facade, and Http client. In a Laravel codebase the bundle's wiring layer is dead weight.
  • The constraint class is a Symfony Validator Constraint; Laravel's validator uses Rule objects. Any reuse means an adapter, at which point you own more glue than logic.

Integration feasibility

  • Non-starter directly: the bundle requires Symfony flex/config/HttpClient. Pulling Symfony components into a Laravel app to satisfy a 40-line feature is a poor cost/benefit trade.
  • Viable path: depend on the framework-agnostic jord-jd/password_exposed core (or similar) and expose it through a Laravel service provider + Rule + config file. Effort estimate: <1 day for a senior PHP engineer, including tests.
  • Equally viable path: reimplement in-house. The algorithm is SHA-1 the plaintext, take the first 5 hex chars, GET https://api.pwnedpasswords.com/range/{prefix}, scan the response body for the remaining 35 chars, cache the prefix response. This is a bounded, well-specified problem — roughly 60–120 lines plus tests — and it removes third-party version, license, and maintainer risk entirely.
  • Privacy posture is acceptable by design: only a 5-character hash prefix leaves the perimeter; no plaintext, no full hash. That is the strongest argument in the package's favour and it should be stated explicitly in any design doc or DPIA.

Technical risk

Risk Severity Notes
Framework mismatch (Symfony bundle in Laravel estate) High Bundle is unusable as-is; forces core-lib adoption or reimplementation
Maintainer/bus factor — single author, 1 star, 0 dependents High No community backstop; abandonment is plausible
LGPL-3.0 Medium Generally workable for SaaS (no distribution, no modification), but requires legal sign-off; not a permissive-license default
Release cadence looks automated (v1.0.0 "automated migration release", v1.0.1 docs-only rename) Medium Indicates low active investment, not active hardening
External API dependency (HIBP) Medium Latency + availability directly on the registration path
Rate limits / ToS interpretation Medium Prior analysis cites 2,000 checks/day; the Pwned Passwords range endpoint is widely used without a key and with generous limits — verify current HIBP terms before committing, and design caching as if the limit is tight regardless
Stale cache → false negatives Medium Default cache_lifetime of 30 days means newly breached passwords pass the gate
Release date metadata (2026-07-18) not reconcilable with observation Low Treat provenance/registry metadata as unverified; pin versions by hash
Docs coverage Medium README only; no stated test suite, no supported-version matrix, no deprecation policy

Key questions

  1. What is our target framework for this feature — is anything Symfony in the estate, or is this purely a Laravel/PHP decision?
  2. Do we accept a third-party dependency for ~100 lines of deterministic logic, or do we own it in-house?
  3. Has legal reviewed LGPL-3.0 obligations for our distribution model (SaaS vs. on-prem shipped binaries)?
  4. What is the expected volume of checks per day, and does caching reduce external calls below any documented threshold?
  5. What is the product policy on API failure — fail-open (allow registration, log) or fail-closed (block)? This is a product/risk decision, not an engineering one, and it drives the entire resilience design.
  6. Do we need the check to be synchronous on the request path, or can it be asynchronous with a "your password appeared in a breach — update it" nudge?
  7. Who owns this integration long-term, and what is the removal cost if the dependency goes stale?

Integration Approach

Stack fit

Concern Bundle (Symfony) Laravel target Recommendation
HTTP Symfony HttpClient Illuminate\Support\Facades\Http (Guzzle) Use Laravel's client; bypass bundle config
Cache cache.app (PSR-6/PSR-16) Laravel Cache (PSR-16 compatible) Use Cache::remember() with prefix-keyed entries
Config YAML bundle tree config/*.php + .env Expose enable, cache_ttl, fail_open as config
DI Symfony container/autowiring Service provider binding Bind an interface to our adapter
Validation Symfony Constraint Illuminate\Validation\Rule Implement a custom PasswordNotExposed rule
PSR-7 factories Configurable Not needed Laravel/Guzzle handles request construction

Migration / adoption path

  1. Do not install the bundle in the Laravel app. Require jord-jd/password_exposed only if it proves framework-agnostic under test; otherwise implement in-house.
  2. Wrap in a single seam: PasswordExposureChecker interface with one method (isExposed(string $password): bool) and one concrete implementation. Everything else — controller, form request, rule, listener — depends on the interface only. This keeps the dependency swappable in one file.
  3. Register a validation rule (PasswordNotExposed) and attach it to registration, reset, and change-password requests. Prefer a soft block (validation error with guidance) over a hard rejection for the first release.
  4. Cache aggressively: store the 5-char prefix → full response body for cache_lifetime (suggest 30 days for the prefix set, and treat it as a cache-warm optimisation rather than correctness). Key with a versioned prefix so a TTL change is a config deploy, not a cache flush.
  5. Compatibility: confirm the chosen library or in-house code supports our PHP version and Guzzle/HTTP client version; verify it is not pinned to Symfony contracts we do not have.
  6. Sequencing: (a) legal/license sign-off on LGPL-3.0; (b) spike behind a feature flag in staging against real HIBP; (c) measure p50/p95 latency and cache hit rate; (d) ship behind flag for 5–10% of registration traffic; (e) ramp to 100%; (f) revisit fail-open/closed policy with security.
  7. Rollback: the feature flag must disable the check without a deploy — registration must never be blocked by our ability to reach a third party.

Operational Impact

Maintenance

  • Owning a fork/wrapper means we own the upgrade path. With one maintainer and near-zero dependents, expect to be the de facto maintainer if the project stalls; budget ~0.5 engineer-day per year to track HIBP API changes, plus a re-evaluation at each major PHP/Laravel upgrade.
  • Pin the dependency to an exact version with a committed composer.lock, and record the decision in an ADR so a future team knows why a 1-star package appears in composer.json — or, better, why we implemented it ourselves.

Support

  • There is no vendor, SLA, or commercial support. Escalation path is "an engineer reads the source." That is acceptable for 100 lines of hashing logic and unacceptable for anything touching auth correctness more deeply.
  • Document the failure policy and the user-facing copy in the runbook; support teams will field "why was my password rejected?" tickets and need consistent language.

Scaling

  • Cost is dominated by HIBP calls, not compute. Prefix caching collapses the call volume dramatically — a modest user base converges on a small set of hot SHA-1 prefixes. Expect cache hit rates well above 90% at volume.
  • Latency: an uncached check adds ~100–300 ms to a registration request. That is tolerable for sign-up and unacceptable as a synchronous check on every login. Restrict to password creation/change events.
  • No bulk/backfill path. If someone asks to "sweep all existing users," that is a separate offline job with strict rate pacing and is out of scope for this package.

Failure modes

  • HIBP unreachable / slow: with fail-open, the feature silently degrades to a no-op — acceptable, but it must be observable (metric + alert on error rate, not on absence of errors).
  • HIBP returns unexpected format: parser throws; treat as "unknown" and apply the fail policy, not as "not exposed."
  • Cache serving stale data: newly breached passwords pass for up to the TTL. Product must accept this; document it rather than hiding it.
  • Regex/substring bug in suffix matching: a naive str_contains on the response body can produce false positives across line-delimited records. This is the most likely correctness defect in a hand-rolled implementation — cover it with fixture-based tests.
  • Password material in logs: the single highest-severity risk. Ensure the plaintext never reaches exception context, request logging, or queue payloads. Add a test that asserts it.

Ramp-up

  • Low: a mid-level Laravel engineer can implement, test, and ship this in one to two days including the feature flag and metrics.
  • Required context: HIBP range API semantics, k-anonymity rationale (so the privacy story is defensible in review), Laravel HTTP client retry/timeout configuration, and Laravel cache key/prefix conventions.
  • Recommended artefacts: one ADR (build vs. adopt + license), one short runbook (failure policy, feature flag, metric names), and a fixture-based test suite covering exposed, not-exposed, malformed-response, and timeout cases.

Bottom line for the roadmap: the capability is worth shipping — breach-correlated password rejection is cheap, high-signal, and defensible under GDPR/NIST framing. The package is not the right delivery vehicle for a Laravel estate. Recommend adopting the capability via a thin in-house or core-library-backed adapter behind a flag, treating this bundle as a reference implementation and a design precedent rather than a runtime dependency.

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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk