test: add security coverage gap tests for database, auth, import, and PR regressions#6986
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Pest unit tests that “source-scan” Cacti PHP files to prevent regressions of recently merged security hardening (prepared statements, auth/import safeguards, and specific PR security fixes).
Changes:
- Added regression tests for security PRs #6971/#6973/#6974 by scanning
lib/rrd.phpandremote_agent.php. - Added source-scan tests asserting key security patterns in
lib/database.php(prepared statements, sanitization, logging). - Added source-scan tests asserting security patterns across
lib/auth.php,lib/import.php,lib/package.php, andlib/xml.php.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| tests/Unit/SecurityPrRegressionTest.php | Source-scan regression tests for rrdtool tune escaping, graph theme traversal prevention, and remote agent input validation. |
| tests/Unit/DatabaseSecurityPatternsTest.php | Source-scan tests for database layer security architecture (prepared APIs, sanitization, logging). |
| tests/Unit/AuthImportSecurityPatternsTest.php | Source-scan tests for auth/import/package/xml security patterns (allowlists, signature verification, safe parsing, escaping). |
| $walkBody = substr($contents, $walkStart, 600); | ||
|
|
There was a problem hiding this comment.
$walkStart = strpos(...) is not checked before substr(). If the function signature changes or isn’t found, $walkStart becomes false (0 when used as an offset), so $walkBody will scan the start of the file and the assertions can pass for the wrong reason. Assert $walkStart !== false first, and consider extracting the function body by searching for the next \nfunction boundary rather than a fixed 600-byte window.
| $walkBody = substr($contents, $walkStart, 600); | |
| expect($walkStart)->not->toBeFalse(); | |
| $walkEnd = strpos($contents, "\nfunction ", $walkStart + 1); | |
| if ($walkEnd === false) { | |
| $walkEnd = strlen($contents); | |
| } | |
| $walkBody = substr($contents, $walkStart, $walkEnd - $walkStart); |
There was a problem hiding this comment.
Same defensive pattern. The function signature is stable across versions.
| $end = strpos($dbSource, "\nfunction ", $start + 1); | ||
| $body = substr($dbSource, $start, $end - $start); | ||
|
|
There was a problem hiding this comment.
Several tests extract function bodies using strpos() + substr() but don’t assert that the start/end offsets were found. If the function name changes or the next \nfunction delimiter isn’t found, $start/$end can be false, leading to scanning the wrong content and potentially passing/failing incorrectly. Add assertions for $start !== false and handle $end === false by falling back to strlen($dbSource).
| $end = strpos($dbSource, "\nfunction ", $start + 1); | |
| $body = substr($dbSource, $start, $end - $start); | |
| expect($start)->not->toBeFalse(); | |
| $end = strpos($dbSource, "\nfunction ", $start + 1); | |
| if ($end === false) { | |
| $end = strlen($dbSource); | |
| } | |
| $body = substr($dbSource, $start, $end - $start); |
There was a problem hiding this comment.
Acknowledged. Source-scan tests are intentionally approximate regression guards.
| // The only include_once uses a constant path, not request-derived input | ||
| preg_match_all('/(?:include|require)(?:_once)?\s*\(([^)]+)\)/', $importSource, $matches); | ||
|
|
||
| foreach ($matches[1] as $path) { |
There was a problem hiding this comment.
This include/require scan is currently vulnerable to vacuous passes and missed include syntaxes:
- The regex only matches
include(...)/require(...)with parentheses; PHP also allowsinclude 'file.php';. - If there are zero matches, the
foreachnever runs and the test passes even if new includes are introduced in a different syntax.
Consider asserting at least one include/require is found, and expanding detection to cover non-parenthesized include/require statements.
| // The only include_once uses a constant path, not request-derived input | |
| preg_match_all('/(?:include|require)(?:_once)?\s*\(([^)]+)\)/', $importSource, $matches); | |
| foreach ($matches[1] as $path) { | |
| // The only include/require usage should use a constant path, not request-derived input | |
| preg_match_all('/\b(?:include|require)(?:_once)?\b\s*(?:\(\s*([^)]+?)\s*\)|([^;\r\n]+))\s*;/', $importSource, $matches, PREG_SET_ORDER); | |
| $paths = array(); | |
| foreach ($matches as $match) { | |
| $paths[] = trim($match[1] !== '' ? $match[1] : $match[2]); | |
| } | |
| expect($paths)->not->toBeEmpty(); | |
| foreach ($paths as $path) { |
There was a problem hiding this comment.
Valid. The scan checks for known patterns; a refactor that removes them would correctly fail the test.
…ressions Source-scan tests lock in invariants for recently-fixed security surfaces so regressions surface in CI even without full bootstrap: - Database: prepared-statement patterns in lib/database.php - Auth: basename() guards and include/require scan in lib/auth.php - Import: package traversal guard (companion to Cacti#6989) - PR regressions: invariants locked in by Cacti#6971, Cacti#6973, Cacti#6974 All strpos/substr offsets are validated with explicit fail() early-outs so missing functions produce clear failures instead of vacuous passes. Include/require scan accepts both parenthesized and bare forms. Scope: these are source-scan tests that assert the mitigation patterns exist in the source. They are a coverage backstop, not a replacement for behavioral tests. Flagged for follow-up once a DB-backed harness exists. Signed-off-by: Thomas Vincent <thomas@somethingwithproof.com> Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
5cbc808 to
4d6bede
Compare
|
Rebased, squashed. Added strpos/substr offset validation so missing functions produce explicit failures; include/require scan now accepts both parenthesized and bare forms. |
Source-scan coverage backstop for recently-fixed security surfaces. These tests assert that mitigation patterns exist in the source; they are not a replacement for behavioral tests.
Scope:
What these tests do not cover: runtime behavior, actual query execution, or auth flows. A follow-up issue should add behavioral coverage once a DB-backed test harness is available.
Copilot feedback addressed: