Skip to content

fix(security): consolidated develop-port hardening batch#7036

Closed
somethingwithproof wants to merge 17 commits into
Cacti:developfrom
somethingwithproof:fix/security-hardening-batch
Closed

fix(security): consolidated develop-port hardening batch#7036
somethingwithproof wants to merge 17 commits into
Cacti:developfrom
somethingwithproof:fix/security-hardening-batch

Conversation

@somethingwithproof

@somethingwithproof somethingwithproof commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Consolidated develop-branch security hardening batch. This PR forward-ports and extends 1.2.x security fixes, and now includes the coverage suite from superseded PR #6986 (aligned with current hardened behavior).

Included hardening

  • SQL injection controls: parameterization, db_qstr_rlike(), safer sort input validation
  • command execution hardening: shell-arg and command-path tightening
  • XSS hardening in relevant render paths
  • path traversal validation helpers and caller integration

Consolidated tests

  • tests/Unit/Pr7036ReviewCoverageTest.php
  • tests/Unit/PathValidationIntegrationTest.php
  • tests/Unit/SecurityValidationHelpersTest.php
  • tests/Unit/RemoteAgentAuthTest.php
  • tests/Unit/AuthImportSecurityPatternsTest.php
  • tests/Unit/DatabaseSecurityPatternsTest.php
  • tests/Unit/SecurityPrRegressionTest.php

Verification

  • include/vendor/bin/pest tests/Unit/Pr7036ReviewCoverageTest.php tests/Unit/PathValidationIntegrationTest.php tests/Unit/SecurityValidationHelpersTest.php tests/Unit/RemoteAgentAuthTest.php tests/Unit/AuthImportSecurityPatternsTest.php tests/Unit/DatabaseSecurityPatternsTest.php tests/Unit/SecurityPrRegressionTest.php

Advisories addressed

GHSA-69gg, GHSA-9jqv, GHSA-pf37, GHSA-84q3, GHSA-j9jv, GHSA-wpjq, GHSA-c4qp, GHSA-8522, GHSA-xq98, GHSA-fwh3

Supersedes #6986

Copilot AI review requested due to automatic review settings April 9, 2026 01:04

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

This PR forward-ports multiple security hardening changes into develop for Cacti’s PHP codebase, aiming to reduce exposure to SQL injection, command injection (RCE), XSS, and path traversal vectors, and adds regression/contract tests to document affected advisories.

Changes:

  • Harden SQL query construction (RLIKE/REGEXP quoting, sort column/direction validation, selected-items sanitization).
  • Harden shell execution and user-controlled command string inputs (escaping/log redirection, array-arg support, additional input validation).
  • Add security regression/contract tests and introduce CodeQL/Dependabot configuration.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/Unit/SecurityXssPathTest.php Adds source/contract tests for XSS + path traversal advisories.
tests/Unit/SecuritySqliTest.php Adds SQLi regression/contract tests around RLIKE/REGEXP/ORDER BY injection vectors.
tests/Unit/SecurityRceTest.php Adds RCE-focused tests around shell execution and whitelist bypass scenarios.
tests/Unit/SecurityAuthTest.php Adds auth-related security advisory tests (open redirect, fixation, IDOR, race).
package_repos.php Fixes UI string typo (“Repositories”).
managers.php Switches to sanitized selected-items handling and int-casting for IN() lists.
lib/rrd.php Adds array-argument support to __rrd_execute() with escaping.
lib/reports.php Switches REGEXP concatenation to db_qstr() quoting.
lib/poller.php Adds array-argument support to exec_background() with escaping.
lib/html_tree.php Replaces raw RLIKE concatenation with db_qstr_rlike().
lib/html_graph.php Replaces raw RLIKE concatenation with db_qstr_rlike().
lib/html_filter.php Hardens sort_column/sort_direction input validation.
lib/functions.php Adds path validation helpers for traversal mitigation.
lib/database.php Adds db_qstr_rlike() helper for safer RLIKE clause construction.
lib/boost.php Escapes $boost_log in redirect args to mitigate command injection.
lib/api_automation.php Hardens sort column/direction validation for automation SQL generation.
data_templates.php Clarifies/fixes validation message wording.
data_input.php Adds metacharacter rejection logic for input_string to reduce RCE risk.
automation_templates.php Grammar fixes in field descriptions.
automation_networks.php Grammar fixes in notification field descriptions.
aggregate_graphs.php Escapes rfilter in HTML and hardens RLIKE clause construction.
.github/workflows/codeql.yml Adds CodeQL workflow (currently configured for JS/TS + Python).
.github/dependabot.yml Adds Dependabot config for npm + GitHub Actions.

Comment thread lib/functions.php
Comment thread lib/functions.php
Comment thread data_input.php Outdated
Comment thread managers.php
Comment thread .github/workflows/codeql.yml Outdated
Comment thread data_input.php Outdated
if (!is_error_message()) {
$input_string_bare = preg_replace('/<[a-zA-Z_]+>/', '', $save['input_string']);

if (preg_match('/[;&|`$\\\\\n\r]/', $input_string_bare)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will cause failures as many people use the | with things like grep and wc.

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.

The regex is /[;&$\n\r]/which does not block pipe|`. Pipes are intentionally allowed for grep/wc chaining.

Comment thread lib/api_automation.php
'filter' => FILTER_CALLBACK,
'default' => 'description',
'options' => ['options' => 'sanitize_search_string']
'options' => ['options' => function ($v) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing backtics and parenthesis.

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.

Relaxed the sort column regex to allow spaces, *, and dotted identifiers (h.hostname, COUNT(*), FIELD(status,1,3,2)). Still blocks ; -- UNION DROP DELETE INSERT UPDATE.

Comment thread auth_login.php
}

if (!$error) {
// rotate session ID to prevent session fixation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs testing.

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.

Agreed. Will verify in a test environment before requesting merge.

Comment thread lib/auth.php Outdated
AND realm = ?',
[$username, $realm]);

$failed = intval($user['failed_attempts']);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

intval not required

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.

Removed.

Comment thread lib/auth.php
cacti_log(sprintf("DEBUG: Referer from REDIRECT_URL with Value: '%s', Effective: '%s'", $_SERVER['REDIRECT_URL'], $referer), false, 'AUTH', POLLER_VERBOSITY_DEBUG);
} elseif (isset($_SERVER['HTTP_REFERER'])) {
$referer = $_SERVER['HTTP_REFERER'];
$referer = validate_redirect_url($_SERVER['HTTP_REFERER']);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Need deeper inspection

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.

Happy to walk through this section together if you want to schedule a review.

Comment thread lib/reports.php
$outstr .= "\t\t\t<td class='text' style='text-align:" . $alignment[$item['align']] . ';font-size: ' . $item['font_size'] . "pt;'>" . PHP_EOL;
}
$outstr .= "\t\t\t\t<h3>$title</h3>" . PHP_EOL;
$outstr .= "\t\t\t\t<h3>" . htmle($title) . '</h3>' . PHP_EOL;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment.

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 verification — raw DB value, not previously escaped.

Comment thread lib/rrd.php
// when the command is passed to shell_exec. Backticks enable command
// substitution; $() enables subshell execution. Standalone $ in text
// (e.g., "$5.00" in graph legends) is preserved.
$command = str_replace('`', '', $command);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This may be a breaking change. Have to review.

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.

Defense-in-depth against RCE via graph title injection into shell_exec. Backtick stripping and $() removal prevent command substitution. Legitimate graph titles with literal backticks are affected, but the alternative is an unguarded shell_exec path. Documented inline.

Comment thread lib/xml.php
* to be used for data retrieval(result-structure is Data oriented)
*/
if (function_exists('libxml_disable_entity_loader')) {
$loader = libxml_disable_entity_loader(true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs testing.

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.

Belt-and-braces XXE guard. No-op on PHP 8.0+ (libxml 2.9+ disables external entities by default). The function_exists check keeps it safe for forward compatibility. Low-risk.

Comment thread remote_agent.php
gfrv('rra_id');
gfrv('graph_theme', FILTER_CALLBACK, ['options' => 'sanitize_search_string']);
gfrv('graph_nolegend', FILTER_CALLBACK, ['options' => 'sanitize_search_string']);
gfrv('effective_user');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why?

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.

Bounds check on graph_start/graph_end timestamps. Without the < FILTER_VALIDATE_MAX_DATE_AS_INT guard, an attacker-supplied integer overflow value could produce unexpected behavior in rrdtool time range calculations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No, I mean you dropped the filter for effective user.

Comment thread remote_agent.php
} else {
$user = 0;
}
// The remote agent runs as the authenticated session user, not a request override.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs validation.

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.

IDOR fix. The previous code allowed the request to specify an arbitrary user_id for graph rendering. Using \$_SESSION[SESS_USER_ID] ensures the authenticated session user is used, preventing privilege escalation to another user's graph permissions.

@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Consolidation update: this PR now includes the security coverage-gap test suite from #6986, with assertions adjusted to match current hardening behavior on develop. Continue security review on #7036 only.

@somethingwithproof somethingwithproof deleted the fix/security-hardening-batch branch April 13, 2026 07:42
@somethingwithproof somethingwithproof restored the fix/security-hardening-batch branch April 13, 2026 07:42
@somethingwithproof somethingwithproof changed the title fix(security): harden SQLi, RCE, XSS, and path traversal (develop port) fix(security): consolidated develop-port hardening batch Apr 13, 2026
@somethingwithproof somethingwithproof force-pushed the fix/security-hardening-batch branch 2 times, most recently from 4048cd3 to 5bc4fff Compare April 13, 2026 20:41
@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Fixed two Pr7036ReviewCoverageTest assertions that drifted from the squashed hardening helpers. The tests now match the actual cacti_validate_sort_column regex (permits spaces inside aggregate function args like FIELD(status,1,3,2)) and the cacti_input_string_is_safe blocklist literal. Full pest suite passes (727 tests).

Defense-in-depth hardening covering:

- SQLi: migrate remaining RLIKE/REGEXP sites to db_qstr_rlike/db_qstr;
  parameterize managers.php selected-items IN() list
- RCE: escape rrdtool tune arguments; preg_replace strips $( from
  graph commands to block subshells (documented inline)
- XSS: rfilter rendering, html_filter.php escapes, form template
  hardening
- Path traversal: validate_path_within and validate_relative_path_within
  hardened with realpath boundary + NUL byte reject
- IDOR: auth_changepassword plugin redirect basename stripping;
  remote_agent effective_user int cast and OID format validation
- Session: auth lockout and CSPRNG fallback
- XML: libxml_disable_entity_loader belt-and-braces guard

Tests: source-scan and integration tests for database, auth, import,
path validation, RCE, XSS, and PR regression patterns.

PHP 8.0+ (develop).

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Match the tighter sort-column regex (allows aggregate function args with
spaces) and the backslash-free blocklist literal shown in
lib/security_validation.php.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…, fix unused loop var

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…s call

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof somethingwithproof force-pushed the fix/security-hardening-batch branch from 798e256 to f8c0551 Compare April 17, 2026 11:20
…n syntax.yml

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
….php and import.php

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…ake effect

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…PStan baseline

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…lity

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
… indentation styles

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…yamllint accepts any seq indent

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 18, 2026
…acti#7057)

Consolidated PR now only contains its unique work, avoiding merge conflicts
with the companion PRs. Files moved to their respective PRs:
- lib/ping.php → Cacti#7057
- theme/nav/layout files → Cacti#7040
- test files, workflow files, security helpers → Cacti#7036

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 18, 2026
This branch contains only files unique to the consolidated security
work that are not already covered by:
- Cacti#7036 (security hardening batch)
- Cacti#7040 (tooltip pin / theme+nav changes)
- Cacti#7057 (ping.php alignment)

Previously shipped as feat/security-consolidated-develop with 124 files;
rebased to eliminate merge conflicts by removing duplicated content.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 18, 2026
This branch contains only files unique to the consolidated security
work that are not already covered by:
- Cacti#7036 (security hardening batch)
- Cacti#7040 (tooltip pin / theme+nav changes)
- Cacti#7057 (ping.php alignment)

Previously shipped as feat/security-consolidated-develop with 124 files;
rebased to eliminate merge conflicts by removing duplicated content.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 18, 2026
This branch contains only files unique to the consolidated security
work that are not already covered by:
- Cacti#7036 (security hardening batch)
- Cacti#7040 (tooltip pin / theme+nav changes)
- Cacti#7057 (ping.php alignment)

Previously shipped as feat/security-consolidated-develop with 124 files;
rebased to eliminate merge conflicts by removing duplicated content.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 18, 2026
This branch contains only files unique to the consolidated security
work that are not already covered by:
- Cacti#7036 (security hardening batch)
- Cacti#7040 (tooltip pin / theme+nav changes)
- Cacti#7057 (ping.php alignment)

Previously shipped as feat/security-consolidated-develop with 124 files;
rebased to eliminate merge conflicts by removing duplicated content.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 18, 2026
This branch contains only files unique to the consolidated security
work that are not already covered by:
- Cacti#7036 (security hardening batch)
- Cacti#7040 (tooltip pin / theme+nav changes)
- Cacti#7057 (ping.php alignment)

Previously shipped as feat/security-consolidated-develop with 124 files;
rebased to eliminate merge conflicts by removing duplicated content.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof somethingwithproof force-pushed the fix/security-hardening-batch branch 2 times, most recently from a088760 to 5dd7480 Compare April 22, 2026 18:32
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 22, 2026
…acti#7057)

Consolidated PR now only contains its unique work, avoiding merge conflicts
with the companion PRs. Files moved to their respective PRs:
- lib/ping.php → Cacti#7057
- theme/nav/layout files → Cacti#7040
- test files, workflow files, security helpers → Cacti#7036

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 22, 2026
This branch contains only files unique to the consolidated security
work that are not already covered by:
- Cacti#7036 (security hardening batch)
- Cacti#7040 (tooltip pin / theme+nav changes)
- Cacti#7057 (ping.php alignment)

Previously shipped as feat/security-consolidated-develop with 124 files;
rebased to eliminate merge conflicts by removing duplicated content.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof somethingwithproof force-pushed the fix/security-hardening-batch branch from 5dd7480 to a088760 Compare April 22, 2026 18:58
TheWitness and others added 2 commits April 22, 2026 17:26
Add .claude/, .omc/, .worktrees/, and notepad.md to .gitignore so local
AI-assistant session state, oh-my-claudecode orchestration files, and
scratch worktrees never leak into a PR. Add CLAUDE.md at the repo root
documenting the house conventions (PHP 7.4 target on 1.2.x branches,
cacti_sizeof/htmle/gfrv wrappers, db_qstr_rlike, the vendor-pollution
pitfall) so future AI contributions match the review feedback pattern
from TheWitness instead of repeating it on every PR.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Added .claude//.omc//.worktrees/ to .gitignore plus a CLAUDE.md at the repo root with the idiom list (cacti_sizeof, htmle, gfrv, CACTI_SERVER_OS) so future PRs stop repeating the same class of style feedback. On the substantive items from your April 12 review pass — the breaking-change flags on lib/html_filter.php and lib/rrd.php, the intval / escaping checks, the remote_agent.php decisions, and the repeated same comment on lib/reports.php — the PR as currently scoped collides with hardening that already landed on develop since then. I'll either close this in favor of targeted per-class PRs (SQLi, XSS, RCE-hardening each as its own branch) or narrow this one to the non-overlapping residual. Which would you prefer?

…file writes

The file-write loop in import_package() was gated by str_contains(,
'scripts/') || str_contains(, 'resource/') but did not verify the
resolved path stayed under CACTI_PATH_BASE. Route $name through
validate_relative_path_within so encoded traversal, NUL bytes, and
absolute paths are rejected before fopen, and continue on rejection so
one bad entry cannot abort the import.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Closing this in favor of the atomic PR pattern that's been working.

Why

This PR bundled 44 files across ~15 separate security intents (auth, XSS, XXE, SQL, SSRF, path traversal, RCE coverage, CI tooling). Two things changed since it was filed:

  1. The 1.2.x side of this work is now consolidated in feat(security): architectural security helpers — eliminate vulnerability classes at root #7054 (nine architectural helpers). Where they overlap, the 1.2.x version is the authoritative one.
  2. The atomic-PR approach, like feat(security): add cacti_dispatch helper for action table authorisation #7063 (cacti_dispatch alone), is proving far easier to review. fix(security): consolidated develop-port hardening batch #7036's size has been the main reason it hasn't moved.

Re-filing plan

Rather than force-split this 44-file branch into 15 half-stale PRs, I'll re-file items individually when there's demand, scoped tight like #7063:

The branch stays on my fork at fix/security-hardening-batch; nothing is lost.

If there's a specific piece from the file list you want re-filed now, point at it and it'll come back as its own PR.

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.

3 participants