* fix(ci): pin htmx.org back to 2.0.10 to match vendored asset and SRI
The npm group bump #8085 raised htmx.org to 4.0.0, but the vendored
include/js/htmx.js, the HTMX_2_0_10_SRI integrity constant, and the
htmx.js.version file were not regenerated. CI's 'Build managed JavaScript'
step (build/sync-js.mjs) copies node_modules/htmx.org/dist/htmx.js, so it
shipped 4.0.0 while htmx_script_tag() still emitted the 2.0.10 SRI, failing
tests/Unit/Ui/Html/HtmxTest. Revert htmx.org to the vendored/pinned 2.0.10;
a real htmx 4.x upgrade should be a separate, tested PR.
* fix(htmx): sync version file, tests and docs to vendored 2.0.10
* fix: Restore base javascript files for testing
* fix: screenfull 6.0.2 is missing screenfull.js
* fix: Restoring screenful after discovering how the files are managed
* fix: Remove node modules directory after installing modules
* fix: Change in screenfull.js behavior affects cacti
* fix: Restore jQuery 4 compatibility in the tablesorter pager plugin
$.isArray()/$.trim() were removed in jQuery 4.0, which this PR pins in
package.json. The restored jquery.tablesorter.pager.js still called
E.isArray(...)/E.trim(...), so any Ajax pager response threw before
rendering. Also fix a TypeError in the countChildRows branch, which
indexed into `a` while it was still an undeclared var.
Add patches/tablesorter+2.32.0.patch so the fix survives the next
`npm run build:js` sync from node_modules, following the existing
pace-js patch-package precedent.
* fix: Rewrite screenfull's ESM export during sync-js instead of downgrading
screenfull 6+ ships ESM-only (`export default screenfull;` in
node_modules/screenfull/index.js), which throws "export declarations
may only appear at top level of a module" when the copied file is
loaded as a classic <script>.
Add a post-copy transform step to build/sync-js.mjs that rewrites that
line to `window.screenfull = screenfull;` for include/js/screenfull.js,
so the 6.0.2 pin in package.json can be kept instead of reverting to
5.2.0. Throws if the expected export line isn't found, so a future
screenfull release changing its export shape fails the build loudly
rather than silently shipping a broken asset.
Updates tests/js/sync_js.test.mjs to cover the transform and its
failure mode.
* build: add symfony/security-csrf
Backs the CSRF token core that replaces the vendored csrf-magic fork.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: fix security-csrf package path assertion
symfony/security-csrf ships CsrfTokenManager.php at the package root, not under src/.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* refactor: register the composer autoloader before the CSRF include
CSRF validation ran before the autoloader existed, which blocks any class-based guard.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* feat(csrf): add CactiCsrfGuard over symfony/security-csrf
Issues and validates session-scoped tokens; not yet wired into the request path.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* feat(csrf): accept pre-upgrade csrf-magic tokens during the grace window
Legacy tokens carry their own expiry, so the fallback closes itself without a config flag.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* feat(csrf): port the output buffer rewriter to CactiCsrfGuard
Keeps the hidden field name and JS globals that third-party plugins rely on.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): use json_encode for the JS script context in rewriteBuffer
htmlspecialchars() protects the HTML attribute context but not the
<script> string literal, which relied on Symfony's token generator
never emitting a quote rather than being safe by construction.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* refactor(csrf): move the XHR shim to include/js as Cacti-owned code
It has been maintained by the Cacti Group since 2020 and is not an upstream package.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* refactor(csrf): retire the vendored csrf-magic fork
The procedural csrf_* surface stays as a deprecated shim so plugins keep working.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* feat(csrf): validate requests through CactiCsrfGuard
csrf-magic self-invoked from its own file; the guard is called explicitly.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(install): stop gating the permission step on the csrf secret
Retiring csrf-magic removed the directory its default path lives in, and setStep() clamps to the first errored step, so no fresh install could advance.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): key the legacy fallback on the raw secret bytes
refresh_csrf.php writes PHP source with a trailing newline, and trimming that yields a different HMAC key than csrf-magic used.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): log when the guard degrades to disabled in a web context
The installer is the only web path that can reach it, and failing open in silence is not something csrf-magic could do.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: changelog for the CSRF library replacement
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): mint the token before the session write callback closes it
csrf_startup() minted lazily from the output buffer callback, which runs after session_write_close()'s shutdown-function registration, so a token minted there on a legacy-fallback POST was silently dropped.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): make refresh_csrf.php a no-op
Its default secret path no longer ships and live tokens are session-bound, not HMACs over the secret, so rotation neither worked nor did anything meaningful; running it during the upgrade grace window used to delete the legacy secret and kill validation for pre-upgrade tokens outright.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): fall back to the old default secret path when reading it
csrf-magic tried its own directory as a second candidate regardless of $path_csrf_secret; the new reader only tried the configured path, so a site pointing $path_csrf_secret at a file that was never created lost the legacy-token grace window entirely.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs(csrf): point the JS header at its BSD license sidecar
The header reduces the original BSD terms to prose and the actual text sits in an unreferenced csrf.js.LICENSE, so add a pointer to it.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): restore csrf_check()'s bool return and $fatal contract
The shim ignored $fatal and always returned void, so audit/audit.php's `!csrf_check(false)` evaluated `!null` (always true) and denied every request instead of getting a real pass/fail result back.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: reference the pull request number in the changelog entry
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: resolve includes through Cacti path constants
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(csrf): enforce full coverage
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Harden CSRF fallback handling
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(csrf): assert the component stays out of the index
Committing it would leave a second copy to drift against the lock.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: align CSRF checks with PHP 8.2
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: normalize the CSRF changelog entry
* Fix CHANGELOG entry ordering
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(csrf): support constrained test environments
* fix(csrf): narrow nullable guard state
* test: place CSRF coverage in security domain
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* security(test): match disabled exec exactly
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: run CSRF coverage on PHP 8.3
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* chore: replace removed jQuery $.isFunction() with typeof checks
jQuery 4.0 removes $.isFunction(). Swap the three call sites in navigationBox.js
and navigationBox.tree.js for behaviour-identical 'typeof x === function' checks,
removing the only jQuery-4-removed API in Cacti's own front-end code.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: correct typeof negation in the mark.js availability check
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* chore: manage front-end JavaScript libraries with npm and Dependabot
Move all third-party front-end JavaScript to npm so Dependabot tracks it, and
drop the committed copies in favour of a build. package.json pins jquery,
jquery-ui, jstree (3.3.12 -> 3.3.17), billboard.js, pace-js, tablesorter and
jquery-validation; build/sync-js.mjs copies their dist files into include/js/
and runs on npm install via postinstall.
pace-js carries a one-line local fix (a stray regex alternation that made it
match the empty string) applied through patch-package
(patches/pace-js+1.2.4.patch); the fix should be sent upstream so the patch can
be dropped. Only Cacti's own themed jquery-ui/jstree CSS stays committed.
An npm ecosystem is added to dependabot.yml for develop. The release pipeline
and any workflow that serves Cacti pages must run 'npm ci' (which builds via
postinstall) before use, since the generated files are no longer in the tree.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* hardening: remove vendored development artifacts
* feat(release): attach the built artifact to the release
The workflow already builds an attested, checksummed archive carrying the
Composer dependencies, then discards it after thirty days.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: fail the publish step when the bundle is empty
An unmatched glob would otherwise reach gh as a literal path.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: treat the maintenance lock notice as expected log output
The line records the lock working, so a second maintenance run overlapping
in CI failed unrelated pull requests.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: consolidate release dependency changelog
* ci: build managed JavaScript in release artifacts
* test: cover managed JavaScript build end to end
* deps: complete phpseclib 4 migration
* deps: update patch-package to audited release
* ci: satisfy workflow shell policy
* chore: source DOMPurify from npm and update it to 3.4.14
Add dompurify to the npm-managed set (was committed at 3.4.7) and bump to 3.4.14.
DOMPurify is the client-side XSS sanitizer used on tree labels, device tooltips
and plugin messages; keeping it current with upstream bypass fixes matters more
than for the UI libraries.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: preserve release changelog ordering
* test: preserve DOMPurify source map in npm build
* chore: source htmx, d3, big.js, lzjs and screenfull from npm
Add the remaining unmodified third-party libraries to the npm-managed build:
htmx.org 2.0.6, d3 7.8.2, big.js 6.2.2, lzjs 1.3.1 and screenfull 3.3.3. Each
was verified byte-identical (lzjs differs only in a JSDoc comment) to the copies
being removed. htmx in particular moves from a hand-tracked SHA pin to npm with
lockfile integrity.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: cover phpseclib 4 integration
* ci: build managed assets before htmx tests
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* 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>
* fix(security): align develop with advisory hardening
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(security): harden remote agent auth compatibility
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(security): reorder remote auth checks
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(security): harden remote_agent auth order and query scope
Move whitelist check before the single-poller guard so single-poller
installs relying on the whitelist are not incorrectly rejected.
Narrow SELECT * to SELECT hostname; the loop only reads hostname.
Drop function_exists guards around md5/hash; both are PHP core and the
raw $cache_seed fallback is unsafe as an APCu key.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(theme): cast read_config_option result to string in cacti_validate_theme
read_config_option can return false or null when the setting is absent.
Without the cast, a strict comparison to '' would not match, leaving
$default unset and risking a null-typed value flowing into callers.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(reports): skip invalid IDs in bulk action without aborting the loop
Replace input_validate_input_number (which calls die_html_input_error
on bad input, aborting the entire bulk action) with an inline guard.
Cast $report_id to int at call sites where the signature requires int.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* feat(security): comprehensive UI hardening and 7.4 normalization
- Refactored poller_spikekill.php to use array-based execution and sanitized SQL IN clauses.
- Refactored script_server.php Windows process check to use cacti_exec.
- Hardened snmpagent background process lifecycles.
- Standardized version checks in support.php and utilities.php.
- Replaced short array syntax [] with array() and verified 7.4 compatibility.
- Synchronized all hardened files to primary developer path.
* fix(snmpagent): replace undefined cacti_process_execute() with exec_background()
cacti_process_execute() was called but never defined, causing a PHP fatal.
Replaced with exec_background() from lib/poller.php. Also fix pre-existing
parse error in snmpagent_persist.php ([ closed with ) instead of ]).
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style: convert new code to short array syntax
php-cs-fixer (array_syntax/list_syntax short) flagged array() introduced
on this branch; convert to [] so the coding-standards gate passes.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* revert off-topic exec changes to match develop
poller_spikekill, script_server, snmpagent_persist/mibcache and support
had been switched to 1.2.x-style cacti_exec(), which is undefined on
develop and failed phpstan. Restore develop's native exec() idiom; these
files are outside this PR's scope (report actions and theme handling).
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* build: pin phpstan to 2.2.1
phpstan 2.2.2 added a stricter is_numeric() always-true inference that
fails on pre-existing develop code (lib/data_query.php); pin until that
is addressed upstream so CI is reproducible.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(reports): deserialize type-prefixed bulk items so actions run
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: ignore phpstan function.alreadyNarrowedType identifier
Clears the level 6 alreadyNarrowedType error on lib/data_query.php:2591 that
develop currently emits, matching the sibling narrowing identifiers already ignored.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: add missing PCRE delimiters to graph_template_id regexp filter
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(reports): parse the confirm list identically to the action branch
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* refactor(remote_agent): drop the APCu auth cache (uncertain dependency, cross-request only)
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Enhance security checks for remote agent access
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* chore(assets): update jQuery UI and theme assets
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(codex): restore jQuery UI compatibility and fix PHPStan issues
* fix(midwinter): restore full-width multiselect button rules dropped in jquery-ui rebuild
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: ignore phpstan function.alreadyNarrowedType identifier
Clears the level 6 alreadyNarrowedType error on lib/data_query.php:2591 that
develop currently emits, matching the sibling narrowing identifiers already ignored.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* support dynamic resizable rrdgraph
* introducing the newest version of navigationBox including plugin support. This allows reusable navigation Boxes e.g. different trees and so on.
* Update billboard.css to version 3.14.3
Update billboard.css to version 3.14.3
* Update billboard.js to version 3.14.3
Update billboard.js to version 3.14.3
* Update CHANGELOG
billboard.js and billboard.css to be updated to version 3.14.3 #6165
* Upgrade the library to get latest support
* Move cdef.php and vdef.php to form_selectable_cell calls.
* Make rows selectable for Drag and Drop items for clarity.
* Make consistent "Item#%d" vs. "Item # %d"
- feature: introduce tooltip orientation by class.
- fix: in quick mode zoom stopped while moving the mouse quickly into the opposite direction.
- fix: suppress livedata flickering while drawing the zoom areas.
* Fixing #2572 - Ordering Graphs by Data Source
* Support Sorting of Tree or Graph View by Data Source
* This feature is not yet complete. The API modifications for ordering are not yet complete.
* This first commit covers much of the initial front end changes.
* Proposed API changes
* First working prototype of Graph Ordering
* Fixing bugs and applying to tree
* Updating the ChangeLog for #2572
* QA: Fix GitHub Action - Pre-checks
- add new raw data attribute to graph image container
- add lzjs compression library
- update rrdtool graph option by --add-jsontime for version 1.8.0 or above.
* This should resolve a long standing issue using toast in Cacti
* Some plugins that still call "$.get" or "$.post" directly, will still have to be updated.
* Also reverting jquery.toast.js to the stock version.