GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — FOUR-34328: [EPIC] MCP Server by caleeli · Pull Request #9160 · ProcessMaker/processmaker

Conversation

@caleeli

@caleeli caleeli commented Nov 12, 2026 •
edited by pmPaulis
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

MCP Server

Related Tickets & Packages

  • https://processmaker.atlassian.net/browse/FOUR-34328

ci:deploy

ci:nayra:FOUR-34376
ci:screen-builder:FOUR-33916
ci:modeler:feature/FOUR-34380

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
marcoAntonioNina and others added 30 commits July 21, 2026 15:47
@marcoAntonioNina
Add ScriptModuleInterface and update ServiceTaskImplementationInterfa…
ff7077b
…ce and WorkflowManagerInterface

- Introduced ScriptModuleInterface for module exposure in PHP scripts.
- Updated ServiceTaskImplementationInterface to allow an optional timeout parameter in the run method.
- Modified WorkflowManagerInterface to include a timeout parameter in the runServiceImplementation method.
@marcoAntonioNina
Add allow_in_process column to scripts and script_versions tables
437a331
@marcoAntonioNina
Add ScriptRuntime and InProcessPhpRunner for executing PHP scripts in…
65d2cb0
…-process

- Introduced ScriptRuntime facade for managing script execution context and module registration.
- Added InProcessPhpRunner to run PHP scripts directly within the Laravel process.
- Updated ScriptRunner to support in-process execution based on script properties.
- Enhanced WorkflowManager and Script models to accommodate new functionality, including allow_in_process property.
- Implemented bootstrap for in-process execution and improved error handling for script execution.
@marcoAntonioNina
Add ScriptExecutionContext class for managing script execution context
988e676
- Introduced ScriptExecutionContext to encapsulate per-run context for script modules.
- The class includes properties for data, configuration, user, token ID, timeout, script ID, and source.
- This addition supports the in-process execution of PHP scripts by providing necessary context information.
@marcoAntonioNina
Add CoreServiceTask implementation for in-process PHP script execution
2b21390
- Introduced CoreServiceTask class to handle the execution of allowlisted PHP scripts without Docker or the script microservice.
- Implemented validation for script configuration, including script ID and user requirements.
- Added support for both module-based and bare execution modes.
- Enhanced error handling for various execution scenarios, ensuring robust configuration checks.
- Utilized ScriptRuntime and InProcessPhpRunner for executing scripts based on the defined context.
@marcoAntonioNina
Add in-process script preview functionality
d96e67f
- Enhanced ScriptController to support a new `run_in_process` property for script previews, allowing execution via an in-process PHP runner when enabled.
- Updated TestScript job to handle the new `runInProcess` parameter, enabling direct script execution without Docker.
- Introduced `shouldPreviewInProcess` method to determine if in-process execution is applicable based on script properties and request flags.
- Added comprehensive unit tests to validate in-process execution scenarios and ensure proper handling of configuration and execution context.
- Implemented new test cases for CoreServiceTask to verify in-process script execution and error handling for various conditions.
@marcoAntonioNina
Add core service task configuration for in-process PHP execution
b9eaba4
@marcoAntonioNina
Implement allow_in_process feature for PHP scripts in edit view.
b676d65
@julceslauhub
Squashed commit of the following:
a92a2c7
commit b84543b
Author: AugustoLopezProcess <[email protected]>
Date:   Tue Apr 21 08:27:15 2026 -0400

    Adding plugin admin ui

commit ca8a863
Author: Nolan Ehrstrom <[email protected]>
Date:   Mon Mar 9 11:01:46 2026 -0700

    Add controllers and routes for plugin UI

    Copied from feature/plugins-poc-vite-ui (just without the vite/Vue3 integration)

commit 3cc89bc
Merge: 9e9b4aa 5da27ce
Author: Nolan Ehrstrom <[email protected]>
Date:   Mon Mar 9 10:58:27 2026 -0700

    Merge branch 'task/FOUR-28803' into feature/plugins-poc

commit 9e9b4aa
Author: Nolan Ehrstrom <[email protected]>
Date:   Mon Jan 19 12:30:54 2026 -0800

    Add install command and cached routes

commit 4a5df14
Author: Nolan Ehrstrom <[email protected]>
Date:   Wed Dec 10 09:29:20 2025 -0800

    Start on plugins
@julceslauhub
Merge pull request #8983 from ProcessMaker/develop
9f07823
Updating feature/plugins-poc-4 with last changes in develop
@julceslauhub
FOUR-32722 Some adjustments in plugins installation
75c359e
@julceslauhub
FOUR-32722 Some adjustments in plugins installation
34ae7ca
@nolanpro
Merge pull request #8993 from ProcessMaker/FOUR-32722
0ac5c82
FOUR-32722 Some adjustments in plugins installation
@julceslauhub
FOUR-32760 After install/enable/disable a plugin, should be executed …
5cf0368
…the command: php artisan horizon:terminate
@nolanpro
Merge pull request #8999 from ProcessMaker/FOUR-32760
2d599f4
FOUR-32760 After install/enable/disable a plugin, should be executed the command: php artisan horizon:terminate
@henryjonathanquispe
Update with develop
a96f564
@henryjonathanquispe
update blade with vite
2998dab
@caleeli
plugins in jobs
f6b4c56
@henryjonathanquispe
Update remove modal
af00fc7
@henryjonathanquispe
PLUGINS POC 4: Upload from local path, remove directory, remove symnl…
6fc992e
…ink, add load screen-builder-form.js files from plugin
@henryjonathanquispe
PLUGINS POC 4: Upload from local path, remove directory, remove symnl…
b733752
…ink, add load screen-builder-form.js files from plugin
@henryjonathanquispe
PLUGINS POC 4: Upload from local path, remove directory, remove symnl…
2418729
…ink, add load screen-builder-form.js files from plugin
@henryjonathanquispe
PLUGINS POC 4: Upload from local path, remove directory, remove symnl…
5bbed5d
…ink, add load screen-builder-form.js files from plugin
@henryjonathanquispe
PLUGINS POC 4: Update plugin
b9f28ad
@henryjonathanquispe
PLUGINS POC 4: Update plugin, with supervisorctl
1a97475
@henryjonathanquispe
PLUGINS POC 4: Update plugin output
19a2b0c
@henryjonathanquispe
PLUGINS POC 4: Update plugin user and token
79eca53
@caleeli
reload service
93a078b
@caleeli
fix url
823dcb0
@caleeli
remove test folders
7c6ea45
pmPaulis added 2 commits November 11, 2026 18:21
@pmPaulis
update with develop and conflict with scripts/edit.blade.php
b7ca62a
@pmPaulis
fixed conflicts
0b5b819
@cursor

cursor Bot commented Nov 12, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

PR Summary

High Risk
Adds plugin install from remote/local sources with runtime reloads and in-process PHP script execution—security- and ops-sensitive areas alongside a large but mostly tooling-only requirements harness.

Overview
Adds a cross-project manage-requirements agent skill with JSON schemas, Node validators (check:requirements / validate:requirements), Python quality judging (stub acceptance criteria, optional Laya), templates, and unit tests—separate from runtime app code.

Introduces end-to-end plugin management: Artisan commands, queued install/reinstall/uninstall/toggle jobs with PluginLog broadcasting, HTTPS-or-zip API installs, admin Plugins UI, PLUGIN_INSTALL_ALLOW_LOCAL_PATH for CLI-only local paths, tenant public asset publishing, Screen Builder loading of published plugin JS, and a plugin:reload-listen Redis listener that schedules supervisor restarts after plugin changes.

Extends Core Service Task / in-process PHP: ScriptRuntime and ScriptModuleInterface, script preview via run_in_process (and allow_in_process scripts), and an optional timeout on WorkflowManager::runServiceImplementation.

MCP-related additions include FlowGenie config validation/normalization and an McpSkillResource for exposing skill markdown. The rest of the diff is mostly minor PHP/doc/style cleanup.

Reviewed by Cursor Bugbot for commit bba43f1. Bugbot is set up for automated code reviews on this repo. Configure here.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes

@cursor cursor Bot left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Mcp/FlowGenies/FlowGenieCatalog.php
$flowGenie->saveOrFail();

return $this->detail($flowGenie->refresh());
}

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

FlowGenie updates wipe secrets

High Severity

update replaces the entire config JSON with normalize(), which merges the payload onto hardcoded defaults. Partial updates therefore reset tools, MCP servers, and provider_api_key. get/detail also put [redacted] in those secret fields, so a get-then-update writes the placeholder over the real key and OAuth tokens.

Additional Locations (2)
  • ProcessMaker/Mcp/FlowGenies/FlowGenieConfigValidator.php#L99-L127
  • ProcessMaker/Mcp/FlowGenies/FlowGenieCatalog.php#L330-L364
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9214abe. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Mcp/FlowGenies/FlowGenieCatalog.php
['id', 'uuid', 'name', 'description', 'flow_genie_category_id', 'updated_at'],
'page',
$filters['page'] ?? 1,
);

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

FlowGenie list omits config

Medium Severity

list paginates FlowGenies without selecting config, but summary() reads model, provider, output_type, and has_instructions from that attribute. Eloquent leaves omitted columns null, so every list row reports empty model/provider and has_instructions: false.

Additional Locations (1)
  • ProcessMaker/Mcp/FlowGenies/FlowGenieCatalog.php#L296-L311
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9214abe. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Mcp/Processes/ProcessCatalog.php
$query->where('status', $filters['status']);
}

return $query;

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Archived processes cannot be listed

Medium Severity

list always applies notArchived() and still accepts status=ARCHIVED. The two filters contradict, so a request for archived processes returns an empty page even though update can set that status.

Additional Locations (1)
  • ProcessMaker/Mcp/Processes/ProcessCatalog.php#L62-L70
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9214abe. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Mcp/DataConnectors/CollectionCatalog.php
$collection->fill($validated);
if (isset($input['type'])) {
$collection->type = $input['type'];
}

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Collection create skips type validation

Medium Severity

After validating with Collection::rules(), create assigns type from the raw input. Update restricts type to NORMAL or RAG; create does not, so arbitrary values can be stored and later break listing or RAG behavior.

Additional Locations (1)
  • ProcessMaker/Mcp/DataConnectors/CollectionCatalog.php#L273-L276
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9214abe. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread ProcessMaker/Mcp/FlowGenies/FlowGenieCatalog.php
* @param array<string, mixed> $config
* @return array<string, mixed>
*/
private function redactConfig(array $config): array

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: HIGH

Description: get-flow-genie returns the stored FlowGenie config after a fixed denylist. redactConfig masks only top-level provider_api_key and, under mcp_servers[].config.http_config.authentication, the names access_token, client_secret, cached_access_token, and cached_refresh_token (also those same names inside oauth_pkce and oauth_client_credentials). It does not walk the rest of the authentication object, http_config siblings, or header maps. FlowGenie Studio persists two additional MCP auth methods, Access Token/API Key and Custom Headers, whose secret values are not those four names. find() loads any FlowGenie by id for any caller with view-flow_genies and returns that config from get-flow-genie (create and update use the same detail() path).

Impact: A designer with view-flow_genies and an API token can read another FlowGenie's MCP credentials that the new write-only contract says are redacted. Those values are returned in the tool payload, so they also enter the MCP client and model transcript. Top-level provider_api_key and the four OAuth token names are masked; Access Token/API Key material and custom header secrets are not. This is a new read into a destination the PR tried to close. Nested subagent provider_api_key is not part of this finding: subagents are references to other genies plus a handoff description, not an embedded copy of the child config.

