Skip to content

chore(deps): upgrade to Laravel 12 - #3956

Merged
marcelfolaron merged 3 commits into
masterfrom
chore/laravel-12
Oct 20, 2026
Merged

marcelfolaron merged 3 commits into
masterfrom
chore/laravel-12

Conversation

@marcelfolaron

Copy link
Copy Markdown
Collaborator

Phase 0 of the MCP protocol upgrade: Laravel 12 first, as its own PR, then laravel/mcp 1.0. Target: 3.13.

Why

  • Every laravel/framework 11.x release carries four advisories with no 11.x fix: debug page XSS, signed URL path confusion, and two CRLF issues in the email rule. All are fixed in 12.69+.
  • laravel/mcp 1.0 (MCP 2026-07-28) needs illuminate/json-schema, which needs framework ≥ 11.47.
  • The lock moves 11.45.1 → 12.70.0. It also clears 26 more advisories (guzzle, psr7, commonmark, flysystem, http-foundation). 7 advisories remain; they were already on master and come from unrelated packages (jmespath, phpseclib, php-jwt, symfony/yaml, phpcs). The audit job is continue-on-error.

Commits

  1. fix(modules): require ProjectModules in every service that enforces module switches.
    • PermissionService, Tickets, Timesheets, Search, Ideas, Goalcanvas and ReportEngine took ?ProjectModules $projectModules = null and skipped the module gate when it was null.
    • L12's container injects the default instead of resolving an unbound class. ProjectModules is a bound singleton, so the upgrade alone wouldn't have nulled it (verified). The gate still shouldn't fail open: the dependency is now required.
    • New ModuleGateWiringTest, plus Unit\TestCase::allModulesOn() for tests that build these services by hand.
  2. chore(deps): upgrade to Laravel 12.
    • Checked every framework method Leantime overrides against its L12 parent.
    • Drop our Console\Application::addCommand() override: L12 ships the same setLaravel() fix.
    • LoadConfig registers config_loaded_from_cache like the parent does.
    • Language::get() declares its string return (L12's trans() is return-typed).
    • Test fixes:
      • Query grammars take their connection in the constructor now.
      • Symfony 7.4.18+ counts the documentation ranges (203.0.113.0/24…) as PRIVATE_SUBNETS, so the "public client can't spoof X-Forwarded-For" test now uses a routable IP.
  3. refactor: PHPStan findings from L12's sharper types. 21 new on L12, 0 on L11.
    • 15× update(...) >= 0 was always true.
    • A dead setTimezone() on an immutable (formatDateTimeForDb() already converts to UTC).
    • Str:: helpers on a Stringable.
    • Wiki row iteration.
    • route() is documented as non-null but can be null before routing.

Behaviour note for reviewers

With the default LEAN_TRUSTED_PROXIES (PRIVATE_SUBNETS), Symfony's widened list now also trusts peers in 100.64.0.0/10 (CGNAT, e.g. Tailscale) and the documentation/benchmark ranges to set X-Forwarded-For. Public addresses are unaffected. Set LEAN_TRUSTED_PROXIES explicitly if that matters for a deployment.

Verification

  • Unit: 2010 tests green on L12. The L11 baseline of the same commit was also green.
  • Plugin unit tests (plugins main): identical on L11 and L12. One pre-existing PostHog EventMapTest failure that is unrelated.
  • PHPStan: clean. Pint: clean on the touched files.
  • Smoke test on a worktree site (copy of the dev DB):
    • Login; dashboard; kanban/list/ticket detail/roadmap; timesheets; calendar; project pages; users; company settings; My Apps.
    • Widget and subtask HTMX fragments.
    • Module switches: pages redirect to "not available", and JSON-RPC answers -32001 on the switched-off project while the same call works on another.
    • /mcp unauthenticated → 401.
    • bin/leantime (--version, list, plugin:list, db:migrate --help).
    • App log empty.
  • Not run locally: the acceptance suite. It runs in CI on this PR.

Companion plugins PR: Copilot ExecutionController had two nullable memory services that are unbound. On L12 both came out null (verified), so it needs to land with this.

🤖 Generated with Claude Code

marcelfolaron and others added 3 commits October 20, 2026 12:48
…odule switches

PermissionService, Tickets, Timesheets, Search, Ideas, Goalcanvas and ReportEngine took
`?ProjectModules $projectModules = null` and skipped the module gate when it was null. A
missing dependency meant module switches silently stopped being enforced (UI, JSON-RPC and
MCP alike).

Laravel 12's container injects a nullable parameter's default instead of resolving the class
when the class has no binding. ProjectModules is a bound singleton, so the upgrade alone would
not have nulled it, but the gate should not depend on that: the dependency is now required and
the null branches are gone, so a missing gate is a construction error instead of a silent allow.

ModuleGateWiringTest resolves each service from the container and asserts it got the real
ProjectModules; tests that build the services by hand pass an all-on instance
(Unit\TestCase::allModulesOn()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
laravel/framework ^12.69 (12.70.0). Every 11.x release carries four advisories with no 11.x
fix (debug page XSS, signed URL path confusion, CRLF in the email rule); the upgrade also
clears the guzzle, commonmark, flysystem and http-foundation advisories in the lock. It
unblocks laravel/mcp 1.0 (needs illuminate/json-schema, framework >= 11.47).

Framework layer, checked against the L12 parents of what we override:
- Console\Application: drop the addCommand() override. Laravel 12's own addCommand() calls
  setLaravel(), which is what the symfony/console 7.4 workaround did.
- LoadConfig: register config_loaded_from_cache for both paths, as LoadConfiguration does.
- Language::get() declares its string return. L12's trans() is return-typed, and a mocked
  Language returned null from the untyped method.

Tests:
- Query grammars take their connection in the constructor now.
- Symfony 7.4.18+ counts the documentation ranges (203.0.113.0/24 etc.) as PRIVATE_SUBNETS, so
  the "public client can't spoof X-Forwarded-For" test uses a routable address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- update() is typed int<0, max>, so `update(...) >= 0` was always true: run the update and
  return true (the result only ever meant "didn't throw").
- Timesheets: drop a discarded setTimezone() on an immutable; formatDateTimeForDb() already
  converts to UTC.
- Wiki: iterate the row with get_object_vars().
- Api::resolveStaticAsset: use the Stringable's own contains()/startsWith(); Str:: helpers are
  now typed to return false for non-string haystacks.
- CheckPermissions: read the route through the resolver. route() is documented as never null
  but is null until a route matched, so the instanceof guard stays meaningful.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@marcelfolaron
marcelfolaron requested a review from a team as a code owner October 20, 2026 16:59
@marcelfolaron
marcelfolaron requested review from broskees and a balanced review from Copilot and removed request for a team October 20, 2026 16:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The framework-wide upgrade needs human confirmation of acceptance CI results and coordinated validation of the companion plugin changes.

No open issues 🎉

What changed in this PR

Upgrades Leantime to Laravel 12.70 for security fixes and prepares the framework for the subsequent MCP upgrade targeting 3.13.

Changes:

  • Updates framework dependencies and adapts core integrations to Laravel 12.
  • Makes project-module enforcement dependencies mandatory and adds wiring coverage.
  • Simplifies code flagged by sharper types and updates affected test fixtures.
File Description
tests/​Unit/​TestCase.php Adds an all-modules-enabled test helper.
tests/​Unit/​app/​Domain/​Timesheets/​Services/​TimesheetsServiceTest.php Supplies required module switches.
tests/​Unit/​app/​Domain/​Tickets/​Services/​TicketsServiceTest.php Updates service construction.
tests/​Unit/​app/​Domain/​Tickets/​Services/​TicketsApiContractTest.php Updates API test dependencies.
tests/​Unit/​app/​Domain/​Tickets/​Services/​TicketChangeEventsTest.php Updates event test dependencies.
tests/​Unit/​app/​Domain/​Tickets/​Repositories/​StatusTypeFilterTest.php Adapts query grammar construction.
tests/​Unit/​app/​Domain/​Tickets/​Repositories/​StatusSummaryQueryTest.php Adapts query grammar construction.
tests/​Unit/​app/​Domain/​Tickets/​Repositories/​SearchSortTest.php Adapts query grammar construction.
tests/​Unit/​app/​Domain/​Reports/​Services/​ReportEngineTest.php Supplies required module switches.
tests/​Unit/​app/​Domain/​Ideas/​Services/​IdeasServiceTest.php Supplies required module switches.
tests/​Unit/​app/​Domain/​Goalcanvas/​Services/​GoalProgressUpdatedTest.php Updates goal-event test dependencies.
tests/​Unit/​app/​Domain/​Goalcanvas/​Services/​GoalcanvasServiceTest.php Updates goal-service test dependencies.
tests/​Unit/​app/​Core/​Modules/​ModuleGateWiringTest.php Verifies container injection of module switches.
tests/​Unit/​app/​Core/​Middleware/​RequestRateLimiterTest.php Uses a routable public-IP fixture.
tests/​Unit/​app/​Core/​Auth/​Permissions/​PermissionServiceViewAllTest.php Supplies required module switches.
tests/​Unit/​app/​Core/​Auth/​Permissions/​PermissionServiceModulesTest.php Removes optional-module dependency coverage.
tests/​Unit/​app/​Core/​Auth/​Permissions/​PermissionServiceCacheTest.php Supplies required module switches.
composer.lock Locks Laravel 12.70 and updated dependencies.
composer.json Requires Laravel 12.69 or newer within 12.x.
CLAUDE.md Updates the documented framework version.
app/​Domain/​Wiki/​Repositories/​Wiki.php Adjusts row iteration and simplifies update returns.
app/​Domain/​Timesheets/​Services/​Timesheets.php Requires module switches and simplifies date formatting.
app/​Domain/​Tickets/​Services/​Tickets.php Requires module switches.
app/​Domain/​Sprints/​Repositories/​Sprints.php Simplifies update success handling.
app/​Domain/​Search/​Services/​Search.php Removes optional module-gate bypasses.
app/​Domain/​Reports/​Services/​ReportEngine.php Requires module filtering and preloading.
app/​Domain/​Projects/​Repositories/​Projects.php Simplifies update success handling.
app/​Domain/​Plugins/​Repositories/​Plugins.php Simplifies update success handling.
app/​Domain/​Notifications/​Repositories/​Notifications.php Simplifies update success handling.
app/​Domain/​Ideas/​Services/​Ideas.php Requires module filtering.
app/​Domain/​Ideas/​Repositories/​Ideas.php Simplifies update success handling.
app/​Domain/​Goalcanvas/​Services/​Goalcanvas.php Requires module checks and filtering.
app/​Domain/​Comments/​Repositories/​Comments.php Simplifies update success handling.
app/​Domain/​Clients/​Repositories/​Clients.php Simplifies update success handling.
app/​Domain/​Auth/​Repositories/​Auth.php Simplifies update success handling.
app/​Domain/​Api/​Services/​Api.php Uses instance methods on Stringable paths.
app/​Core/​Language.php Declares and documents the string return type.
app/​Core/​Console/​Application.php Removes the command-registration override supplied upstream.
app/​Core/​Bootstrap/​LoadConfig.php Registers configuration-cache loading state.
app/​Core/​Auth/​Permissions/​PermissionService.php Makes module-gate injection mandatory.
app/​Core/​Auth/​Permissions/​CheckPermissions.php Resolves routes while retaining nullable handling.

Review effort: Balanced

@marcelfolaron
marcelfolaron merged commit a6309fe into master Oct 20, 2026
13 checks passed
@marcelfolaron
marcelfolaron deleted the chore/laravel-12 branch October 20, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants