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

Altcha Laravel Package

altcha-org/altcha

ALTCHA for PHP offers a privacy-first, self-hosted CAPTCHA alternative using proof-of-work. Generate and verify challenges server-side, protect forms without cookies or tracking, and integrate easily into Laravel/PHP apps. GDPR-friendly and simple to deploy.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Revision note: updated for v2.3.0 (security fix + breaking changes). Prior assessment assumed optional signature verification.

Architecture fit ALTCHA is a proof-of-work CAPTCHA alternative: the server issues a signed challenge, the browser solves it (hashing until a target is met), and the server verifies an HMAC-signed payload. altcha-org/altcha-lib-php is the server-side half only — challenge creation and payload verification — and is deliberately framework-agnostic, dependency-light PHP. For a Laravel codebase this means it does not "fit" a framework slot out of the box; it sits one layer below where we want it. We would wrap it in a service provider, a config file (HMAC secret, algorithm, maxNumber, expiry), a challenge endpoint/controller, a Rule object or FormRequest extension for verification, and a Blade component hosting the JS widget. That is a routine, well-bounded piece of glue work — roughly a thin internal package. v2.3.0 does not change this shape, but it does make the config file load-bearing: the HMAC secret is now mandatory rather than optional.

Architecturally the attractive property is statelessness: verification is a signature check, no session, no cookie, no third-party network call at request time. That is a genuine fit for a load-balanced Laravel app behind a CDN, and it removes the data-processor relationship we currently carry with reCAPTCHA/hCaptcha. The statelessness claim now depends on the secret being correctly present in every environment — see risk below.

Integration feasibility High, and marginally higher than before. Composer install, PHP 8.x, no extensions, no migrations, no queue, no external service. The only non-PHP dependency is the client widget script, which can be self-hosted. v2.3.0 also fixes canonical-JSON parity with the JS library, which means a PHP backend can now reliably verify challenges minted by a JS-side or edge-side component (and vice versa) — useful if we ever want to issue challenges at the CDN edge, and useful for local development where the widget and server are separate. Friction points remain outside the library: the Web Crypto API requires a secure context (HTTPS everywhere, including internal staging), CSP needs allowances for the widget script and its worker (script-src, worker-src blob:), and challenge replay protection is our responsibility. If we want single-use challenges we must add a store (Redis or cache) keyed on the challenge signature with a TTL — that converts a stateless design into a stateful one and should be a deliberate decision. New in this release: challenge creation can now throw JsonException on invalid UTF-8 in data, so the wrapper must decide whether that surfaces as a 500 or a graceful fallback.

Technical risk

  • v2.3.0 is a security fix with breaking changes; the prior version had a fail-open verification bug. Before this release, if hmacSignatureSecret was unset (null), the signature check was skipped entirely — unsigned or client-tampered challenges (arbitrary keyPrefix, arbitrary cost) verified successfully. Anyone running the library without configuring a secret had no cryptographic protection at all while appearing to be protected. This is the single most important fact in the release and it directly reprices the "thin third-party validation" concern below.
  • Behaviour flips from fail-open to fail-closed. This is the correct direction, but operationally it inverts our failure mode: a missing or empty secret used to mean silent insecurity, and now means 100% verification failure. Both are bad; the new one is at least loud. Our wrapper must assert at boot (config validation / health check) that the secret is a non-empty string in every runtime that verifies solutions — web nodes, CLI, queue workers, any Lambda or cron that touches the validation rule.
  • Ecosystem signal is still weak, and this release explains why it matters. Still ~115 stars and zero recorded dependents. The fail-open bug is precisely the class of defect that broad third-party usage tends to surface early. Treat as "adopt with a pin, our own verification test suite, and a fork plan." We should not rely on upstream tests to prove our security property; we should have an integration test that asserts an unsigned/cost-tampered payload is rejected.
  • Mixed-version deployments will break in-flight challenges. Challenges signed under the old scheme (empty or list-shaped data, floats formatted differently from JS) will fail verification after upgrade, and sub-second expiry removes the previous ~1s grace. A rolling deploy across a fleet will reject users mid-form. Mitigate with a short expiresAt during the change window, a drain, or a brief dual-verify path.
  • expiresAt semantics changed in a footgun-adjacent way. 0 now means no expiry, fractional timestamps are accepted and signed as-is, and expiry is checked to sub-second. Sub-second checking raises the bar on fleet clock accuracy (NTP) — small skew between nodes is no longer absorbed by the old 1s grace.
  • Security model is cost-imposition, not proof of humanity. PoW raises the marginal cost of automated abuse; it does not stop a determined, funded attacker or a human click farm. It is a spam-mitigation control, not an authentication control. Do not position it as equivalent to a risk-scoring CAPTCHA on high-value surfaces (account takeover, payment) without additional signals.
  • Accessibility and client capability. Solving is CPU-bound on the client; low-end mobile devices, throttled tabs, and battery-saver modes can produce slow or failed solves. Any mandatory human-verification gate needs a fallback path.
  • Operational blind spots. Secret rotation, clock skew, and CDN caching of the challenge endpoint remain the common failure modes; the release makes secret misconfiguration strictly more visible but also strictly more fatal.