Remediation: Stop returning credential material from FlowGenie reads. Omit mcp_servers[].config.http_config.authentication (and any header map) the way DataConnectorCatalog::detail omits credentials, or recursively redact secret-bearing values at any depth: provider_api_key, token, api_key, password, secret, refresh_token, code_verifier, and header values such as Authorization, including Access Token/API Key and Custom Headers. Do not rely on a four-name denylist on one authentication object. Add a test that stores an API key and a custom Authorization header, calls get-flow-genie as a different user who has only view-flow_genies, and asserts those values are absent.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@cursor
cursor Bot requested review from agustinbusso and nolanpro November 12, 2026 13:25
@caleeli
Update processmaker/nayra dependency to dev-FOUR-34376 SubProcesses
349c255
cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes

@cursor cursor Bot left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread composer.json Resolved

Uh oh!

There was an error while loading. Please reload this page.

cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread composer.json Resolved

Uh oh!

There was an error while loading. Please reload this page.

@nolanpro

nolanpro commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

QA server K8S was successfully deployed https://tenant-1.ci-ef28357a3a.engk8s.processmaker.net

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@decisions-sonarqube

decisions-sonarqube Bot commented Nov 12, 2026

Copy link
Copy Markdown

Quality Gate failed Quality Gate failed

Failed conditions
19 New issues
B Security Rating on New Code (required ≥ A)
16 New Maintainability Issues (required ≤ 0)

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE SonarQube for IDE

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

devmiguelangel and others added 4 commits November 12, 2026 11:16
@devmiguelangel
feat: remove deprecated MCP skills and update guidance for connectors…
ac0683c
… and screens
@devmiguelangel
Merge remote-tracking branch 'origin/feature/FOUR-34328' into FOUR-34384
9ce3d93
@pmPaulis
fixing sonar observations
fb61f88
@caleeli
Merge remote-tracking branch 'origin/FOUR-34294' into feature/FOUR-34328
9420bd0
cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes

@cursor cursor Bot left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Managers/PluginManager.php
{
$pluginDir = storage_path('plugins');
$pluginPath = "{$pluginDir}/{$pluginName}";
$pluginDisabledPath = "{$pluginDir}/_{$pluginName}";

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Plugin names allow path traversal

High Severity

Uninstall, toggle, reinstall, and git install join storage/plugins with the raw plugin name or URL basename. A name like .. resolves to the tenant storage directory, so those operations can delete or overwrite the whole storage tree. The API {name} route and extractRepoName do not restrict the value to a single safe path segment.

Additional Locations (2)
  • ProcessMaker/Managers/PluginManager.php#L406-L409
  • ProcessMaker/Managers/PluginManager.php#L490-L498
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9420bd0. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Managers/PluginManager.php
// Remove existing plugin directory if present
if (is_dir($pluginPath)) {
$this->deleteDirectory($pluginPath, $repoName, $userId);
}

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Failed overwrite drops existing plugin

High Severity

Zip and local installs call markNewInstallForRollback while the old plugin directory still exists, so rollback is skipped, then they delete that directory. If copy, validation, or composer install fails afterward, revertFailedInstall does nothing and the previously working plugin is gone.

Additional Locations (1)
  • ProcessMaker/Managers/PluginManager.php#L93-L103
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9420bd0. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Managers/PluginManager.php
} finally {
// Always clean up temp directory and zip file
if (is_dir($tmpExtractDir)) {
$this->deleteDirectory($tmpExtractDir, $repoName, $userId);

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Zip cleanup uses unset name

Medium Severity

The installFromZip finally block always passes $repoName into deleteDirectory, but that variable is assigned only after extractTo, close, and scandir. A failure in those steps leaves $repoName undefined, so Laravel can turn the warning into an exception that hides the original zip error and skip temp cleanup.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9420bd0. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread resources/js/admin/plugins/Plugins.vue
page.value = Math.floor(total / value) + 1 || 1;
}
fetch();
};

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Per-page clamp uses floor plus one

Medium Severity

changePerPage sets the last page with Math.floor(total / value) + 1 instead of Math.ceil. When the total is an exact multiple of the new page size, the list stays on a page past the last one, so the table is empty while the footer still implies extra rows.

