Skip to content

Upgrade to Laravel 13, bump to v2.8.0, and remediate vulnerabilities#1574

Merged
KodeStar merged 8 commits into
2.xfrom
chore/laravel-13-upgrade
Jul 9, 2026
Merged

Upgrade to Laravel 13, bump to v2.8.0, and remediate vulnerabilities#1574
KodeStar merged 8 commits into
2.xfrom
chore/laravel-13-upgrade

Conversation

@KodeStar

@KodeStar KodeStar commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

Upgrades Heimdall from Laravel 11 → 13, bumps the app version to 2.8.0, and remediates the reported security vulnerabilities. Also adds upgrade-regression tests and fixes a latent bug found along the way.

Please don't merge yet — leaving this for review. In particular, the npm residual below has a follow-up decision for you.

Commits

  1. Upgrade to Laravel 12laravel/framework ^11 → ^12 (12.63.0), PHPUnit 11, Carbon 3, graham-campbell/github ^12 → ^13; phpunit.xml migrated to the 11.5 schema. No application code changes.
  2. Upgrade to Laravel 13laravel/framework ^12 → ^13 (13.19.0), laravel/tinker ^3, PHPUnit 12; Symfony components move to 8.x. No application code changes — the fluent validateCsrfTokens(except: …) config still resolves (it now proxies the renamed PreventRequestForgery middleware).
  3. Remediate npm build-toolchain vulnerabilities via overrides — see below.
  4. Add upgrade-regression test coverage — see below.
  5. Fix appload() 404 coercion bug — see below.
  6. Bump version to 2.8.0.

Security remediation

Composer — fully clean ✅

composer audit went from 13 advisories across 7 packages (laravel/framework ×3, symfony/cache·mailer·routing·yaml ×7, league/commonmark ×2, psy/psysh ×1) to “No security vulnerability advisories found.” — resolved entirely by landing on Laravel 13.19.0 + tinker 3 + the patched Symfony / commonmark / psysh transitive versions the upgrade pulls in.

npm — 41 → 9, every critical & high eliminated ⚠️ (a decision for you)

npm audit: 41 (1 critical, 13 high, 18 moderate, 9 low) → 9 (0 critical, 0 high, 3 moderate, 6 low), via a package.json "overrides" block that force-patches vulnerable transitive build deps. No laravel-mix change — both npm run production and npm run dev still compile (identical output).

The 9 residuals are all dev / build-time only (never in the production runtime — runtime deps are just select2 + sortablejs), and each is irreducible without replacing the EOL laravel-mix@6 toolchain:

Residual Severity Why it can't be cleared
elliptic (+ browserify-sign, create-ecdh, crypto-browserify, node-libs-browser) 5× low Latest published elliptic is still flagged; no upstream patch exists. Node crypto polyfills, never bundled.
ajv 6.x moderate Under babel-loader; forcing ajv 8 breaks the build (schema-utils 2.x uses the ajv-6 API).
webpack-dev-server + laravel-mix moderate Patched wds needs v5 API; laravel-mix 6 hard-depends on v4. Only affects npm run hot, not the production build.
webpack low laravel-mix 6 is incompatible with the patched webpack (>5.104).

To reach literal zero, the asset pipeline needs migrating off laravel-mix to Vite. I deliberately did not do that blind — it's an architectural change to the frontend that warrants visual QA of the dashboard. Say the word and I'll do the Vite migration as a follow-up commit on this branch.

Also included

  • Regression tests (7 files, 30 tests) covering the surfaces upgrades tend to break: app/Helper.php globals (format_bytes, parse_size, className, get_brightness, title_color), the CSRF exception config (asserts the order/appload/test_config exceptions actually reach PreventRequestForgery and the routes resolve), core-route boot/render, and the filesystem-disk pins (guards the Laravel 12 storage/app/private default-root change). Full suite: 89 tests green (1 pre-existing skip).
  • appload() bug fixItemController::appload() was typed : ?string, so its two return response()->json([...], 404) branches had the JsonResponse coerced through Response::__toString() into a raw HTTP message served as an HTTP 200 body. Widened the return type to \Illuminate\Http\JsonResponse|string|null so those branches emit a real 404 JSON. The method body is unchanged, so the happy path returns the identical payload and the frontend contract is preserved.

Heads-up / behavior change to confirm

  • Avatar upload validation: Laravel 12 changed the image validation rule to reject SVGs by default. UserController uses 'file' => 'image' for avatars, and those avatars are stored unsanitized (unlike the icon path, which runs svg-sanitize). I kept the new secure default — SVG avatars are now rejected — rather than re-open that stored-XSS vector. If you'd rather keep SVG avatars, change it to 'file' => 'image:allow_svg'.
  • phpcs: 16 pre-existing errors / 10 warnings across 8 app files remain (identical to baseline — not introduced here). Left untouched to keep this PR focused; happy to clean them up separately.

Verification

  • php artisan --versionLaravel Framework 13.19.0 (boots; serves HTTP 200 end-to-end through the full middleware stack).
  • ./vendor/bin/phpunit89 tests, 210 assertions, 1 skipped — all green.
  • composer auditno advisories. npm audit9 (dev-only).
  • npm run production and npm run dev both compile successfully.

Environment note: the local Composer binary was an ancient 2.3.9 that can't resolve modern graphs; all dependency work used a current Composer 2.10.2. Nothing about that is committed.

CI

Green ✅ (Run tests passing). One extra commit — “Fix committed PHPUnit vendor path casing for case-sensitive CI” — was needed: PHPUnit 12 renamed PHPTPhpt, and because this repo commits vendor/ and macOS is case-insensitive, git kept the old-case paths while Composer wrote the new case to disk. That only fails on CI's case-sensitive Linux filesystem (PHPUnit's require couldn't find the file), so it passed locally but broke php artisan test in CI until the 8 files were re-tracked under the correct case. Worth keeping in mind for future dependency bumps done on macOS.

KodeStar added 6 commits July 8, 2026 19:49
- laravel/framework ^11.45 -> ^12.0 (installed 12.63.0)
- phpunit/phpunit ^10.5 -> ^11.0; migrate phpunit.xml to the 11.5 schema
- graham-campbell/github ^12.5 -> ^13.0 (v12 caps illuminate/support at ^11)
- Carbon 3 pulled in by L12; no application code changes required
- gitignore /.phpunit.cache

Full suite green: 59 tests, 128 assertions (1 skipped).
- laravel/framework ^12.0 -> ^13.0 (installed 13.19.0)
- laravel/tinker ^2.9 -> ^3.0 (installed 3.0.2, pulls psysh >=0.12.19)
- phpunit/phpunit ^11.0 -> ^12.0 (installed 12.5.31)
- Symfony components move to 8.x (supported by L13); symfony/yaml stays on
  patched 7.4.14 via its direct ^7.0 constraint
- league/commonmark auto-bumped to 2.8.2 (patched)
- No application/config code changes required; the fluent
  validateCsrfTokens(except: ...) config still resolves under L13

composer audit: No security vulnerability advisories found.
Full suite green: 59 tests, 128 assertions (1 skipped).
Add a package.json "overrides" block forcing patched versions of
vulnerable transitive build dependencies (shell-quote, ws, node-forge,
serialize-javascript, lodash, minimatch, path-to-regexp, svgo, postcss,
qs, uuid and others). This takes `npm audit` from 41 vulnerabilities
(1 critical, 13 high, 18 moderate, 9 low) down to 9 (0 critical, 0 high,
3 moderate, 6 low).

The 9 residuals are all dev/build-time-only advisories in the EOL
laravel-mix@6 toolchain (the elliptic crypto-polyfill chain with no
upstream patch, ajv 6.x under babel-loader, webpack-dev-server /
laravel-mix, and webpack) that cannot be cleared without replacing
laravel-mix; none ship in Heimdall's production runtime. Both
'npm run production' and 'npm run dev' still compile successfully.
Add tests guarding the surfaces most likely to break on a future
Laravel/PHP upgrade:
- Helper globals: format_bytes, parse_size, className, get_brightness,
  title_color (tests/Unit/helpers)
- CSRF exception config actually reaches the framework
  (PreventRequestForgery neverVerify list) and the excepted routes resolve
- Core GET routes boot and render on the current framework
- Filesystem disks resolve and the local disk root stays pinned to
  storage_path('app') (guards the Laravel 12 default-root change)
ItemController::appload() was declared ': ?string', so its two error
branches that 'return response()->json([...], 404)' had the JsonResponse
coerced through Response::__toString() into a raw HTTP message served as
an HTTP 200 body. Widen the return type to
'\Illuminate\Http\JsonResponse|string|null' so those branches emit
real 404 JSON responses. The method body is unchanged, so the happy path
still returns the same JSON string and the frontend contract is preserved.

Flip the endpoint characterization test to assert the corrected 404.
KodeStar added 2 commits July 8, 2026 20:40
PHPUnit 12 renamed PHPT -> Phpt (src/Framework/Exception/PhptAssertionFailedError.php
and the src/Runner/Phpt/ directory). Because this repo commits vendor/ and the
macOS dev filesystem is case-insensitive, git kept the old-case paths in the index
while Composer wrote the new case to disk, so the change went undetected locally.
On CI's case-sensitive Linux filesystem the checked-out old-case files don't satisfy
PHPUnit's require of the new-case names and 'php artisan test' fatals before running.
Re-track these 8 files under their correct case.
- itemImport: check response.ok in fetchAppDetails so a genuine 404
  (from the appload return-type fix) is reported as 'Failed to find app
  id' instead of being parsed as a successful import; applied to the
  source and the committed compiled bundle.
- phpunit.xml: point the schema URL at 12.5 to match the installed
  PHPUnit 12.5.x.
- ColorHelpersTest: exercise the get_brightness() non-hex stripping the
  test name promised (interior separators), which the prior assertion
  never covered.
@KodeStar
KodeStar merged commit 49c29c8 into 2.x Jul 9, 2026
1 check passed
@LinuxServer-CI LinuxServer-CI moved this from PRs to Done in Issue & PR Tracker Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants