jgxvx/cilician-bundle
Cilician Bundle integrates the Cilician PHP library with Symfony, providing bundle registration, dependency-injection wiring, and configuration so Cilician services can be installed and used in Symfony applications with minimal setup.
First, confront the framework mismatch. This is a Symfony bundle (Bundle class + Symfony DI service definitions), not a Laravel package. In a Laravel app you cannot composer require it and have it boot. "Getting started" therefore means one of two things:
config/bundles.php (or rely on auto-discovery if the version supports it), then look first at the DependencyInjection/ extension and the client class — that is where the API key header, base URL, and User-Agent are wired. Publish/override config only for the API key and timeout.Where to look first, in order:
breachedAccount, pasteAccount, breaches, breach/{name}, dataClasses, latestBreach).404 is treated as "not breached" rather than an error, and whether 429 backoff exists.First use case — the one to ship first: the Pwned Passwords range API (k-anonymity SHA-1, first 5 hash chars only). It is free, needs no paid key, sends no PII, and blocks breached passwords at signup/reset. That is the lowest-risk, highest-visibility win and it defers every privacy-review question.
Minimal Laravel equivalent to stand up instead:
// config/services.php
'hibp' => [
'key' => env('HIBP_KEY'),
'user_agent' => env('HIBP_USER_AGENT', 'MyApp/1.0'),
'base_url' => 'https://haveibeenpwned.com/api/v3',
'timeout' => 5,
],
Bind a BreachCheck interface in a service provider, back it with Http::withHeaders([...])->timeout(...), and keep controllers unaware of HIBP.
1. Wrap, never call directly. Define an internal interface (PasswordBreachCheck, EmailBreachLookup) and bind an implementation in a service provider. Whether you vendored the bundle's client or wrote your own, the auth path depends on your interface. This is the whole return on reading the package: the request semantics, not the object graph.
2. Password check at the boundary — always fail-open.
public function isPwned(string $password): bool
{
$sha1 = strtoupper(sha1($password));
$prefix = substr($sha1, 0, 5);
$suffix = substr($sha1, 5);
$body = Cache::remember("hibp:range:$prefix", now()->addDay(), fn () =>
Http::timeout(2)
->withHeaders(['User-Agent' => config('services.hibp.user_agent')])
->get("https://api.pwnedpasswords.com/range/$prefix")
->throw()
->body()
);
foreach (explode("\n", $body) as $line) {
[$hash, $count] = explode(':', trim($line));
if ($hash === $suffix) {
return true; // optionally return (int) $count for telemetry
}
}
return false;
}
Call it in a FormRequest rule or a dedicated BlockBreachedPasswords action at signup and reset. If the call times out or errors, let the signup through — an HIBP outage must never block registration or login.
3. Email lookup — queue it, never inline. The breached-account endpoint requires a paid key, sends user email addresses to a third party, and is rate-limited (~1.5 req/sec). Put it in a job:
final class CheckEmailForBreaches implements ShouldQueue
{
public function handle(BreachLookup $hibp, EventDispatcher $events): void
{
$breaches = $hibp->breachedAccount($this->email);
if ($breaches !== []) {
$events->dispatch(new EmailBreachDetected($this->user, $breaches));
}
}
}
Consumers of EmailBreachDetected: forced password reset, session revocation, MFA step-up, risk scoring.
4. Cache the catalogue, rate-limit the lookups. Breach metadata (breaches, breach/{name}, dataClasses) is near-static — cache for hours. Wrap lookups in a Laravel RateLimiter keyed to HIBP's limit so a burst of queued jobs doesn't trip 429. Caching also shrinks the blast radius of an upstream outage.
5. Fake it in tests and CI. Bind a fake BreachLookup implementation in the test container. Keep recorded HIBP fixtures for contract tests so no API key is needed in the pipeline and behaviour on 404/401/429 is asserted, not assumed.
6. Emit domain events, not UI side effects. The service answers "is this breached?"; queues, notifications, and auth policy react to the event. That keeps the HIBP client swappable and the fail-open policy in one place.
Framework fit is the first gotcha. Do not attempt to install this into Laravel. Symfony's container does not consume Laravel's, and pulling Symfony components into an auth path for a thin REST wrapper buys nothing. Read it, reimplement it.
404 means "not breached", not "error." The single most common bug in HIBP integrations is treating a 404 from breachedAccount as a failure — which either throws on every clean account or, worse, silently marks every user as breached. Handle it explicitly. Conversely, 401 (missing/invalid key) and 429 (rate limit) are real errors and should surface to your monitoring.
The User-Agent header is mandatory. HIBP rejects requests without it. Make it a config constant, not an afterthought — and never derive it from user input.
The API key is a paid requirement for everything except passwords. Budget for it (~$3.95/month), know it is billed to the account, and respect the terms: no bulk enumeration, no reselling. The free range API is the only exception.
Never call HIBP synchronously on a hot path. Signup, login, and reset are latency- and availability-sensitive. Short timeouts, a circuit breaker, and a fail-open default are non-negotiable. If you check passwords at volume, self-host the Pwned Passwords range dataset or front it with a long-TTL cache — the dataset is free and immutable enough.
PII in logs is a real leak. The breached-account endpoint sends email addresses; ensure neither the package (if you vendor it) nor your client logs request bodies, emails, or key material on error. Audit the error paths, not just the happy path.
Supply-chain posture. Zero stars, zero dependents, single maintainer, Codeberg-hosted, no visible CI or advisory channel, and no stated licence. For anything touching credentials:
Extension points are thin on purpose. There is no meaningful algorithm, schema, or storage layer to inherit — the value is endpoint/header/status knowledge. That makes it easy to replace and hard to justify depending on.
Config quirks: defaults tend to be override-friendly, so publish/override only the key, timeout, and base URL. Keep the key in .env, out of version control, and remember that a framework config cache means a changed key needs a cache clear.
Scope discipline: ship the password check first (free, hashed-only, no DPA/DPIA work). Treat email-based lookup and any domain-wide monitoring as a separate roadmap decision with a recurring vendor cost and a privacy review attached — not a config flag.
How can I help you explore Laravel packages today?