Skip to content

test: add security coverage gap tests for database, auth, import, and PR regressions#6986

Closed
somethingwithproof wants to merge 1 commit into
Cacti:developfrom
somethingwithproof:test/security-coverage-gaps
Closed

test: add security coverage gap tests for database, auth, import, and PR regressions#6986
somethingwithproof wants to merge 1 commit into
Cacti:developfrom
somethingwithproof:test/security-coverage-gaps

Conversation

@somethingwithproof

@somethingwithproof somethingwithproof commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

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:

  • All `strpos()`/`substr()` offset uses now have explicit `fail()` early-outs so a renamed or missing function produces a clear failure rather than a vacuous pass.
  • The `include`/`require` scan regex now matches both parenthesized (`include('path')`) and bare (`include 'path'`) forms.

Copilot AI review requested due to automatic review settings April 6, 2026 05:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.php and remote_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, and lib/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).

Comment thread tests/Unit/SecurityPrRegressionTest.php
Comment thread tests/Unit/SecurityPrRegressionTest.php Outdated
Comment on lines +129 to +130
$walkBody = substr($contents, $walkStart, 600);

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

$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.

Suggested change
$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);

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same defensive pattern. The function signature is stable across versions.

Comment on lines +166 to +168
$end = strpos($dbSource, "\nfunction ", $start + 1);
$body = substr($dbSource, $start, $end - $start);

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

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).

Suggested change
$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);

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. Source-scan tests are intentionally approximate regression guards.

Comment on lines +335 to +338
// 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) {

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

This include/require scan is currently vulnerable to vacuous passes and missed include syntaxes:

  • The regex only matches include(...)/require(...) with parentheses; PHP also allows include 'file.php';.
  • If there are zero matches, the foreach never 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.
Suggested change
// 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) {

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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>
@somethingwithproof somethingwithproof force-pushed the test/security-coverage-gaps branch from 5cbc808 to 4d6bede Compare April 11, 2026 10:13
@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Rebased, squashed. Added strpos/substr offset validation so missing functions produce explicit failures; include/require scan now accepts both parenthesized and bare forms.

@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Superseded by #7036. The security coverage-gap tests were consolidated into #7036 and aligned with the latest hardening behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants