Skip to content

1.0.1

Choose a tag to compare

@tdrabikdev tdrabikdev released this 15 Jul 13:21
8a106bf

Security release. Fixes the findings from an OWASP audit of the codebase.

1227 tests pass (25 added — every fix has a regression test, because these hid in plain sight). composer audit --locked and npm audit --omit=dev both report zero.

Versioning note. This continues the v1 line started at v1.0.0. The release before that was v13.0.0, whose tag tracked the Laravel major, and Composer still orders 13.0.0 above this tag — so composer create-project resolves v13.0.0 unless you pin 1.0.1 explicitly. The v1 tag now points here.

🔴 Critical

Any registered customer could ship goods at the merchant's expense. The dropshipping API group had auth:sanctum and no role check — the only API controller without one. DropshippingService::placeOrder makes a live call to the supplier with the merchant's API key and a caller-supplied payload, and registration is open. Register → mint a token → POST /api/dropshipping/place-order with any items and address → real goods ship, billed to the merchant. No order record, no payment, no limit. track-order was additionally an unscoped IDOR over any supplier reference. Now staff-only on all four endpoints.

🟠 High

  • Plaintext passwords written to logs. Failed registration stripped password but not password_confirmation — the same value. The common trigger is a duplicate email, where the password is perfectly valid. The QueryException path leaked the bcrypt hash twice, because formatMessage() interpolates bindings into the message.
  • Webhook signing secrets returned on every response. No $hidden, so listing endpoints returned every whsec_ secret — the keys needed to forge deliveries that consumers validate as authentic. The code claimed "returned once"; it wasn't.
  • No security headers at all — no CSP, HSTS, X-Frame-Options, nosniff or Referrer-Policy. The admin panels were framable. CSP ships as Report-Only so it can't break Filament/Livewire.
  • /checkout/process was public and unthrottled, reaching payment capture with a caller-supplied card token: unlimited card testing.
  • The installer's key check failed open. INSTALLER_ENABLED=true without a key exposed shell execution, .env rewrite and arbitrary-role user creation to anyone who found the URL. Now fails closed.

🟡 Medium

Guest API rate limiting had collapsed into a single shared bucket — no proxies were trusted, so every client looked like the ingress and one attacker could throttle everyone. Wildcard CORS on api/* is now env-driven. The session cookie now gets Secure outside local/testing. Authentication failures and lockouts are now logged (allowlisting the email out of the credentials array, which holds the password). SSRF targets rejected on webhook URLs.

Known issues, not fixed here

  • Public registration is broken, and that bug is the only thing masking an access-control hole. CreateNewUser assigns a staff role no seeder creates, so signup rolls back. Behind it, every registrant would join the store's primary team, canAccessPanel returns true for /app, and Article/ProductCollection have no policies (Filament allows by default). These must be fixed together — repairing registration alone opens the store's tenant to every signup.
  • SVG stored XSS is latent. Product image upload accepts image/svg+xml onto a public disk. Currently unreachable (no public/storage symlink) but live the moment storage:link runs.
  • Password::default() is min 8 with no complexity or breach check.
  • The Dependency Security Audit workflow is red and has been since before this work — it runs composer audit with no composer install and no --locked. One flag fixes it.
  • /user/profile renders only because social login now defaults off; enabling a provider re-breaks it (// use HasConnectedAccounts; is commented out in User).

Full changelog: v1.0.0...v1.0.1