Key questions

  1. What is the threat profile per surface (newsletter, contact, registration, login, password reset) and is cost-imposition sufficient there?
  2. Do we require single-use challenges? If yes, do we accept a Redis dependency on the verification path?
  3. What is our PoW time budget on the slowest supported device, and what maxNumber satisfies it?
  4. Who owns the HMAC secret, how is it injected into every verifying runtime, and what stops a deploy from shipping with it empty? Where is the boot-time assertion?
  5. Do we pass anything in the challenge data field? If so, does it contain floats or non-UTF-8 input, and are we exposed to the v2.3.0 signature-format change?
  6. What expiry do we set, and is 0 (no expiry) explicitly forbidden by our config validation rather than merely undocumented?
  7. How fast can we consume an upstream security release, and what is the rollback plan if a pin bump breaks verification?
  8. Is there a maintained Laravel adapter we should depend on rather than writing our own, and who owns it?
  9. What are our WCAG obligations, and is there an accessible alternative flow?
  10. Are we willing to self-host the widget JS to remove a third-party runtime dependency from our forms?

Integration Approach

Stack fit Composer-native, PSR-compatible, no framework coupling — it drops into a Laravel service container cleanly. The integration surface is small and well understood: a config/altcha.php, a singleton wrapper around the library's challenge/verify calls, a POST /altcha/challenge route (rate-limited), a validation rule applied to the submitted payload, and a Blade component that renders the widget and its script tag. Nothing touches Eloquent, queues, or the scheduler. Add two things the previous plan didn't need: a config assertion that the secret is non-empty, and an integration test proving a tampered payload is rejected.

Migration path Run in parallel, not as a cutover. Keep the existing CAPTCHA provider behind a config/feature flag with ALTCHA as an alternate driver, and select per-form. Sequence from low-risk to high-risk surfaces: internal/staging forms → newsletter and contact → registration → login and password reset. Instrument solve time, solve failure rate, and submission-to-abuse ratio at each step before widening. A parallel-run period of two to four weeks on the low-risk tier gives us the data to decide whether the high-risk surfaces are appropriate at all.

If ALTCHA is already deployed anywhere on a pre-2.3.0 pin, treat this as an incident-adjacent change: audit first whether a secret was actually configured (if not, we have been running unprotected and should assume the surface was exploitable), then upgrade with a controlled window rather than a routine dependency bump.

Compatibility Verify the library's PHP constraint against our actual runtime and our oldest supported Laravel version; the package is framework-agnostic so Laravel version risk is near zero, but the wrapper we write is not. v2.3.0 raises expiresAt types to int|float|null and changes ChallengeParameters::toArray()['data'] to stdClass for empty/list-shaped data — audit any wrapper code that type-hints or serialises these. Confirm HTTPS enforcement everywhere the widget runs (secure-context requirement) and that CSP allows the widget script and worker. Confirm no sticky-session requirement, and confirm the challenge endpoint is excluded from CDN caching and response-caching middleware. Confirm fleet-wide NTP sync, since sub-second expiry checking removes the old grace window.

