* test(cdef): enforce resolver coverage
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci(cdef): pin cache action correctly
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: isolate CDEF coverage bootstrap
* fix(cdef): fail closed on invalid recursive definitions
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): log corrupt resolver records
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): bound and isolate recursive resolution
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): preserve resolver failure state
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): isolate invalid graph expressions
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): abort unsafe consumer operations
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): close consumer failure paths
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): make aggregate updates atomic
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): keep repair queries auditable
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): bind bulk deletes and escape item types
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cdef): guard corrupt types and repair reporting
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(aggregate): propagate push-out failures
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(aggregate): document mutable message checks
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* 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>
* fix(auth): reject malformed remember cookies
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: load cookie contracts lazily
* test(cookie): fail clearly when contract markers move
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(auth): align persistent credential lifecycle
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(auth): find cookie function boundaries robustly
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* hardening: validate the login realm and escape the LDAP username per context
domains_login_process() now rejects a realm get_auth_realms() does not list and
fails closed on one that is listed but has no domain behind it, and the Ldap
class escapes the username for the DN and the search filter separately rather
than relying on a filter-only blocklist.
- an unlisted realm sent with a non-empty password used to match no branch at
all and returned an empty user with no error, which auth_login.php reads as
an authenticated user and provisions from the template account
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* security: reject LDAP logins that skip a domain bind
Posted realms outside user_domains no longer fall through to template or guest login.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(ldap): align escaping contract with username lookup
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: cover LDAP domain login through OpenLDAP
The e2e stack now binds a real directory user and checks that a forged realm cannot skip that bind.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(ldap): address Copilot review feedback
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: substitute LDAP filter placeholders in one pass
LDAP_ESCAPE_FILTER leaves angle brackets intact, so a looped str_replace can rescan one value as another key.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Change realm retrieval function from gnrv to gfrv
* tests: add gfrv() stub to harnesses that exercise gfrv()-calling code
domains_login_process() and api_aggregate_convert_template() call gfrv() for
realm/aggregate_template_id, but their test harnesses only stubbed gnrv(),
so Pest failed with 'Call to undefined function gfrv()'.
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix(csv): align export and graph sample windows
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci(csv): satisfy release policy checks
* test(csv): align legacy capacity contract
* test(csv): add standard license headers
* fix(rrd): infer step from trailing fetch row
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci(csv): trigger on resolution query coverage
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): mirror fetch helper arguments
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): isolate percentile globals
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): annotate intentional eval
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): restore integration config
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): enable strict types
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(rrd): restore font config
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: align CSV coverage with supported PHP versions
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix(csrf): require POST tokens for item actions
* test(csrf): cover every guarded action path
* test(csrf): keep legacy guard contract extensible
* fix(csrf): hide POST-only targets from crawlers
* test(csrf): reject every crawlable state-action link
* fix(csrf): fail closed without logging crawler probes
* fix(csrf): post data-query item actions safely
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): normalize stripped token separators
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): distinguish method and token failures
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): escape POST action URL attributes
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): delegate POST action events
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(csrf): post data template add action
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style(html): align POST button class
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* feat(cli): add unified command frontend and normalize legacy failures
Add the Symfony-backed bin/cacti entry point while preserving legacy argument, terminal, signal, and exit-status behavior. Fold in the focused legacy CLI correctness fixes from #7906 without adding its unused parser abstraction.
Keep path containment and the PHP 8.3 floor out of this review; the latter remains in #7939.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(cli): enforce PHP floor and deny web dispatch
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* build: raise the 1.3 floor to PHP 8.3
RHEL 10 ships 8.3 in AppStream and dropped modularity, so an 8.4 floor would
put every RHEL, Alma and Rocky 10 install on Remi. Ubuntu 24.04 is 8.3 as
well, which makes it the lowest common denominator of the current LTS fleet.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: raise the documented PHP floor to 8.3
The 1.2.x column keeps 8.1, which still matches that branch's composer constraint.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: run process lock coverage on PHP 8.3
* fix: finish PHP 8.3 floor cleanup
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
process-only was empty, so lock-threads walked discussions as well, and the
job has no discussions permission. The exempt lists also matched only the
flat label names, which would have exposed 238 open enhancements to the
stale closer once those labels are retired.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Serialize process registry admission with Symfony Lock
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Fix PDO process lock mode handling
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: run process lock coverage on PHP 8.2
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: confirm a pid is still ours before signalling it
#7574 added the still-running guard to dsstats but left six byte-identical
siblings signalling a pid straight out of the processes table. After a crash or
a power loss the row survives, the pid is recycled by an unrelated daemon, and
the next start SIGTERMs a stranger. That is issue #7425, still live in rrdcheck,
boost, poller_commands, float_rrdfiles, poller_reindex_hosts and
rebuild_poller_cache.
Wrap each kill in cacti_process_still_running(), matching the dsstats shape, and
leave unregister_process() outside the guard so a stale row is still cleaned up.
Cover all seven with a test that walks every kill site, so a new variant cannot
reintroduce it.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: assert the guard plainly
toContain() takes needles rather than a failure message, so the file name was
being asserted as a second required substring and the test always failed.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs(poller): describe required lock parameters
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: share one body across the per-script coverage workflows
Eight of the coverage workflows were the same file with four things changed:
the PHPUnit configuration, the job name, occasionally the PHP version, and
occasionally the snmp extension. Everything else, the triggers, permissions,
concurrency, checkout, dependency setup and the Pest invocation, was copied
between them, so a change to any of it meant eight edits and eight chances to
miss one.
They now call a reusable workflow and supply only what differs. Each caller
keeps its own path filters and concurrency group, since those are genuinely
per-script.
installer, snmp and rrd-graph-item are left alone. They run more than one
suite or produce clover output, so folding them in would mean parameterising
the shared body until it stopped being shared.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: rebuild the callers when the shared coverage body changes
Each caller watched its own file but not the reusable one, so editing the
shared body would not have run any of the suites it drives.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
There was no root CONTRIBUTING.md and no way to run the CI gates locally in one
step, so reproducing a CI failure meant reading workflow YAML. Add a
'composer check' target that chains the gates, expose the sink and hotspot
guards, the changelog validator and the JavaScript tests as their own targets,
and document setup and the changelog.d fragment workflow.
Pin Node in .nvmrc to the version CI uses, and move the one remaining
setup-node still on 20 to 22 so every workflow agrees.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
The recursive wget crawl is the single most expensive thing in CI. Measured over
the last 120 runs, the Commit Audit workflow is half of all CI minutes, the
locked integration job is 72 percent of that workflow, and this one step is 60
percent of that job: 422 seconds of a 16 minute run.
It walks the authenticated UI one request at a time, so nothing it covers can
change unless server-rendered code, assets or the schema did. Skip it when a
pull request touches only tests, workflows, translations or documentation.
The step is gated rather than the job, so the required-check aggregator still
sees a real result rather than a skip. The decision fails open: an unreadable or
empty diff, or a push rather than a pull request, runs the crawl.
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>
* issue#7355: add a vendor-neutral ENTITY-SENSOR-MIB script data query
ENTITY-SENSOR-MIB (RFC 3433) exposes physical sensors (temperature, fan, PSU,
voltage, current) on most modern gear, so one data query covers F5, Juniper,
Arista, Cisco NX-OS and others. ss_entity_sensor collects the sensor table and
scales each reading through entPhySensorScale and entPhySensorPrecision; the
SNMP I/O sits behind an injectable walker so every logic branch is unit tested.
A dedicated coverage job enforces 100% line coverage on the collector.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* issue#7355: add a JUNIPER-MIB jnxOperatingTable script data query
Junos routers and switches expose per-subject health (Routing Engine, FPC, PSU,
fan) through JUNIPER-MIB jnxOperatingTable, which is the signature Juniper data
beyond the IF-MIB and HOST-RESOURCES standards. ss_juniper_operating collects
CPU, temperature, buffer and state; the multi-index table is keyed on the full
OID suffix. SNMP I/O sits behind an injectable walker so every branch is unit
tested, and a dedicated job enforces 100% line coverage.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* issue#7355: add an F5-BIGIP-SYSTEM-MIB per-CPU utilization script data query
BIG-IP reports per-CPU utilization as sliding 5s/1m/5m averages in
sysMultiHostCpuTable, the signature F5 metric beyond the IF-MIB and
HOST-RESOURCES standards. ss_f5_cpu collects that multi-indexed table, keyed on
the full OID suffix. Every OID was resolved from the published
F5-BIGIP-SYSTEM-MIB with snmptranslate. SNMP I/O sits behind an injectable
walker so every branch is unit tested, and a dedicated job enforces 100% line
coverage.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style: apply php-cs-fixer to ss_entity_sensor.php
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Use Symfony Finder for MD5 traversal
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* chore: add the CHANGELOG entry
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(md5sum): reject a bad ignore pattern instead of ignoring every file
preg_match() returns false on a bad pattern and false is not 0, so the old
test skipped the whole tree and wrote an empty manifest.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style: use a line comment to satisfy php-cs-fixer
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: resolve includes through Cacti path constants
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(md5): enforce full finder coverage
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Move Finder import before CLI bootstrap
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* build: lock Symfony Finder runtime dependency
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* build: leave Composer autoload metadata generated
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: run Finder coverage on PHP 8.2
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: widen the symfony/finder constraint for PHP 8.5
Pinning ^6.4 left the dev dependency graph unsatisfiable on PHP 8.5 in the native-dependencies job.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Make installer completion fail closed
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Add installer unit and Docker integration gates
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Require installer coverage evidence
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Stabilize installer failure scenarios
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Address installer review feedback
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Reject incomplete installer selection payloads
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Match table validation to rendered controls
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Protect long-running installer migrations
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Load installer table metadata in one query
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Optimize installer completion queries
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch installer default settings writes
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch installer color upserts
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Batch installer template and poller lookups
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Refresh SQL interpolation baseline after develop rebase
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: use Cacti Composer test setup
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: resolve includes through Cacti path constants
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: align installer workflows with policy
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: refresh security baselines after develop rebase
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci: reuse TCP database credentials for installer checks
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>
* ci: run installer coverage on PHP 8.2
The 8.1 pin is below the ^8.2 composer requirement, so pest died in platform_check before any test ran.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
PHP 8.1 left security support in December 2025 and Symfony 7.4 requires 8.2,
so the platform floor has to move before any component can advance.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* chore: resolve concurrent CHANGELOG appends with a union merge
Every PR lands on the same two anchors, so merging one reliably conflicts
the others on CHANGELOG alone.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
(cherry picked from commit 6bc8e63ecb)
* feat(changelog): take entries as fragment files instead of CHANGELOG edits
Every pull request edited one of two lines, so merging one conflicted the
rest. A file per change gives concurrent PRs nothing to collide over.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
(cherry picked from commit a8a0bfa29a)
* chore(security): drop line numbers from the tracked sink baselines
Both verifiers already strip the line before comparing, so the tracked
numbers carried no signal and rewrote 212 lines of baseline on any edit that
shifted code, conflicting every open pull request.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
(cherry picked from commit ccfd3d374b)
* fix(ci): rebase stale pull request branches instead of merging them
A merge update adds a commit per run, so branches accumulate snapshots of
old develop and re-conflict on every shared file they carry.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
(cherry picked from commit e9e33c77cd)
* test: match the escaping call rather than the exact source line
The literal assertions here required the uncast form while
PhpBinaryPathNullCoercionTest required the cast, so the two could not both
pass and whichever landed second broke the other.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
(cherry picked from commit 98cfc84756)
* fix(changelog): handle security entries in fragments and validation
Review caught the missing type. Security entries also take GHSA identifiers
and open a release section ahead of issue and feature entries, none of which
the first pass handled.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: require Composer-managed dependencies
Prevent agents from committing third-party package trees and require composer.json plus composer.lock updates instead.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(tests): correct path depths after the domain reorganisation
The category move left nine files resolving paths relative to their old
depth, so the bootstrap could not read tests/Helpers/UnitStubs.php and no
test ran at all.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix tests without replacing production functions
* remove unrelated failing integration tests
* remove unrelated broken test workflows
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* 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.
* Make PHP 8.1 dependency builds reproducible
* Tighten integration runner permissions
* Harden and standardize GitHub Actions
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Restore ripgrep for security guards
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Wait for Cacti daemon readiness in CI
* Satisfy strict shell policy in diagnostics
* Preserve Cacti config group ownership
* Run stateful Cacti checks as the web user
* Run privileged page-crawl harness as root
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* ci(security): add Cacti-aware Semgrep taint rules
Off-the-shelf PHP SAST does not model Cacti's request helpers, escaping
wrappers, or db_* API, so it misses request-variable to SQL/shell/RRDtool
flows and flags htmle()-escaped output as false positives. These rules teach
the scanner Cacti's sources (grv/gnrv), sanitizers (db_qstr, sanitize_sql_column,
cacti_escapeshellarg, html_escape, casts) and sinks. The SQL rule independently
flags the host_id sink in lib/html_graph.php.
The rule self-test is a hard gate; the whole-tree scan is advisory and its
findings go to the code-scanning tab (grv() is validated where a filter is
registered, which Semgrep cannot see, and taint over large files is not
deterministic enough for a pass/fail baseline).
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Harden automation template ID filters
* Match automation test size semantics
* Test API automation tools at 100% coverage
* Make API coverage fixture load-order safe
* fix(tests): isolate API coverage stubs from unit suite
* ci(coverage): isolate API automation coverage workflow
* perf(automation): eliminate tree display N+1 queries
---------
Co-authored-by: TheWitness <thewitness@cacti.net>
* 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>