Fix in Cursor Fix in Web

Triggered by learned rule: Pagination last-page is ceil; reset modal page state

Reviewed by Cursor Bugbot for commit 9420bd0. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Http/Controllers/AdminController.php Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread ProcessMaker/Events/PluginLog.php Resolved

Uh oh!

There was an error while loading. Please reload this page.

cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread ProcessMaker/Jobs/InstallPlugin.php
public ?string $tag = null,
public ?string $zipPath = null,
public ?string $gitUsername = null,
public ?string $gitToken = null,

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: MEDIUM

Description: POST /api/1.0/plugins/install copies the caller-supplied git_token onto public InstallPlugin::$gitToken. Laravel serializes that property into the queue payload, so the PAT stays in Redis, Horizon job history, and failed_jobs after the clone. The same change only redacts the token from log and exception text.

Impact: A manage-plugins user can submit a GitHub PAT or other git password that is then retained outside the clone. Default queueing is Redis (config/queue.php), Horizon keeps the full payload for completed jobs (60 minutes) and failed jobs (7 days), and failed jobs are written to failed_jobs with the raw serialized command. Horizon's viewHorizon gate and the tenant queue detail API are administrator-only, but Redis, database backups, and those admin views are still a different authority than the installer, and the token remains reusable after the install finishes or fails. The PR already treats this value as a secret: PluginManager strips it from the git remote and redactHttpUserInfoFromString is applied before PluginLog and timeout errors.

Remediation: Do not put gitUsername or gitToken on the queued job. Store the token in a short-lived server-side secret (cache/encrypted row) addressed by an opaque id, delete it as soon as the remote URL is set, and keep InstallPlugin free of the raw credential. If the payload must carry it, implement ShouldBeEncrypted and still remove the secret from the job before it can be written to failed_jobs.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@caleeli
Merge pull request #8922 from ProcessMaker/task/scriptrunner
0c20653
Proof of concept for running a PHP script without Docker
cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes

@cursor cursor Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 5 potential issues.

There are 16 total unresolved issues (including 11 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0c20653. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/ScriptRuntime/ScriptRuntime.php
] + $__instances, EXTR_SKIP);

return include $__scriptPath;
})($scriptPath, $context, $instances);

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

In-process scripts ignore timeout

High Severity

Default modules execution computes a timeout and stores it on ScriptExecutionContext, but ScriptRuntime::run never reads timeout and does not cap memory either. A sleep, infinite loop, or heavy allocation can block a Horizon worker indefinitely or OOM it. Only bare mode actually enforces Process::timeout() and memory_limit.

Additional Locations (2)
  • ProcessMaker/ServiceTaskImplementations/CoreServiceTask.php#L63-L77
  • ProcessMaker/ScriptRunners/ScriptRunner.php#L115-L129
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0c20653. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/ScriptRuntime/ScriptRuntime.php
}
if (is_file($scriptPath)) {
@unlink($scriptPath);
}

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Included scripts can kill workers

Medium Severity

Default modules mode includes user PHP in the Laravel worker. exit/die (common in PHP scripts) terminates the worker immediately, skipping the finally that restores putenv state and deletes the generated API token, and it can leave the BPMN token stuck.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0c20653. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Http/Controllers/Api/ScriptController.php
}

return ['status' => 'success'];
return (bool) $script->allow_in_process;

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Preview bypasses in-process allowlist

High Severity

When Core Service Task is enabled, run_in_process=true on script preview runs caller-supplied PHP in the queue worker without allow_in_process. That skips Docker isolation and runs as run_as_user_id with an API token, so any user who can preview a PHP script can execute arbitrary code in the ProcessMaker process.

Additional Locations (1)
  • ProcessMaker/Jobs/TestScript.php#L80-L87
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0c20653. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/ScriptRunners/InProcessPhpRunner.php
$env['API_SSL_VERIFY'] = config('app.api_ssl_verify') ? '1' : '0';
}

