Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b3bc1c0
FOUR-32465: [Octane] CRITICAL Data Leaks Between Requests "$uid2id"
rodriquelca Jul 30, 2026
214ac42
feat: refactor redirect handling by introducing RedirectToEventService
devmiguelangel Jul 31, 2026
53c44b2
Merge remote-tracking branch 'origin/feature/FOUR-30918' into feature…
rodriquelca Jul 31, 2026
7f405b4
test: add unit tests for BpmnAction and RedirectToEventService functi…
devmiguelangel Jul 31, 2026
05d250f
Merge remote-tracking branch 'origin/feature/FOUR-32353' into feature…
rodriquelca Jul 31, 2026
2ff685c
feat(FOUR-32473): [Octane] CRITICAL Data Leaks Between Requests "$red…
rodriquelca Jul 31, 2026
f0ba618
FOUR-32474 [Octane] CRITICAL Data Leaks Between Requests "$landlordVa…
gproly Jul 31, 2026
6775f5e
FOUR-32475 [Octane] CRITICAL Data Leaks Between Requests "AnonymousUs…
gproly Jul 31, 2026
a32cdc6
Merge remote-tracking branch 'origin/feature/FOUR-30918' into feature…
rodriquelca Jul 31, 2026
99c4147
Merge remote-tracking branch 'origin/feature/FOUR-30918' into feature…
rodriquelca Jul 31, 2026
3c628d3
Merge remote-tracking branch 'origin/feature/FOUR-30918' into FOUR-32466
devmiguelangel Jul 31, 2026
9d0d012
feat: refactor ResetRequestState to use RedirectToEventService for ha…
devmiguelangel Jul 31, 2026
51a4444
test: update ResetRequestStateTest to utilize app() for dependency re…
devmiguelangel Jul 31, 2026
0c58e63
test: enhance ResetRequestStateTest with query duration tracking and …
devmiguelangel Jul 31, 2026
ee03183
test: add query duration tracking and isolate metrics in ServerTiming…
devmiguelangel Jul 31, 2026
fde5410
FOUR-32498 [Octane] MEDIUM — Accumulating State "addons"
gproly Jul 31, 2026
27fdaec
Merge pull request #8951 from ProcessMaker/feature/FOUR-32465
pmPaulis Jul 31, 2026
e628337
Merge pull request #8956 from ProcessMaker/feature/FOUR-32473
pmPaulis Jul 31, 2026
560b457
Merge remote-tracking branch 'origin/feature/FOUR-32464' into FOUR-32466
devmiguelangel Jul 31, 2026
3392225
test: enhance HandleRedirectListenerTest to utilize RedirectToEventSe…
devmiguelangel Jul 31, 2026
6d4fc34
Merge pull request #8954 from ProcessMaker/FOUR-32466
pmPaulis Aug 3, 2026
b40ef22
Merge pull request #8955 from ProcessMaker/feature/FOUR-32474
pmPaulis Aug 3, 2026
64060eb
Merge pull request #8957 from ProcessMaker/feature/FOUR-32475
pmPaulis Aug 3, 2026
78e5b1b
Merge pull request #8961 from ProcessMaker/feature/FOUR-32498
pmPaulis Aug 3, 2026
5f0fa0f
Merge branches 'feature/FOUR-32464' and 'FOUR-32496' of github.com:Pr…
devmiguelangel Aug 3, 2026
743dde4
feat: update SettingObserver to use instance property for artisan cac…
devmiguelangel Aug 3, 2026
c6fee3d
test: add unit tests to validate callback scheduling and cache invali…
devmiguelangel Aug 3, 2026
96fbfd3
FOUR-32507 [Octane] MEDIUM — Accumulating State "ServerTimingMiddleware"
gproly Aug 3, 2026
3d9f5b1
FOUR-32504 [Octane] MEDIUM — Accumulating State "RetryProcessRequest"
gproly Aug 3, 2026
1f00e5d
Merge pull request #8960 from ProcessMaker/FOUR-32496
pmPaulis Aug 3, 2026
562dcc4
Merge pull request #8965 from ProcessMaker/FOUR-32505
pmPaulis Aug 3, 2026
3752453
Merge branch 'feature/FOUR-32464' into feature/FOUR-32507
gproly Aug 3, 2026
c583b0f
feat: implement WorkerBootTimingService for improved boot timing trac…
devmiguelangel Aug 3, 2026
9b798c5
test: add middleware request timing validation tests
devmiguelangel Aug 3, 2026
46afd1b
FOUR-32502 [Octane] MEDIUM — Accumulating State "Manifest"
gproly Aug 3, 2026
bcfcbcf
Merge pull request #8966 from ProcessMaker/feature/FOUR-32507
pmPaulis Aug 3, 2026
03554c4
Merge remote-tracking branch 'origin/feature/FOUR-32464' into FOUR-32501
devmiguelangel Aug 4, 2026
6b6cb67
Merge remote-tracking branch 'origin/feature/FOUR-30918' into feature…
devmiguelangel Aug 4, 2026
1605e9d
Merge branch 'feature/FOUR-32464' of github.com:ProcessMaker/processm…
devmiguelangel Aug 4, 2026
c38f4df
Merge remote-tracking branch 'origin/feature/FOUR-32464' into FOUR-32501
devmiguelangel Aug 4, 2026
2dba74d
Merge pull request #8967 from ProcessMaker/feature/FOUR-32504
pmPaulis Aug 4, 2026
d1478e3
Merge pull request #8970 from ProcessMaker/feature/FOUR-32502
pmPaulis Aug 4, 2026
b7ad1b6
Merge pull request #8968 from ProcessMaker/FOUR-32501
pmPaulis Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ProcessMaker/Console/Commands/UnblockRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function handle(): int