Sequencing

  1. Timeboxed spike (1–2 days): generate and verify a challenge end-to-end in a branch at v2.3.0, measure solve time on a throttled mid-tier phone, and assert that an unsigned payload fails.
  2. Build the internal wrapper package: config with mandatory secret + assertion, service binding, validation rule, rate-limited challenge route.
  3. Pin to exact v2.3.0; add a regression test that a payload with a tampered cost/keyPrefix is rejected.
  4. Add the Blade component and self-hosted widget asset; update CSP.
  5. Accessibility and fallback review; define the escape hatch for clients that cannot solve.
  6. Staging soak with synthetic load on the challenge endpoint, including a rolling-restart test to catch in-flight-challenge rejection.
  7. Progressive production rollout behind the flag, low-risk surfaces first.

Operational Impact

Maintenance Low ongoing effort in code volume: small API surface, no database, no background jobs. The real maintenance obligation is the pinned dependency plus the wrapper we own. The v2.3.0 release changes the maintenance posture in two ways: (a) it demonstrates that upstream ships security-relevant fixes, so a quarterly review is too slow for security advisories — subscribe to GitHub security advisories/watch releases and triage within days; (b) it shipped a breaking security fix, so we must be able to test and deploy a pin bump on short notice. Budget a few hours per quarter for routine review, plus the ability to act in hours if another advisory lands. Keep the fork plan current — at 115 stars we cannot contract for upstream cadence.

Support Community-only support via GitHub issues; no SLA on the OSS library. If we need one, it comes from the vendor's commercial product — a different procurement conversation. Internally the runbook now must cover: secret rotation, "all verifications failing" triage (first check: is the secret set and identical across nodes?), distinguishing invalidSignature from expired in logs, widget-load failure, and the manual-override path for locked-out users. Add an alert on the ratio of invalidSignature to expired failures — v2.3.0 makes a missing secret indistinguishable, to the end user, from a bot attack, so the failure reason code is the thing to monitor.

Scaling Server-side cost is negligible — HMAC sign and verify, no external calls, horizontally scalable by default. The challenge endpoint is the only new attack surface: it is unauthenticated and cheap to call, so rate-limit it per IP and set Cache-Control: no-store so it can't become a DoS amplifier or a CDN-cached static response. If we add replay protection, the verification path acquires a Redis round-trip and a new failure mode; size that deliberately rather than by default. Client-side cost is the real scaling constraint: keep maxNumber modest and validate on our lowest-spec supported device.

Failure modes

  • Secret unset or empty → every verification fails. New dominant failure mode in v2.3.0. Previously this failed open (silently insecure); now it fails closed (loudly broken). Assert at boot and alert on invalidSignature rate.
  • Mixed versions during rollout → in-flight challenges rejected. Especially where data was empty, list-shaped, or contained floats. Drain or shorten expiry across the change window.
  • expiresAt: 0 set by accident → no expiry at all. The new "0 means never expires" semantics is a footgun; forbid it in config validation and set an explicit TTL.
  • Clock skew → sub-second expiry rejections. The old ~1s grace is gone; enforce NTP across the fleet.
  • JsonException from invalid UTF-8 in data → challenge creation throws. Only reachable if we pass user-derived data; catch it and fail gracefully rather than 500.
  • Widget script blocked by CSP, ad-blocker, or CDN outage → form unusable. Mitigate by self-hosting and testing CSP in staging.
  • Web Crypto unavailable (insecure context, legacy browser, embedded webviews) → silent solve failure. Detect and surface a fallback.
  • Determined abuse still passing → PoW is a speed bump. Pair with rate limiting and server-side heuristics on high-value surfaces.
  • Accessibility regression → users unable to complete forms. Treat as a release blocker, not a follow-up ticket.

Ramp-up A mid-level Laravel engineer should reach productive integration in one to two days; the protocol (salt, challenge, signature, HMAC verification) is small and well documented upstream. That estimate held before and holds now, with one addition: whoever onboards must read the v2.3.0 changelog, because the library now has a hard prerequisite (a configured secret) whose absence is a silent-insecurity bug in older versions and a hard outage in this one. The larger ramp-up cost remains organisational: security review of the threat model, confirmation of what our current deployment's secret configuration actually is, an accessibility sign-off, and getting the CSP and secret-management changes through the platform team. Plan for the people work, not the code.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route