return $env;

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Subprocess inherits worker secrets

Medium Severity

buildEnvironment copies the entire parent $_ENV and $_SERVER into the bare PHP subprocess. Unlike the Docker runner, that includes APP_KEY, database credentials, and other worker secrets, so in-process scripts receive far more privileged environment than isolated executors.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0c20653. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/ScriptRunners/ScriptRunner.php Resolved

Uh oh!

There was an error while loading. Please reload this page.

cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread ProcessMaker/Http/Controllers/Api/ScriptController.php
return false;
}

if ($request->boolean('run_in_process')) {

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: HIGH

Description: POST /api/1.0/scripts/{script}/preview host-executes the request body's PHP when the caller sends run_in_process=true, or whenever the saved script has allow_in_process, without requiring the executed bytes to be that persisted trusted script. The route is only can:view-scripts. ScriptRunner and CoreServiceTask both refuse in-process execution unless the script record has allow_in_process, and config/core-service-task.php documents that limit, but shouldPreviewInProcess returns true on the client boolean first. TestScript then overwrites the in-memory script with the request code and, in the default modules mode, ScriptRuntime::run include()s it in the queue worker after putenv of every EnvironmentVariable and a run-as user's API_TOKEN.

Impact: A user granted view-scripts (included on the default Process Designer role; requesters do not have it) can run arbitrary PHP inside the Laravel worker once CORE_SERVICE_TASK_ENABLED is on. That is the worker process, not the Docker or script-microservice sandbox preview used before: the included code can read the worker environment and database, the injected environment-variable secrets, and an API token for the script's run-as user. The same caller cannot set allow_in_process unless they also have edit-scripts, so this skips the host-execution trust gate the rest of the change enforces. The feature flag defaults off; the hole is the enabled configuration this PR adds.

Remediation: Do not honor a client run_in_process flag. Host-execute preview only when the saved script has allow_in_process and the executed bytes are that persisted script (ignore or reject a differing code body). Keep the ScriptRunner and CoreServiceTask allow_in_process checks as the only in-process gate.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/ScriptRuntime/ScriptRuntime.php
'modules' => $__instances,
] + $__instances, EXTR_SKIP);

return include $__scriptPath;

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: MEDIUM

Description: Modules-mode script execution stores a capped timeout on ScriptExecutionContext and then never enforces it. ScriptRuntime::run() include()s the PHP script in the current worker with no set_time_limit, alarm, or subprocess timeout. The bare runner (InProcessPhpRunner) applies the same cap via Process::timeout(). CLI workers are configured with max_execution_time=0, and TestScript, ExecuteScript, RunScriptTask, and RunNayraServiceTask set no job $timeout or retryUntil, so the only remaining kill is the Horizon bpmn supervisor timeout (default 3600s) via pcntl_alarm. That backstop is not the configured max_timeout (default cap 300s), and because the script runs in the worker it can clear it with pcntl_alarm(0) or pcntl_signal(SIGALRM, SIG_IGN) and then loop. Default bpmn maxProcesses is 1 and redis retry_after is 86400, so one run holds the shared BPMN worker for the full supervisor timeout (or until an operator kills it). SIGKILL also skips the finally block, so the script-runner Passport token created for the run is not deleted.

Impact: A Process Designer (view-scripts / edit-scripts), or a preview caller who can set run_in_process, can stall the shared bpmn queue past core-service-task.max_timeout once CORE_SERVICE_TASK_ENABLED is on. Execution mode defaults to modules. With the shipped Horizon pool of one bpmn worker, every other process, timer, and script task waits. A plain tight loop is reaped only at the 3600s supervisor timeout; disarming SIGALRM in the same process removes that kill. The script-runner access token then stays valid because GenerateAccessToken::delete() never runs. This is a fail-open of the execution limit the new in-process path claims to apply, on a hot shared worker, not a self-scoped delay.