$retryRequest->retry();

foreach ($retryRequest::$output as $line) {
foreach ($retryRequest->getOutput() as $line) {
$this->info($line);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ public function retry(ProcessRequest $request, Request $httpRequest): JsonRespon
$retryRequest->retry();

return response()->json([
'message' => $retryRequest::$output,
'message' => $retryRequest->getOutput(),
'success' => true,
]);
} catch (Throwable $throwable) {
Expand Down
16 changes: 7 additions & 9 deletions ProcessMaker/Http/Middleware/ServerTimingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,19 @@
use Closure;
use Illuminate\Http\Request;
use ProcessMaker\Providers\ProcessMakerServiceProvider;
use ProcessMaker\Services\WorkerBootTimingService;
use Symfony\Component\HttpFoundation\Response;

class ServerTimingMiddleware
{
// Minimum time in ms to include a package in the Server-Timing header
private static $minPackageTime;

public function __construct()
public function __construct(private WorkerBootTimingService $workerBootTimingService)
{
self::$minPackageTime = config('app.server_timing.min_package_time');
}

/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
Expand All @@ -39,7 +36,7 @@ public function handle(Request $request, Closure $next): Response
// Calculate execution times
$controllerTime = (microtime(true) - $startController) * 1000; // Convert to ms
// Fetch service provider boot time
$serviceProviderTime = ProcessMakerServiceProvider::getBootTime() ?? 0;
$serviceProviderTime = $this->workerBootTimingService->getProviderBootTime() ?? 0;
// Fetch query time
$queryTime = ProcessMakerServiceProvider::getQueryTime() ?? 0;

Expand All @@ -55,13 +52,14 @@ public function handle(Request $request, Closure $next): Response
array_unshift($serverTiming, "boot;dur={$bootTiming}");
}

$packageTimes = ProcessMakerServiceProvider::getPackageBootTiming();
$packageTimes = $this->workerBootTimingService->getPackageBootTiming();
$minPackageTime = config('app.server_timing.min_package_time');

foreach ($packageTimes as $package => $timing) {
$time = ($timing['end'] - $timing['start']) * 1000;

// Only include packages that took more than MIN_PACKAGE_TIME ms
if ($time > self::$minPackageTime) {
if ($time > $minPackageTime) {
$serverTiming[] = "{$package};dur={$time}";
}
}
Expand Down
6 changes: 6 additions & 0 deletions ProcessMaker/ImportExport/Manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ class Manifest

private static $logger = null;

public static function resetRequestState(): void
{
self::$parents = null;
self::$logger = null;
}

public function has(string $uuid)
{
return array_key_exists($uuid, $this->manifest);
Expand Down
6 changes: 4 additions & 2 deletions ProcessMaker/Jobs/BpmnAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
use Illuminate\Support\Facades\Log;
use ProcessMaker\BpmnEngine;
use ProcessMaker\Exception\HttpABTestingException;
use ProcessMaker\Listeners\HandleRedirectListener;
use ProcessMaker\Models\Process as Definitions;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestLock;
use ProcessMaker\Services\RedirectToEventService;
use Throwable;

abstract class BpmnAction implements ShouldQueue
Expand Down Expand Up @@ -60,6 +60,7 @@ abstract class BpmnAction implements ShouldQueue
public function handle()
{
$response = null;
$redirectToEventService = app(RedirectToEventService::class);
try {
extract($this->loadContext());
$this->engine = $engine;
Expand All @@ -74,7 +75,7 @@ public function handle()
// (e.g. completed, assigned, process completed, etc)
// excluding system process (non_persistent_process)
if ($this->processId !== 'non_persistent_process') {
HandleRedirectListener::sendRedirectToEvent();
$redirectToEventService->sendRedirectToEvent();
}
} catch (HttpABTestingException $exception) {
Log::error($exception->getMessage());
Expand All @@ -87,6 +88,7 @@ public function handle()
$request->logError($exception, $element);
}
} finally {
$redirectToEventService->reset();
$this->unlock();
}

Expand Down
46 changes: 7 additions & 39 deletions ProcessMaker/Listeners/HandleRedirectListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,19 @@

namespace ProcessMaker\Listeners;

use ProcessMaker\Events\RedirectToEvent;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Services\RedirectToEventService;

class HandleRedirectListener
{
private static $processRequest = null;

protected static $redirectionMethod = '';

private static $redirectionParams = [];

protected function setRedirectTo(ProcessRequest $processRequest, string $method, ...$params): void
{
self::$processRequest = $processRequest;
self::$redirectionMethod = $method;
self::$redirectionParams = $params;
public function __construct(
private ?RedirectToEventService $redirectToEventService = null
) {
}

/**
* Reset the static state for Octane compatibility.
* This prevents data leaks between requests in long-running workers.
*/
public static function reset(): void
{
self::$processRequest = null;
self::$redirectionMethod = '';
self::$redirectionParams = [];
}

public static function sendRedirectToEvent()
protected function setRedirectTo(ProcessRequest $processRequest, string $method, ...$params): void
{
$method = self::$redirectionMethod;
$params = self::$redirectionParams;
$processRequest = self::$processRequest;

// Only get active tokens if there is a valid process request
if ($processRequest !== null) {
$params['activeTokens'] = ProcessRequest::getActiveTokens($processRequest);
$event = new RedirectToEvent($processRequest, $method, $params);
event($event);

// Clean params to prevent sending the same redirect multiple times
self::$redirectionParams = [];
self::$redirectionMethod = '';
self::$processRequest = null;
}
$this->redirectToEventService ??= app(RedirectToEventService::class);
$this->redirectToEventService->setRedirectTo($processRequest, $method, ...$params);
}
}
45 changes: 45 additions & 0 deletions ProcessMaker/Managers/ControllerAddonsRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace ProcessMaker\Managers;

class ControllerAddonsRegistry
{
private $addons = [];

/**
* Register a controller addon.
*/
public function register(string $scope, array $config): void
{
$config['scope'] = $scope;
$this->addons[] = $config;
}

/**
* Get configured addons for a controller.
*/
public function getAddons(string $scope, string $method, array $data): array
{
$addons = [];

foreach ($this->addons as $addon) {
if ($addon['method'] !== $method || $addon['scope'] !== $scope) {
continue;
}

if (isset($addon['data']) && is_callable($addon['data'])) {
$data = call_user_func($addon['data'], $data);
}

$addon['content'] = isset($addon['view']) && !isset($addon['content'])
? view($addon['view'], $data)->render()
: (isset($addon['content']) ? $addon['content'] : '');
$addon['script'] = isset($addon['script']) && is_string($addon['script'])
? view($addon['script'], $data)->render()
: '';
$addons[] = $addon;
}

return $addons;
}
}
6 changes: 6 additions & 0 deletions ProcessMaker/Models/AnonymousUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ class AnonymousUser extends User

protected $table = 'users';

public static function resolve(): self
{
return static::where('username', '=', static::ANONYMOUS_USERNAME)
->firstOrFail();
}

public $isAnonymous = true;

public function receivesBroadcastNotificationsOn($notification)
Expand Down
10 changes: 5 additions & 5 deletions ProcessMaker/Multitenancy/SwitchTenant.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Support\Arr;
use Illuminate\Support\Env;
use Illuminate\Support\Facades\Context;
use Monolog\Handler\RotatingFileHandler;
use ProcessMaker\Application;
use ProcessMaker\Multitenancy\Broadcasting\TenantAwareBroadcastManager;
Expand All @@ -17,7 +18,7 @@ class SwitchTenant implements SwitchTenantTask
{
use UsesMultitenancyConfig;

public static $landlordValues = null;
private const LANDLORD_VALUES_CONTEXT_KEY = 'multitenancy.landlord_values';

/**
* Make the given tenant current.
Expand All @@ -31,9 +32,8 @@ public function makeCurrent(IsTenant $tenant): void

\Log::debug('SwitchTenant: ' . $tenant->id, ['domain' => request()->getHost()]);

// Save the landlord values for later use
if (!self::$landlordValues) {
self::$landlordValues = $app->make('config')->all();
if (!Context::has(self::LANDLORD_VALUES_CONTEXT_KEY)) {
Context::add(self::LANDLORD_VALUES_CONTEXT_KEY, $app->make('config')->all());
}

// Set the tenant's domain in the request headers. Used for things like the global url() helper.
Expand Down Expand Up @@ -70,7 +70,7 @@ public function forgetCurrent(): void

private function landlordConfig($key)
{
return Arr::get(self::$landlordValues, $key);
return Arr::get(Context::get(self::LANDLORD_VALUES_CONTEXT_KEY), $key);
}

private function setConfig($key, $value)
Expand Down
10 changes: 5 additions & 5 deletions ProcessMaker/Nayra/Repositories/EntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

abstract class EntityRepository
{
private static $uid2id = ['requests' =>[], 'tokens' =>[]];
private $uid2id = ['requests' =>[], 'tokens' =>[]];

abstract public function create(array $transaction): ? Model;

Expand Down Expand Up @@ -41,16 +41,16 @@ public function resolveId(string $uid): int
}

// Get record if is not stored previously
if (!isset(self::$uid2id[$type][$uid])) {
if (!isset($this->uid2id[$type][$uid])) {
$record = $instance->select('id')->where('uuid', $uid)->first();
if ($record) {
self::$uid2id[$type][$uid] = $record->getKey();
$this->uid2id[$type][$uid] = $record->getKey();
} else {
throw new Exception("The uid {$uid} does not exist in the database");
}
}

return self::$uid2id[$type][$uid] ?? 0;
return $this->uid2id[$type][$uid] ?? 0;
}

/**
Expand All @@ -71,6 +71,6 @@ public function storeUid(string $uid, int $id): void
break;
}

self::$uid2id[$type][$uid] = $id;
$this->uid2id[$type][$uid] = $id;
}
}
6 changes: 3 additions & 3 deletions ProcessMaker/Observers/SettingObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class SettingObserver
{
private static $added_refresh_artisan_caches = false;
private bool $addedRefreshArtisanCaches = false;

/**
* Handle the setting "created" event.
Expand Down Expand Up @@ -95,7 +95,7 @@ private function invalidateSettingCache(Setting $setting)

// Check to see if we already added the refresh to the app's terminating queue.
// This is important for install commands when multiple settings are being created/updated.
if (self::$added_refresh_artisan_caches) {
if ($this->addedRefreshArtisanCaches) {
return;
}

Expand All @@ -106,6 +106,6 @@ private function invalidateSettingCache(Setting $setting)
RefreshArtisanCaches::dispatchSync();
});

self::$added_refresh_artisan_caches = true;
$this->addedRefreshArtisanCaches = true;
}
}
11 changes: 9 additions & 2 deletions ProcessMaker/Octane/ResetRequestState.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@

namespace ProcessMaker\Octane;

use ProcessMaker\Listeners\HandleRedirectListener;
use ProcessMaker\ImportExport\Manifest;
use ProcessMaker\Providers\ProcessMakerServiceProvider;
use ProcessMaker\Services\RedirectToEventService;

final class ResetRequestState
{
public function __construct(
private readonly RedirectToEventService $redirectToEventService
) {
}

public function handle(): void
{
ProcessMakerServiceProvider::beginRequestTiming();
HandleRedirectListener::reset();
Manifest::resetRequestState();
$this->redirectToEventService->reset();
}
}
Loading
Loading