8 Commits
Author SHA1 Message Date
Thomas Vincent af9d1591a3 fix(aggregate): honor skipped item sequences in totals and percentiles (#8300)
* fix(aggregate): honor skipped item sequences in totals and percentiles

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

* test: tighten SQL baseline for aggregate prepared queries

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

* docs(changelog): defer release entries to final backlog PR

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-11 21:27:25 -04:00
Thomas VincentandTheWitness 3bb7002d2d fix(plugins): report CLI install failures and apply requested realm grants (#8296)
* fix(plugins): grant existing realms for CLI allperms

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

* ci(plugins): pin database images and enforce strict shell mode

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

* fix(plugins): stop automatic grants after CLI setup failures

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

* docs(changelog): defer release entries to final backlog PR

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-10-11 21:23:27 -04:00
Thomas Vincent 964c1fb1f4 Harden RRDtool execution and data handoff (#7615)
* fix(rrdtool): quote RRDfile paths passed to the command line

__rrd_execute() hands the assembled line to shell_exec(), and its docblock
already required callers to quote each variable argument.

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

* fix(tests): widen bare RRD path sweep

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

* fix(rrdtool): quote binary path as one argument

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

* fix(rrd): preserve caller logging context

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

* Make RRD XML mutations transactional

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

* Harden RRDtool proxy transport handling

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

* fix(rrdproxy): reject invalid command argument lists

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

* fix(rrdproxy): preserve binary graph payloads

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

* Keep RRDtool proxy sessions encrypted

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

* Harden local RRDtool command and output handoff

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

* Eliminate graph rendering metadata lookups

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

* Batch DSStats metadata and last-value reads

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

* Batch interface speed metadata reads

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

* Update architectural hotspot baseline for RRD execution

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

* Batch RRD resolution profile lookups

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

* Batch DSStats summary aggregates

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

* Narrow phpseclib key types for the proxy encrypt and decrypt calls

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

* test(rrd): bound the forked proxy peers so a stall cannot hang the suite

The children accepted and read with no deadline while the parent used a 2s
socket timeout, so any mismatch left the child blocked in read and the parent
blocked in a bare pcntl_waitpid(). Note that accept() ignores SO_RCVTIMEO, so
readiness has to be waited on with socket_select() rather than a socket option.
Also corrects the array-command expectation, which still assumed develop's
escape-everything behaviour rather than rrdtool_build_command() leaving the
operation unquoted.

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

* test(rrd): seed proxy settings where the real read_config_option looks

UnitStubs only declares its stub when lib/functions.php has not been loaded
yet, so during a full run the real database-backed function is the one in
effect and $unit_config_options was being ignored. The parent then dialled an
address the child was not listening on, which is what stalled the handshake.
Seeding the option cache keeps the settings visible to either implementation.

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

* test(rrd): assert the shell-free RRDtool launch instead of the old quoting

__rrd_init now hands proc_open an argv array with bypass_shell, so escaping the
binary path would put the quotes inside the filename. The assertion still
expected the pre-argv line and had been failing unnoticed behind the suite hang.

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

* test(dsstats): derive the expected stamps instead of assuming UTC

dsstats_poller_output() formats with date(), so the hardcoded UTC strings only
matched on a UTC runner. The failure was masked by the suite hang.

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

* fix(rrd): keep RRDtool control codes intact in stdin commands

Escaping the backslash when converting a shell-quoted argument to an RRDtool
token left legends such as "  IO Wait\:" ending in a dangling control code, so
RRDtool refused every graph carrying an escaped colon. Verified against RRDtool
1.7.2 in pipe mode; values with no unambiguous quoted form are now refused
rather than mangled.

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

* test: use Cacti test bootstrap and config

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

* test: resolve includes through Cacti path constants

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

* test: match the escaping helpers by token, not by text

lib/rrd.php documents its escaping behaviour in a comment, and the regex
over raw source counted that as a call site.

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

* ci: refresh sink inventory after develop rebase

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

* test(rrd): prove argv isolation and concurrent pipe draining

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

* test(security): refresh sink inventory baseline after rebase onto develop

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-31 16:49:44 -04:00
Sean Mancini 96033267cb refactor(tests): reorganize unit tests into domain category folders (#7733)
* refactor(tests): reorganize unit tests into domain category folders

- Consolidate per-advisory GHSA regression tests into Security/ subfolders by vuln class
- Group all 186 test files into domain folders: DataCollection/, Ui/, Core/,
  Database/, Installer/, Scripts/, Security/, Plugin/
- Strip GitHub issue/GHSA numbers from filenames; keep IDs in test descriptions
- Fix all __DIR__ relative paths to match new folder nesting depth
- Update CI workflow path triggers and phpunit XML configs for new file locations
- Update .github/copilot-instructions.md with unit test organization guidelines
- Add tests/vendor/ to .gitignore

* add gaurds

* fix up guard for unit tests

* fix redeclaration of cacti_log in tests

* fix updates to unit tests

* fix test patjhs

* fix: update test __DIR__ paths for reorganized test structure

* fix: conditionally define read_user_setting to prevent redeclaration in tests

* fix: conditionally define functions to prevent redeclaration in tests

- Wrap clean_up_lines, raise_message, cacti_debug_backtrace, and get_debug_prefix
  with function_exists() checks to prevent conflicts when test stubs are loaded
  before lib/functions.php in the same PHP process

* fix: add unit test bootstrap to prevent function redeclaration errors

- Create tests/bootstrap-unit.php to load core libraries before tests
- Ensures lib/functions.php loads before test stubs define stub functions
- Test stubs use function_exists() checks, so they detect pre-loaded functions
- Update phpunit.xml to use bootstrap-unit.php for Unit testsuite
- Fixes 'Cannot redeclare read_user_setting()' errors when tests run in same process

* fix: load test stubs before global.php in unit test bootstrap

- Reorder bootstrap to load CactiStubs.php and UnitStubs.php first
- Ensures stub functions like __() are available when global.php loads
- global.php calls get_cacti_version_text() which needs __() for translations
- Fixes 'Call to undefined function __()' error during bootstrap

* fix: remove function conflicts from test stubs

- Remove stub definitions for functions already in lib/functions.php:
  cacti_sizeof, cacti_log, cacti_strtolower, read_config_option,
  read_user_setting, cacti_count
- Keep translation function stubs (__(), __esc()) needed by global.php
- Update bootstrap to load lib/functions.php after stubs
- This prevents function redeclaration errors while maintaining test isolation

* fix: prevent constant and variable redeclaration in bootstrap

- Remove explicit PHP_TESTING and CACTI_TEST_BOOTSTRAP defines from bootstrap
  (CactiStubs.php now handles this with guards)
- Add guards to CactiStubs.php to prevent redefinition of PHP_TESTING
- Only set CACTI_TEST_BOOTSTRAP env vars if not already set
- Fixes PHP warnings about 'Constant X already defined'

* fix: remove redundant lib/functions.php load from bootstrap

include/global.php already loads lib/functions.php at lines 287-288.
The bootstrap was trying to load it again after global.php, but global.php
needs format_cacti_version() to be available on lines 293-294 to define
CACTI_VERSION and CACTI_VERSION_FULL constants.

Solution: Let global.php handle loading lib/functions.php itself, and don't
load it separately in the bootstrap.

* fix: remove more conflicting function stubs from test helpers

- Remove stubs for functions also defined in lib/functions.php:
  raise_message, clean_up_lines, cacti_debug_backtrace, get_debug_prefix
- Remove POLLER_ID constant stub (defined unconditionally by global.php)
- Keep only translation functions and test-specific helpers
- This prevents function/constant redeclaration errors

* fix: minimize test stubs to only translation functions

Remove ALL helper functions and constants from UnitStubs.php that are:
- Defined in lib/functions.php (raise_message, clean_up_lines, etc.)
- Defined in lib/html_utility.php (srv, set_request_var)
- Defined in global_constants.php (POLLER_VERBOSITY_*, MESSAGE_LEVEL_*)
- Defined in global.php (POLLER_ID)

Keep ONLY translation functions (__(), __esc()) that are:
- Called by global.php during initialization
- Not defined elsewhere
- Required for proper bootstrap sequence

This eliminates all constant and function redeclaration errors.

* fix: remove __esc stub - already defined in global_languages.php

Keep only __() translation function stub, which is needed by global.php
and not defined elsewhere in Cacti core.

* fix: handle no-arg case in __() translation stub

The __() function may be called with just a string and no format arguments.
Previously, vsprintf() would fail with 'arguments array must contain 2 items'.
Now, if no args are provided, return the text as-is.
2026-08-16 19:23:31 -07:00
Thomas Vincent d4e255b5d9 security: stage nonce CSP migration and gated enforcement (#7662)
* security: add nonce CSP mode for inline JavaScript protection

Reworks the content_security_policy_script setting into a three-mode Inline JavaScript Protection dropdown (HTMLX default, Nonce, None) and ports the per-request nonce, strict-dynamic policy, and CSP violation report endpoint from 1.2.x. The stored option key and its default are unchanged, so existing installs keep their current policy byte-for-byte; nonce mode is opt-in.

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

* security: stage nonce CSP enforcement safely

Add report-only migration, gated enforcement, nonce-aware shared emitters, Linux Docker validation, and operator documentation.\n\nRefs #7703

* security: validate CSP header sources

Reject malformed alternate-source configurations at the CSP boundary, validate fallback report URIs, and remove the obsolete Psalm header-taint baseline entry.\n\nRefs #7705

* chore: drop the composer.json change from this PR

Dependency and manifest changes are a maintainer call and do not belong in
this one.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-16 21:38:46 -04:00
Thomas VincentandTheWitness a75bf823bb feat: consolidate htmx loader and renderer abstraction global.php includes (#7517)
* feat(rendering): add view renderer abstraction

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

* isolate renderer traversal test file

* Standardize renderer headers and endpoint guard

* style(renderer): use standard unit-test header in CactiRendererTest

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

* fix(renderer): reject null-byte paths before realpath

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

* ci: refresh sink and hotspot baselines for line shifts

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

* fix(renderer): guard root base path and template-dir redirect

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

* fix(renderer): canonicalize both paths in index.php direct-access guard

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

* hardening: normalize renderer base path to one separator

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

* fix(renderer): reject filesystem root as template path

A /-rooted renderer collapsed the containment prefix to /, which
str_starts_with() matches for every absolute path, so any file was
includable. Fail closed by refusing a root template path.

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

* hardening: reject windows drive-root template path

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

* refactor: adopt PHP 8.4 idioms in view renderer

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

* test: cover renderer abstraction to full coverage

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

* hardening(renderer): reject UNC template names and fix sink baselines

Refuse a leading-backslash (UNC) template name the same way absolute
POSIX paths are refused, skip the direct-request redirect when
SCRIPT_FILENAME carries a null byte, and correct the renderer include
and views redirect line numbers in the security baselines.

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

* style: apply php-cs-fixer

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

* fix(renderer): use PHP 8.1-compatible readonly property in CactiRenderer

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

* hardening(rendering): drop template context array before include

Unset the internal context array after extract so an included template
only sees the extracted variables, not the full context.

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

* ci: refresh security baselines for renderer line shift

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

* fix(renderer): simplify containment check and drain nested output buffers

The redundant real_path !== template_path comparison in
validateTemplateFile() can never be false once is_file()/is_dir() have
run, so drop it and keep the str_starts_with() prefix check as the
actual containment guard. renderFile() also only unwound a single
output buffer, so a template that opens (and leaves) its own buffer
could leak an ob level or drop its content; unwind every buffer opened
during rendering and reassemble them in the order they were written.

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

* ci: refresh security baselines for renderer buffer/containment fix

The dynamic_include line for lib/renderer.php's include shifted again
after adding drainOutputBuffers().

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

* feat(htmx): vendor htmx 2.0.6 and add opt-in filter loader

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

* test: cover htmx filter loader

Add missing-vendored-file case for htmx_script_tag and mark the pre-bootstrap read_config_option guard as uncoverable.

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

* test: harden htmx loader harness and tighten enable check

Skip anything but an exact 'on' for htmx_enabled, reject 4xx/5xx fixture
responses, fail fast when the fixture base path is unresolved, pin
setup-php to a commit SHA, and reindent the e2e specs to tabs.

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

* style: apply php-cs-fixer to htmx test files

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

* test(htmx): drop duplicate script-tag src assertion

The subdir-install test repeated the preceding assertion; CACTI_PATH_URL is
fixed at bootstrap so it could not exercise a distinct scenario.

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

* fix(e2e): drop sqlite3 from docker-php-ext-install

php:8.2-apache already ships sqlite3 built in, and its ext/sqlite3
source only carries config0.m4 (not config.m4), so building it
standalone always fails with "Cannot find config.m4", breaking the
e2e image build before Playwright ever runs.

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

* test(htmx): clear stat cache around version-file rename

Earlier tests already stat the version file via file_exists()/
file_get_contents(), so PHP's stat cache can report it present right
after these tests rename it away, making the assertions flaky.

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

* fix(htmx): recompute script-tag cache-buster per call

The md5 cache-buster was cached in a static, so FPM/script_server
processes kept serving a stale ?v=... hash after an htmx.min.js
upgrade. Recompute it per call, matching get_md5_include_js() and
get_theme_paths() in lib/functions.php, and drop the 'v=' parameter
name to match their path + '?' + hash shape.

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

* test(e2e): drop obsolete MariaDB options

* ci(htmx): run the htmx integration test by path instead of a testsuite filter

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

* build: vendor htmx unminified

Debian rejects minified sources. The file is the htmx 2.0.6 dist source; its
minified sibling hashes identically to what was committed, and the pinned SRI
is recomputed over the unminified file.

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

* fix: address htmx review feedback

* test(e2e): resolve Cacti version through PHP

* test(htmx): harden portable server cleanup

* ci(actions): update checkout action pins

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-08-10 11:45:24 -04:00
Thomas Vincent 906d19ca72 fix(reports): consolidate image conversion and notification payload hardening (#7515) 2026-08-06 17:37:25 -07:00
Thomas Vincent cbd711869c test: run the Integration and HandOff suites in isolated processes (#7550) 2026-08-04 18:55:37 -07:00