Remediation: Enforce the already-capped context timeout around modules execution and fail the job with the same ScriptTimeoutException the bare runner raises. An in-process set_time_limit or pcntl_alarm is not enough: the included script can call set_time_limit(0) or pcntl_alarm(0). Use a watcher the script cannot clear (subprocess Process::timeout, as in InProcessPhpRunner, or a parent that is not the included script) and delete the script-runner Passport token even when the run is aborted.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
caleeli and others added 2 commits November 12, 2026 12:40
@caleeli
restore version
050d646
@devmiguelangel
Merge remote-tracking branch 'origin/feature/FOUR-34328' into FOUR-34384
c5aff8d
cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread ProcessMaker/ScriptRunners/InProcessPhpRunner.php
*/
private function buildEnvironment(?User $user, ?GenerateAccessToken $accessToken): array
{
$env = array_merge($_ENV, $_SERVER);

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: HIGH

Description: Bare in-process PHP execution forwards the queue worker's environment, including application secrets, into the script subprocess. buildEnvironment() starts from array_merge($_ENV, $_SERVER) and Process::env() passes that array to Symfony Process. Symfony Process 8.1 then overlays the parent environment (getDefaultEnv()) and does not drop keys that were merely omitted, so APP_KEY, DB_PASSWORD, and the rest of the worker environment remain readable with getenv() inside script.php. The script return value is JSON-encoded to stdout and returned to the caller.

Impact: A script author is a lower-trust actor than the worker. Docker execution only injects EnvironmentVariable rows, HOST_URL, smart-extract settings, and a run-as API token. Bare mode is the supported subprocess path (CORE_SERVICE_TASK_EXECUTION other than modules) whose purpose is a PHP process without Laravel, but it still exposes the worker credential set. The script can return those values in preview output or process data, or send them off-box. APP_KEY and database credentials are full application credentials, not script-scoped configuration. This path is off by default: core-service-task is disabled and execution defaults to modules, which does not call InProcessPhpRunner. It is reachable once an operator sets CORE_SERVICE_TASK_ENABLED=true and CORE_SERVICE_TASK_EXECUTION=bare. In that configuration an authenticated user with view-scripts can POST scripts/{script}/preview with run_in_process=true and arbitrary PHP (allow_in_process is not required). The same runner is also used for allow_in_process PHP script tasks and core service tasks.

Remediation: Build the subprocess environment from the same allowlist as Base::getEnvironmentVariables (EnvironmentVariable rows, HOST_URL/APP_URL, smart-extract settings, and the run-as API token). Do not merge $_ENV or $_SERVER into that array. Because Symfony Process 8 still merges getDefaultEnv(), set every inherited name that is not on the allowlist to false before proc_open so those variables are dropped. Add a test that getenv('APP_KEY') and getenv('DB_PASSWORD') are empty in the child while API_TOKEN and an EnvironmentVariable row are present.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread ProcessMaker/Managers/TenantPluginAssetPublisher.php
}

File::ensureDirectoryExists(dirname($destination));
File::copyDirectory($source, $destination);

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: MEDIUM

Description: Tenant plugin asset publish dereferences symlinks under the plugin public/ tree and writes the target bytes into the tenant document root as a normal file. git clone (no core.symlinks=false) leaves those links in storage/tenant_{id}/plugins/{repo}/public. publishPlugin() accepts the tree when is_dir() is true, which follows a symlink, and copyPublicDirectory() calls File::copyDirectory() with no is_link skip and no realpath containment. Laravel's copier uses is_dir()/SplFileInfo::isDir() and PHP copy(), both of which follow links, so a file symlink is materialized and a directory symlink is recursed into. A plugin repository can therefore point public/leak at a stable relative path such as ../../../../../.env (from storage/tenant_{id}/plugins/{repo}/public up to the app root) and the file contents are written under public/storage/tenant_{id}/vendor/processmaker/packages/{package}/, which is the static URL prefix /storage/tenant_{id}/vendor/processmaker/packages/{package}/. That publish runs from finishPluginSetup after clone, including when composer.json has no scripts and no extra.laravel.providers, so the copy itself is enough to place the bytes on disk.

