test: add Pest v1 security test infrastructure#305
test: add Pest v1 security test infrastructure#305somethingwithproof wants to merge 3 commits intoCacti:developfrom
Conversation
Add source-scan tests verifying security patterns (prepared statements, output escaping, auth guards, PHP 7.4 compatibility) remain in place across refactors. Tests run with Pest v1 (PHP 7.3+) and stub the Cacti framework so plugins can be tested in isolation. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds a Pest v1 test scaffold and lightweight Cacti stubs to enable security/compatibility checks against the syslog plugin codebase.
Changes:
- Introduces Pest bootstrap/config and several source-scanning security/compatibility tests.
- Adds a
composer.jsonwith Pest as a dev dependency and test bootstrap autoloading. - Adds a CodeQL workflow configuration.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
tests/Security/SetupStructureTest.php |
Verifies setup.php defines expected plugin entrypoints and version keys. |
tests/Security/PreparedStatementConsistencyTest.php |
Scans key plugin files for raw db_* calls to prevent regressions. |
tests/Security/Php74CompatibilityTest.php |
Scans plugin files for PHP 8-only syntax/function usage. |
tests/Pest.php |
Loads the test bootstrap for Pest runs. |
tests/bootstrap.php |
Provides Cacti framework stubs for running tests without full Cacti. |
composer.json |
Adds Pest v1 as a dev dependency and wires test bootstrap autoloading. |
.github/workflows/codeql.yml |
Adds CodeQL workflow definition. |
| it('uses prepared DB helpers in all plugin files', function () { | ||
| $targetFiles = array( | ||
| 'database.php', | ||
| 'functions.php', | ||
| 'setup.php', | ||
| 'syslog.php', | ||
| 'syslog_alerts.php', | ||
| 'syslog_process.php', | ||
| 'syslog_removal.php', | ||
| 'syslog_reports.php', | ||
| ); |
There was a problem hiding this comment.
This test will flag legitimate db_* calls that must exist in this repo, so it’s likely to fail immediately (e.g., database.php contains return db_execute(...) inside the syslog_db_* wrappers, and setup.php has db_execute(...) calls for plugin metadata cleanup). Consider excluding database.php (and/or known-safe sections) from scanning, or tightening the check to only prohibit raw db_* usage outside the syslog DB wrapper layer (e.g., enforce syslog_db_* usage in non-wrapper files).
| continue; | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| continue; |
There was a problem hiding this comment.
Silently continue-ing when a target file is missing or unreadable makes this test pass without actually validating anything. Prefer failing the test with a clear message when realpath() or file_get_contents() fails so CI can catch moved/renamed files or unexpected read errors.
| continue; | |
| } | |
| $contents = file_get_contents($path); | |
| if ($contents === false) { | |
| continue; | |
| throw new RuntimeException("Unable to resolve target file for prepared statement consistency test: {$relativeFile}"); | |
| } | |
| $contents = file_get_contents($path); | |
| if ($contents === false) { | |
| throw new RuntimeException("Unable to read target file for prepared statement consistency test: {$path}"); |
| function db_execute($sql) { | ||
| $GLOBALS['__test_db_calls'][] = array('fn' => 'db_execute', 'sql' => $sql, 'params' => array()); |
There was a problem hiding this comment.
The db_execute stub signature doesn’t match how the plugin calls it (the syslog DB wrappers call db_execute($sql, $log, $syslog_cnn)), which can cause ArgumentCountError if any test loads plugin code. Update the stub to accept the additional optional parameters (log flag / connection) to mirror Cacti’s function signature.
| function db_execute($sql) { | |
| $GLOBALS['__test_db_calls'][] = array('fn' => 'db_execute', 'sql' => $sql, 'params' => array()); | |
| function db_execute($sql, $log = true, $cnn = false) { | |
| $GLOBALS['__test_db_calls'][] = array('fn' => 'db_execute', 'sql' => $sql, 'params' => array(), 'log' => $log, 'cnn' => $cnn); |
| } | ||
|
|
||
| if (!function_exists('db_execute_prepared')) { | ||
| function db_execute_prepared($sql, $params = array()) { |
There was a problem hiding this comment.
Similarly, db_execute_prepared is called by the plugin with additional parameters (e.g. $log, $syslog_cnn). The stub should accept those optional arguments to avoid runtime errors when plugin code is executed during tests.
| function db_execute_prepared($sql, $params = array()) { | |
| function db_execute_prepared($sql, $params = array(), $log = true, $cnn = null) { |
| if (!function_exists('db_fetch_assoc')) { | ||
| function db_fetch_assoc($sql) { | ||
| return array(); | ||
| } | ||
| } | ||
|
|
||
| if (!function_exists('db_fetch_assoc_prepared')) { | ||
| function db_fetch_assoc_prepared($sql, $params = array()) { | ||
| return array(); | ||
| } | ||
| } | ||
|
|
||
| if (!function_exists('db_fetch_row')) { | ||
| function db_fetch_row($sql) { | ||
| return array(); | ||
| } | ||
| } | ||
|
|
||
| if (!function_exists('db_fetch_row_prepared')) { | ||
| function db_fetch_row_prepared($sql, $params = array()) { | ||
| return array(); | ||
| } | ||
| } | ||
|
|
||
| if (!function_exists('db_fetch_cell')) { | ||
| function db_fetch_cell($sql) { | ||
| return ''; | ||
| } | ||
| } | ||
|
|
||
| if (!function_exists('db_fetch_cell_prepared')) { | ||
| function db_fetch_cell_prepared($sql, $params = array()) { | ||
| return ''; | ||
| } | ||
| } |
There was a problem hiding this comment.
The db_fetch_* stubs accept fewer parameters than the plugin’s wrappers pass through (e.g. db_fetch_cell($sql, $col_name, $log, $cnn) and db_fetch_assoc($sql, $log, $cnn)). If any test includes code paths that invoke these wrappers, this will fatally error; consider updating these stubs to accept the optional $log/$cnn parameters (and $col_name where applicable).
| $source = file_get_contents(realpath(__DIR__ . '/../../setup.php')); | ||
|
|
There was a problem hiding this comment.
If setup.php can’t be resolved/read (e.g., path changes), $source becomes false and subsequent string expectations will behave unpredictably and/or emit warnings. Consider asserting that realpath() and file_get_contents() succeeded (fail fast with a clear message) before running toContain / toMatch expectations.
| $source = file_get_contents(realpath(__DIR__ . '/../../setup.php')); | |
| $setupPath = realpath(__DIR__ . '/../../setup.php'); | |
| expect($setupPath) | |
| ->not->toBeFalse('Failed to resolve setup.php path from ' . __DIR__); | |
| $source = file_get_contents($setupPath); | |
| expect($source) | |
| ->not->toBeFalse('Failed to read setup.php at path: ' . $setupPath); |
| foreach ($files as $relativeFile) { | ||
| $path = realpath(__DIR__ . '/../../' . $relativeFile); | ||
|
|
||
| if ($path === false) { | ||
| continue; | ||
| } | ||
|
|
||
| $contents = file_get_contents($path); | ||
|
|
||
| if ($contents === false) { | ||
| continue; | ||
| } | ||
|
|
There was a problem hiding this comment.
These tests silently skip files when realpath() or file_get_contents() fails, which can lead to false-green results. Prefer failing with an explicit assertion/message when a target file is missing/unreadable so the compatibility gate remains meaningful over time.
| push: | ||
| branches: [main, master, develop, regression-audit] | ||
| paths-ignore: | ||
| - "**/*.php" | ||
| - "**/*.md" | ||
| pull_request: | ||
| branches: [main, master, develop, regression-audit] | ||
| paths-ignore: | ||
| - "**/*.php" | ||
| - "**/*.md" |
There was a problem hiding this comment.
paths-ignore excludes all **/*.php, so CodeQL won’t run on the vast majority of changes in this PHP plugin. If the intent is to security-scan the plugin, remove the PHP ignore (and consider ignoring only vendor/ or generated paths instead).
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| language: ["javascript-typescript", "python", "ruby"] | ||
| steps: |
There was a problem hiding this comment.
The CodeQL language matrix doesn’t include php, so even when the workflow runs it won’t analyze the primary language in this repository. Add PHP to the matrix (or switch to a single-language config) so CodeQL produces useful results for this codebase.
|
Closing this one. We can discuss on Sunday. |
Summary
Test plan
composer install && vendor/bin/pestpasses