feat(security): Symfony Process and Mime helpers#7073
feat(security): Symfony Process and Mime helpers#7073somethingwithproof wants to merge 14 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces a centralized subprocess runner for Cacti PHP code by adding symfony/process and a small wrapper API (CactiProcess) intended to reduce shell-injection risk and standardize timeouts/exit-code handling, with an initial migration of one CLI script plus tests and documentation.
Changes:
- Add
symfony/processdependency and implementCactiProcess,CactiProcessResult, andCactiProcessException. - Migrate
cli/push_out_hosts.phpfrompassthru()shell-string execution to array-argv execution viaCactiProcess::runStreaming(). - Add unit/integration tests for the wrapper and a security/migration guide doc.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
composer.json |
Adds symfony/process dependency. |
lib/CactiProcess.php |
New wrapper for running subprocesses (complete + streaming). |
lib/CactiProcessResult.php |
Result value object for exit code/stdout/stderr plus outputLines(). |
lib/CactiProcessException.php |
Typed exception for process runner failures. |
cli/push_out_hosts.php |
Pilot replacement of passthru() with CactiProcess::runStreaming(). |
tests/Unit/CactiProcessTest.php |
Unit coverage for exit codes, timeout, argv safety, and output parsing. |
tests/integration/CactiProcessIntegrationTest.php |
Integration test for streaming line delivery. |
docs/security/process-runner.md |
Rationale + migration guide for adopting CactiProcess. |
aed054c to
b213f01
Compare
a19eda7 to
c4fdd47
Compare
Routes external commands through Symfony Process in array-mode argv, scrubs the environment to a baseline allowlist, and centralizes timeout and exit-code handling. Migrates cli/push_out_hosts.php as the pilot call site. Includes unit, integration, and a migration guide. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
c4fdd47 to
83ec329
Compare
Pest tests pin every data boundary the wrapper crosses (argv→child, env scrub, stdout bytes, exit code, exec-shape parity, timeout chaining). infection.json5 scopes mutation analysis to the wrapper files with a 70% MSI floor. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
PHP refuses to start without SYSTEMROOT; proc_open relies on COMSPEC and PATHEXT to resolve executables under cmd.exe. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Routes uploaded-file MIME detection through Symfony Mime so a content-type allowlist can be enforced on top of the existing extension and signature checks. package_import.php now rejects uploads whose detected MIME is not in the allowlist (zip, gzip, xml variants) before any further processing. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
LIBXML_NONET blocks network entity loading as defense in depth even though libxml2 ≥ 2.9.0 disables external entities by default. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…t timeout to float Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Match the flat un-namespaced lib/CactiX.php convention used by Cacti#7088, Cacti#7073, Cacti#7077, Cacti#7075. Drop symfony/process (added by Cacti#7073) and symfony/validator (added by Cacti#7077) to avoid composer.json conflicts at merge time. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Match the un-namespaced lib/CactiX.php convention used by canonical PRs (Cacti#7088 CactiProcess, Cacti#7073 CactiMime, Cacti#7077 CactiSettings, Cacti#7075 CactiApplication/CactiCommand). The previous nested PSR-4 namespace required composer autoload changes that conflicted with those PRs. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Drop lib/Process/CactiProcess.php and tests/Unit/CactiProcessTest.php to avoid colliding with Cacti#7088 / Cacti#7073, which already provide the canonical flat lib/CactiProcess.php. Move the remaining wrappers to the un-namespaced lib/CactiX.php convention used by every other lib/Cacti*.php class on develop: lib/Filesystem/CactiFilesystem.php -> lib/CactiFilesystem.php lib/Http/CactiRequest.php -> lib/CactiRequest.php lib/Log/CactiLogger.php -> lib/CactiLogger.php Tests load the classes via require_once instead of the dropped PSR-4 autoloader. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Rename lib/Security/CactiOAuth.php to lib/CactiOAuth.php and drop the Cacti\Security namespace. Update the two callers (oauth2.php and lib/functions.php mailer) to use the unqualified class name. Behavior is unchanged: only the FQCN was rewritten. The flat layout matches every other lib/Cacti*.php class on develop and in the canonical PRs (Cacti#7088, Cacti#7073, Cacti#7077, Cacti#7075). Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Allows callers to inject env name=value pairs without putenv() or argv exposure. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
|
Follow-up commit This unblocks redoing the call-site migrations from the now-closed #7088 against the correct API. The migrations themselves will follow as small per-file PRs once the API change here lands. |
|
Queue note: treating this as the base Symfony/helper infrastructure PR. Intended order is this PR first, then #7075 after the Process/Mime helper direction is accepted. |
…7124) * test: shared hand-off + mutation infrastructure for feature PRs Consolidates infection/infection dev dep, allow-plugins entry, baseline infection.json5, tests/HandOff/HandOffHelpers.php with reusable stubs (cacti_log capture, temp-file fixtures, minimal-zip builder), HandOff testsuite registration, and the documented pattern. Feature PRs that add hand-off coverage rebase onto this and contribute only ONE HandOffTest file plus an optional infection.json5 filter override. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: consolidate per-feature hand-off suites into shared infra Each test guards on its feature file and skips when not present on develop. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(test): replace eval stub, unify log buffer ref, guard ZipArchive add Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ci): correct infection vendor path in infection.json5 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci(phpstan): baseline upstream undefined-variable and isset errors Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: add HandOff and Integration testsuites; add regression guards - phpunit.xml: add Integration testsuite alongside existing Unit/HandOff - tests/Integration/: DbDumpIntegrationTest, PingIntegrationTest (from develop), CactiProcessIntegrationTest, SqlScriptsIntegrationTest - tests/HandOff/RegressionGuardTest: guards against backsliding on shell_exec, password-in-argv, RLIKE concat, and open-redirect patterns Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore(test): pest test infrastructure and PSR-4 autoload Add Pest 2 dev dependency, regenerate composer.lock under PHP 8.1, declare PSR-4 autoload for the Cacti namespace, and seed the test bootstrap with three unit tests plus an orb integration check. Pest stays on the v2 line because v3 transitively requires PHP 8.2 and breaks the 8.1 matrix entry. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore(phpstan): catch up baseline with 11 upstream-detected entries Cacti Commit Audit fails on develop with 11 PHPStan errors that are not yet in phpstan-baseline.neon. Append the matching entries so the PR-A branch passes Run PHPStan at Level 6. Same set of entries already exists on PR #7077; this is a transient catch-up that upstream will absorb when those entries land on develop. Files: aggregate_graphs.php, color_templates.php, graph_templates.php, graphs.php, lib/html.php (3 entries). Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore(test): drop composer.lock; let CI resolve per PHP version The lock file pinned brianium/paratest v7.3.1 which only supports PHP 8.1-8.3, so the 8.4 matrix entry rejected the lock with "Your lock file does not contain a compatible set of packages". A single lock cannot satisfy 8.1 (paratest 7.3.x) and 8.4 (paratest 7.4.x) at the same time because pestphp/pest 2.36.0 is the last 2.x release that supports 8.1 and the 8.1-compatible paratest tag predates 8.4 support. Match upstream develop's behaviour: no committed lock; let `composer install` in CI resolve per matrix entry. Pest stays on the v2 line in composer.json so 8.1 still gets a compatible Pest. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(test): apply Copilot review feedback for test infra Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore(test): drop psr-4 autoload and align deps with canonical PRs Match the flat un-namespaced lib/CactiX.php convention used by #7088, #7073, #7077, #7075. Drop symfony/process (added by #7073) and symfony/validator (added by #7077) to avoid composer.json conflicts at merge time. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: skip integration suites and regression guards pending feature PRs Guards integration tests and regression guards from #7083 against absence of CactiProcess and other feature-PR hardening, so the consolidated foundation PR is green on develop. Suites activate automatically once their feature PRs merge. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ci): drop infection/infection to unbreak PHP 8.4 matrix infection/infection ^0.27 transitively pins thecodingmachine/safe ^2.1.2, resolving to v2.5.0. Its generated stubs use implicit-nullable parameter declarations, which PHP 8.4 emits as deprecations into cacti.log and trips the log-quiet assertion. infection has no config or test wiring in this repo, so removing the dev dep clears the transitive pin without losing functionality. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net>
| get_options(); | ||
|
|
||
| // start background caching process if not running | ||
| $php = cacti_escapeshellcmd(read_config_option('path_php_binary')); |
There was a problem hiding this comment.
Seems this should be registered using register_process_start(), maybe even automatically by Cactid.
| } | ||
|
|
||
| // start background caching process if not running | ||
| $php = cacti_escapeshellcmd(read_config_option('path_php_binary')); |
There was a problem hiding this comment.
Seems this should be migrated to register and unregister process.
| '--graph=' . $local_graph_id, | ||
| '--interval=' . ((int) $graph_data_array['ds_step']), | ||
| '--poller_id=' . hash('sha256', session_id()), | ||
| ], ['timeout' => null, 'expected_exit_codes' => range(0, 255)]); |
There was a problem hiding this comment.
This should have a relatively short timeout, otherwise things will block and crash the web server.
* refactor(oauth): extract Cacti\Security\CactiOAuth provider factory Replace the inline provider switch in oauth2.php and the OAuth branch of mailer() in lib/functions.php with two helpers: - CactiOAuth::getProvider($name, $params) returns the configured provider instance for `google`, `azure`, `yahoo`, or `microsoft`, or null when the configured provider is unknown. - CactiOAuth::getDefaultOptions($name) returns the scope set each provider expects. Behaviour is preserved; both call sites now branch on a null return rather than relying on a `$provider` variable that may have fallen through the switch unset. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * chore(phpstan): catch up baseline with 11 upstream-detected entries Same as PR-A: appends 11 PHPStan ignoreErrors entries that exist on upstream develop but are not yet baselined, so this branch's CI does not regress on phpstan analyse --level 6. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor: flatten CactiOAuth to global namespace Rename lib/Security/CactiOAuth.php to lib/CactiOAuth.php and drop the Cacti\Security namespace. Update the two callers (oauth2.php and lib/functions.php mailer) to use the unqualified class name. Behavior is unchanged: only the FQCN was rewritten. The flat layout matches every other lib/Cacti*.php class on develop and in the canonical PRs (#7088, #7073, #7077, #7075). Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: add shared unit stubs for OAuth tests * fix(oauth): load provider factory in runtime paths * test(oauth): pin provider factory runtime loading --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net>
- Add explicit $onError callback to forward stderr chunks to STDERR, replacing reliance on CactiProcess's internal fallback. - Capture runStreaming() return value and pass to exit() so the wrapper propagates the child's exit status rather than always exiting 0. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
|
Addressed all five Copilot suggestions: timeout is cast to float with strict int|float|null validation; runStreaming forwards stderr via the $onError callback or fwrite(STDERR) fallback; push_out_hosts.php passes a dedicated onError handler that writes to STDERR; env validation throws CactiProcessException on non-array or non-string entries. The exit-code propagation change is intentional and documented in the commit message. |
# Conflicts: # composer.json # graph_realtime.php # tests/Unit/GraphRealtimeShellTest.php # tests/integration/CactiProcessIntegrationTest.php
|
Closing as superseded by #7189, which carries this work forward as the DI-friendly component baseline. Leaving the branch in place so the review history stays accessible. |
Current Status
Consolidated into #7189. Keep this PR only as historical review context for the original Symfony Process and Mime extraction; the merge direction should be the DI-friendly component baseline in #7189.
Original Scope
Recommendation
Do not merge this independently unless it is deliberately rebased to match #7189. New or migrated code should follow the #7189 wrapper style: injectable service instances first, static facades only for legacy procedural call sites.