Impact: A plugin repository author is a lower-trust actor than the host filesystem. An operator with manage-plugins who installs that https git URL (multitenancy on, tenant_plugin_assets_enabled default true, queue job tenant-aware) causes host files readable by the PHP user, including the application .env and other credential files, to be stored inside the web root. The resulting object is an ordinary file on the tenant public disk (public/storage/tenant_{id} is the tenants:storage-link path into storage/app/public, and a missing link still creates a directory under public/), so it is fetched as a static asset with no application auth. That crosses the plugin public-directory boundary the new publisher is responsible for. Zip extraction was not verified to recreate symlink entries; the confirmed path is git clone into the plugin directory followed by this publish.

Remediation: While publishing plugin public assets, skip symlink entries (is_link) and do not recurse through directory symlinks. Before each write, require realpath of the source file to remain under realpath of the plugin public directory, and require the destination to remain under the tenant package directory. Apply the same containment in PluginManager::copyDirectory so a local or extracted tree cannot materialize out-of-tree targets before publish.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
caleeli and others added 3 commits November 12, 2026 13:04
@caleeli
Merge pull request #9162 from ProcessMaker/FOUR-34384
55dc733
FOUR-34384 Migrate ProcessMaker Core skills to Skills repository
@rodriquelca
feat(FOUR-34380):Subprocess Inline to group task to reduce the visual…
705f2fa
…izacion of BPMN - FE
@caleeli
Merge pull request #9163 from ProcessMaker/feature/FOUR-34380_B
bba43f1
feat(FOUR-34380):Subprocess Inline to group task to reduce the visualizacion of BPMN - FE
cursor[bot]
cursor Bot reviewed Nov 12, 2026
View reviewed changes
Comment thread .agents/skills/manage-requirements/quality/requirements.txt
# Schema/heuristic gates use requirements-schema.txt (pinned PyYAML).
PyYAML==6.0.3
laya>=0.3.21,<0.4
laya-evals>=0.1.0

@cursor cursor Bot Nov 12, 2026

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

🔒 Agentic Security Review

Severity: MEDIUM

Description: The new Laya extras manifest depends on PyPI project laya-evals>=0.1.0. That project is owned by PyPI user gjxv (github.com/Gjusev/laya-evals), not Convai Innovations, and its own description says it is not the laya-evals CLI shipped with upstream laya. The quality harness only imports laya.Router. README.md and run_laya_eval.sh still tell an operator to pip install -r requirements.txt, so that install trusts every later gjxv release. Published laya-evals 0.1.0 has no console script, so this release does not itself overwrite upstream laya's laya-evals entry point; the open range lets a later release add that script or run code from its build backend.

Impact: An operator or agent who follows the new optional Laya install instructions installs and can execute code from an unrelated publisher in the same environment as laya. That is a new provenance trust of a third-party PyPI project, with an unbounded upper version, on a name that collides with upstream laya's documented eval CLI. Repo CI does not install this file today; the blast radius is every environment that opts into the documented requirements file, including a later in-range release chosen by that publisher.

Remediation: Remove laya-evals from requirements.txt. The harness only needs the pinned laya distribution, which already documents its own laya-evals CLI. If a separate extra is intentional, depend on a hash-pinned artifact from the same publisher as laya, not the colliding PyPI name on an open range.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@pmPaulis pmPaulis changed the title FOUR-34328 MCP Server FOUR-34328: [EPIC] MCP Server Nov 12, 2026
@pmPaulis pmPaulis assigned caleeli Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@cursor cursor[bot] cursor[bot] left review comments
@agustinbusso agustinbusso Awaiting requested review from agustinbusso
@nolanpro nolanpro Awaiting requested review from nolanpro

At least 1 approving review is required to merge this pull request.

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@caleeli caleeli

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

9 participants

@caleeli @nolanpro @marcoAntonioNina @julceslauhub @henryjonathanquispe @agustinbusso @pmPaulis @devmiguelangel @rodriquelca
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.