623 Commits
Author SHA1 Message Date
Thomas Vincent 2b6e96b23a style(php): [develop] wrap CLI and poller calls without changing execution (#8303)
* style(php): wrap CLI and poller calls without changing execution

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

* fix(ci): review security inventories after poller wrapping

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

* test(boost): compare handoff source contracts by PHP tokens

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

* style(php): wrap poller constant strings without changing values

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

* style(php): wrap poller concatenations with token-preserving source contracts

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

* test(cli): assert CDEF repair queries after constant folding

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

* test: verify PHP formatting preserves parsed behavior

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-14 00:01:26 -07: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 9899fdf221 fix(installer): allow forced replays after LDAP migration (#8317)
* fix(installer): preserve forced replay on already upgraded schemas

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

* docs(changelog): record forced upgrade replay repair

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-11 18:57:46 -04:00
Thomas Vincent 06c32ab13f fix(installer): preserve upgrade state and propagate failures (#8266)
* fix(installer): preserve upgrade state and propagate failures

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

* docs(changelog): record installer recovery fixes for #8266

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

* test(security): refresh installer sink inventories

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

* fix(installer): retain merge precedence for browser parameters

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

* fix(installer): defer debug settings until the process lock is held

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

* fix(installer): enforce table conversion failures and postconditions

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

* test(installer): refresh SQL audit baseline after clause refactor

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-11 17:08:12 -04:00
TheWitnessandci 9facd9b500 security: batch 7 develop - consolidate remaining develop-only GHSA advisories (12 fixes + 8 confirmations) (#8149)
* security: batch 7 develop - consolidate remaining develop-only GHSA advisories (12 fixes + 8 regression confirmations)

Fixes: f7jw (automation preview XSS), rp5g (db_column_exists LIKE), vx2m (automation pivot CASE WHEN), h5wg (plugin DDL identifiers), 929h (aggregate LIKE filter), mfw8 (add_graphs REGEXP), m49v (user_admin sort_column allowlist), mpfm (rrd error-image theme allowlist), m3fh (SNMPv3 control chars), cg45 (remember-me row revoke), xq26 (remember-me retention window), m67r (import xml_path containment).

Confirmed already fixed on develop, pinned with regression tests: 4gph, 977w, mgcq, 5p79, cfhh, wfcf, pjq5, wp33.

Excluded: 227p (1.2.x-only), mx5c (maintainer private branch).

* security: address batch-7 review feedback (lexical-first xml_path containment, session-wide sort allowlist, connection-aware db_column_exists quoting, reject-not-normalize plugin DDL identifiers, behavior-based test names)

* style: apply php-cs-fixer single-quote fix to api_automation pivot builders; add WSL dev-environment note to copilot instructions

* fix phpstan string typing

* test: refresh SQL interpolation baseline for db_column_exists db_qstr change (database.php 9->8)

---------

Co-authored-by: ci <ci@local>
2026-10-02 15:27:34 -04:00
TheWitnessandTheWitness 3ab4c69fb7 PHPStan Level 8 - Phase 1 Conversion (#8135)
* PHPStan Level 8 - Phase 1 Conversion

Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).

Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
  array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
  return array (was mixed); the _prepared workers coerce the connection-failure
  path to an empty array so the declared type is honest. empty()/cacti_sizeof()
  checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
  the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
  now return self instead of a bare object so chained calls type-check.

Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
  (json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
  wrap foreach over array|false/list|false results with `?: []`.

Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
  the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
  entry (passing null to api_tree_item_save(int $type) -> TypeError), and
  api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
  (an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
  and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
  an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.

Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
  existing always-true/false ignore family (same "PHPStan gets confused"
  dynamic-constant cause already covered by identical.alwaysTrue,
  notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
  untouched long tail for Phase 2.

* Remove redundant @phpstan-ignore comments

Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:

- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
  covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
  lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
  lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
  match any reported error after the type-safety fixes (e.g. the
  db_fetch_row_prepared() return-type narrowing removed the offset-assign
  errors the api_automation ignores were suppressing).

Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.

* ci: satisfy validator ratchet and refresh security sink baselines

- lib/api_tree.php: give the new tree-item site_id a real numeric
  constraint ('^[-0-9]+$', matching html_reports.php) instead of an
  empty regex, so the validator constraint ratchet passes without
  bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
  whose code text changed from the (string) casts added in this branch
  (lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
  simplexml_load_string).

* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test

The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.

* test: update sql.php null-handling assertion for the (string) cast

This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.

* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound

- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
  (guarding the false lookup) instead of string-casting them, which made
  them resolve account/group names like '1001' and fail. Covers
  lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
  lib/package.php returns false, lib/clog_webapi.php and utilities.php
  render an error, and snmpagent_mibcachechild.php skips the cache write
  and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
  only the array offset, so a non-even entries/columns split no longer
  truncates the loop and drops a cached row.

* revert: keep db_fetch_* returning false on error; baseline Level 8 instead

Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.

Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.

---------

Co-authored-by: TheWitness <copilot@users.noreply.github.com>
2026-10-02 10:08:56 -04:00
Thomas VincentandTheWitness d3d99bb08a test(cdef): enforce resolver coverage (#7974)
* 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>
2026-09-23 13:38:51 -04:00
TheWitness e87d87f0cb fix(cli): replace dead github_actions check with a real TTY check before stty size (#8041)
* fix(cli): detect a real TTY instead of a dead github_actions check before calling stty size

* chore: add CHANGELOG entry for #8041
2026-09-18 11:50:53 -04:00
Thomas VincentandTheWitness cb49f31ff9 Replace csrf-magic with maintained CSRF protection (#7744)
* 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>
2026-09-18 00:16:39 -04:00
Thomas Vincent 3f377531b7 fix(poller): stop the registry pid probe narrowing a wide pid to -1 (#7956)
* fix(poller): stop the registry pid probe narrowing a wide pid to -1

processes.pid is int(10) unsigned, so a corrupted row can hold 4294967295.
Through PHP 8.4 posix_kill() narrows that to a 32 bit pid_t of -1 and kill(2)
reads it as every process the caller may signal, so cacti_process_pid_exists()
calls a dead registry row live and the two kill sites that ask it first SIGTERM
the caller's whole session. From PHP 8.5 the same value raises a ValueError and
ends the poller instead. Refusing it ahead of the call covers both.

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

* hardening: bound every process table PID before signalling it

Twelve call sites read a pid out of a process table and handed it straight to
posix_kill(). processes.pid is int(10) unsigned, so a corrupted row can carry
4294967295, which narrows to a 32 bit pid_t of -1 and signals every process
the caller owns, or from PHP 8.5 raises a ValueError that ends the collector.
They now go through cacti_process_kill(), which refuses init, anything that
cannot name a process, and anything wider than pid_t.

poller_recovery read its pid with a bare posix_kill($pid, 0) and treats false
as "stale, clear the row and run". cacti_process_signalable() keeps exactly
that meaning, including EPERM as stale, and adds the bound. Neither
cacti_process_pid_exists() nor cacti_process_still_running() fits there: both
report a pid live when signalling it is merely forbidden, so a recycled
recovery_pid would have wedged recovery until an operator cleared the row.

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

* test: place process guards in poller domain

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

* fix(poller): honor platform PID ranges

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

* test(poller): cover platform signal semantics

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-12 15:24:27 -04:00
Thomas Vincent 1ae479d4f5 feat(cli): add unified command frontend and normalize legacy failures (#7903)
* 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>
2026-09-07 18:36:55 -07:00
Thomas Vincent a155419ff3 Serialize process registry admission with portable locks (#7771)
* 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>
2026-09-04 19:36:42 -07:00
da7015e497 Break out of the --bulk_walk case in change_device.php (#7904)
* Break out of the --bulk_walk case in change_device.php

* Add CHANGELOG entry for #7904

* Fix CHANGELOG entry ordering

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

* test(cli): pin bulk walk option control flow

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Sean Mancini <sean@seanmancini.com>
2026-09-02 21:01:34 -07:00
Thomas Vincent 6e72b00ae5 security(cli): keep database credentials off the command line (#7909)
* security(cli): keep database credentials off the command line

audit_database.php passed the database password as --password= to the mysql
client. Escaping does not help there: an argument is readable from the process
list by every local user for as long as the import runs, and with --debug the
same string was printed to stdout. The credentials now go in a defaults file
created with mode 0600 and removed afterwards.

splice_rrd.php named its dump files from the RRD basename and mt_rand() in a
world writable directory and created them by shell redirection, and
backupRRDFile() copied into the same place. Both now use one private directory
created for the run. Modern kernels block the symlink case through
fs.protected_symlinks, so this is hardening rather than a fix for an
exploitable path, but the names should not be guessable in the first place.

float_rrdfiles.php appended its debug log to a fixed /tmp/clearer.log, which
any local user could pre-create or read. It now uses the system temporary
directory with a name of its own.

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

* ci: refresh audit database checks

* fix: quote credentials written to the MySQL option file

The option file format is not byte transparent. An unquoted '#' starts a
comment, a backslash escapes, and trailing whitespace is stripped, so any
install whose database password contained one of those authenticated with a
truncated credential and failed the schema load with no useful diagnostic.
Double quote the values and escape the backslash and quote characters,
verified against my_print_defaults.

Refuse a credential containing a newline rather than write a file the parser
would read as further options.

Use --defaults-file rather than --defaults-extra-file so a ~/.my.cnf belonging
to whoever runs the script cannot override the credentials Cacti supplies, and
trim the 'which mysql' fallback, whose trailing newline split the command and
stranded the credential argument on the second line.

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

* Align CLI hardening with quality guards

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 20:35:26 -07:00
Thomas Vincent c77906b144 Use a maintained finder for recursive MD5 discovery (#7718)
* 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>
2026-08-31 17:07:36 -04:00
Thomas Vincent ead882172e Make installer completion fail closed and add test coverage (#7626)
* 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>
2026-08-31 16:59:49 -04:00
Thomas Vincent 5da1a2d5e8 security: restrict graph input fields across handoffs (CVE-2024-31458) (#7669) 2026-08-30 10:56:16 -07:00
Thomas Vincent 5c6e3d21cb Add DI-friendly CactiFilesystem and adopt atomic CLI writes (#7189) 2026-08-29 17:52:26 -07:00
Thomas VincentandTheWitness f2898730e1 fix: guard process registration and cleanup against PID reuse (#7425) (#7574)
* fix: guard process registration and cleanup against PID reuse

A bare posix_kill($pid, 0) only proves some process owns the pid, so a
recycled one blocked new tasks or drew a SIGTERM meant for something else.

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

* test: spawn probe children without a wrapping shell

proc_open with a string command runs it via sh, so the child's /proc comm was
sh and the identity check correctly refused to signal it on Linux.

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

* docs: append the changelog entry at the end of the issue block

Matches .github/copilot-instructions.md, which appends rather than inserting
in numeric order.

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

* fix: limit the process identity check to CLI callers

The registry holds CLI tasks, so a web request comparing command names would
never match and would report a running task dead.

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

* test: use the full licence header on the new test files

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

* test: treat CACTI_PATH_LOG as a directory and pass ctor args to the statement class

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

* fix: make process identity checks self-contained

* fix(batchgapfix): guard forced process termination

* fix(processes): preserve live pids on permission errors

* Refresh security baselines after develop rebase

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

* Wait for the spawned child to exec before the identity check

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

* Serve fetch() from the buffered rows in the process registry fake

db_table_exists() probes with SHOW TABLES, and db_fetch_row_prepared()
reads a single row with fetch(); the fake drained the cursor on execute()
and answered neither, so every registry query came back empty and the
tests exercised the no-processes-table path instead of the guard.

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

* fix(changelog): drop entries duplicated by an earlier rebase

Only the issue#7425 line belongs to this branch.

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

* fix(poller): identify a registered process by its executable, not its name

Children are spawned from path_php_binary, so a versioned child and a parent
started as plain php reported different command names for one interpreter.

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

* test(poller): skip the alias check where both paths share a command name

Whether a second path to the interpreter yields a second name is the runner's
business; asserting it failed where php is already the plain name.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-08-16 20:12:10 -04:00
Thomas Vincent 5bc4df40c9 fix(cli): escape RRDfile paths passed to the shell (#7609)
* fix(cli): escape RRDfile paths passed to the shell

The paths come from data_source_path, which the web UI writes, so they were
running as whoever invoked the tool.

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

* Normalize configured CLI binary paths

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-08 11:31:09 -07:00
Thomas VincentandTheWitness 7a1e9bbf92 harden CSV encoding and process execution handling (#7196)
* fix: harden CSV encoding and process execution handling

Route CSV export cells through a shared cacti_csv_cell() helper so values with quotes, commas, or leading formula characters round-trip correctly, and add defensive guards on shell construction, unchecked proc_open handles, discarded exec exit codes, and gap values in nth-percentile fetch math.

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

* fix: address review feedback on CSV and process hardening

- escape poller_id in cmd_realtime proc_open command to match remote_agent

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

* fix: keep numeric CSV cells unquoted and guard snmptrap and rrdtool process failures

- exempt numeric values from the CSV formula-injection prefix and preserve empty snmptrap positions while bailing out when the RRDtool process fails to spawn

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>
2026-06-24 09:43:16 -04:00
Thomas VincentandTheWitness d9de6dff57 fix: logic and security bugs in 1.3.0 upgrade scripts (#7161)
* fix: correct logic and security bugs in upgrade scripts

install/upgrades/1_3_0.php:
- case 11 (hourly reports): was an unreachable duplicate case 10; fixes
  all hourly report rows being silently skipped during migration
- case 11: add missing comma between enabled and recur_every in SET clause
- settings/settings_user.name: restore to varchar(255) to match final
  schema; 1_2_31 widened it, 1_3_0 was incorrectly narrowing it to 75
- db_add_index -> db_install_add_key for all 5 index-creation calls so
  that index failures surface in the upgrade status tracker instead of
  being silently discarded

install/functions.php:
- install_unlink/install_rmdir/install_rmdir_recursive: add missing
  return after the CACTI_PATH_BASE guard; without it the path-escape
  log message was emitted but execution fell through to the destructive
  unlink/rmdir anyway
- import_colors: replace string-interpolated SQL with prepared statements
  and add hex-format validation (/^[0-9a-fA-F]{6}$/) to prevent SQL
  injection from a modified colors.csv

cli/upgrade_database.php:
- use db_execute_prepared for the final version stamp to be consistent
  with adjacent calls and eliminate the direct-concat pattern

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

* fix: use realpath() for canonical base-path boundary check in install helpers

str_contains($path, CACTI_PATH_BASE) is bypassable: a path like
CACTI_PATH_BASE.'/../other' contains the base as a substring but
resolves outside it. Replace with realpath() on both sides and
str_starts_with() on the resolved paths. Fail closed when realpath()
returns false (non-existent path).

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-05-16 12:59:59 -04:00
Thomas Vincent 32c604460d fix(cli): allow remove_graphs.php to target graphs with host_id=0 (#7041)
* fix(cli): allow remove_graphs.php to target graphs with host_id=0

Graphs left behind when a device is deleted with "Leave all Graph(s)
untouched" have their host_id set to 0.  Two issues prevented the CLI
tool from removing them:

1. The host_id validation rejected 0 as invalid ($id <= 0).  Changed to
   $id < 0 so zero is accepted as an explicit filter value.

2. The query unconditionally INNER JOINed the host table, which silently
   excluded host_id=0 rows because no host record with id=0 exists.
   The JOIN is now emitted only when --host-template-id is active, since
   h.host_template_id is the sole column that references the h alias.

Fixes #7035

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

* fix(style): align match arm fat arrows in ping.php

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-04-22 15:32:55 -04:00
Petr Macek 678039bec8 Add class filter + phpstan issue (#6890)
* return back old code becauese of phpstan

* add class
2026-03-27 07:54:21 -04:00
TheWitness 89c5ab2443 Fix: incorrect type in lib/html_utility.php and display_help format (#6887) 2026-03-24 09:46:09 -04:00
TheWitness 82f0204666 Comment out shell lint and coverage steps (#6874)
* Comment out shell lint and coverage steps

We will remove these checks until all the issues indenified by them have first been resolved.

* Disable shell-lint job in quality-extended.yml

Completely remove the step

* Disable spell check job in syntax.yml

Comment out the spelling job in the workflow.

* Fixing Syntax issue in lib/html_graph.php

* Fixing PHPDoc in lib/html_graph.php

* Fixing some style issues in syntax.xml

* Fix more style issues.

* Commenting out actionlint for the moment

* Fixing last issue with html_graph.php

* Fixing issue with auth_profile.php

* Fixing PHPStan issues

* Fix another PHPStan issue

* Fixing Unit Test

* Fix config.php prime step

* Fixing syntax check style

* Going back to old approach

* Moving csrf.php inclusion back to main line for automation

* Fixing issue with create logic

* Fix issue where poller exits with non-zero exit code

* Fix logging only for *NIX

* Fix poller completion detection by reverting code

* Fixing syslog change

* Fixing syslog change
2026-03-21 17:51:52 -04:00
Thomas VincentandTheWitness 096165a1bf fix: correct grammar and spelling errors in English source strings (#6802)
* fix: correct grammar and spelling errors in English source strings

Fix eleven errors in user-facing strings, log messages, and CLI output:

- global_form.php: "is not update" -> "is not updated"
- global_form.php: "name match" -> "name matches"
- global_form.php: "free form text" -> "free-form text"
- global_form.php: "packet to sent" -> "packet to send"
- global_settings.php: "Sources can includes" -> "Sources can include"
- data_templates.php: "only will affect" -> "will only affect"
- lib/utility.php: "This settings should" -> "This setting should"
- lib/data_query.php: "it's index" -> "its index" (possessive)
- poller_automation.php: "it's execution" -> "its execution" (possessive)
- cli/sqltable_to_php.php: "it's install function" -> "its install function"
- cli/sqltable_to_php.php: "it's name" -> "its name" (possessive)

No functional changes.

Fixes #6801

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

* fix: address Copilot review suggestions on PR #6802

Three grammar fixes from Copilot inline review:

- cli/sqltable_to_php.php: schema's (spurious possessive) → schemas;
  plugins setup.php → plugin's setup.php (correct possessive)
- include/global_settings.php: Space delimited → Space-delimited
  (hyphenated compound adjective before noun)
- lib/utility.php: Cacti instances is running → Cacti instance is
  running (subject/verb agreement)

Refs #6801

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

* fix: address Copilot review suggestions in #6802

- Fix subject/verb agreement: "instance is" -> "instances are" in lib/utility.php
- Regenerate cacti.pot to reflect updated msgid

Refs #6801

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

* Fix string formatting in sqltable_to_php.php

Fixing php-cs-fixer violations

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-03-10 09:25:10 -04:00
Thomas VincentandTheWitness f89c7d8254 fix: correct codespell-detected spelling errors in PHP source files (#6804)
* fix: correct codespell-detected spelling errors in PHP source files

Fix 63 spelling errors identified by automated codespell scan across 23
files. All changes are in comments, docblocks, string literals, and one
dead variable assignment ($flage → $flags in lib/database.php). No logic
changes.

Corrected words include: accommodate, authentication, complete, dependent,
draggable, filtering, formatting, heartbeat, immediately, information,
initialize, language, logging, parameter, please, resource, resetting,
similarly, specified, SQLite, statistics, support, timeout, whether.

Exclusions: parms/PARMS (Cacti CLI variable convention), tht/ot (SQL
aliases), CurrOS/hasTables (PHP variables), pring (internal function).

Fixes #6803

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

* fix: address Copilot review suggestions on PR #6804

Remove dead variable assignment activated by $postion -> $position typo
fix in install/upgrades/1_0_0.php. The misspelled variable was never
read, so fixing the name caused it to silently overwrite the correctly
computed $position before the UPDATE. Removing the line restores the
original (intended) behavior.

Fix docblock possessive: "it's formatting" -> "its formatting" in
lib/reports.php.

Refs #6803

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

* i18n: regenerate cacti.pot after msgid changes in global_settings.php

"pre-selected" -> "preselected" and "re-using" -> "reusing" changed two
msgid strings. Regenerate cacti.pot so translation tooling picks up the
corrected English source strings.

Refs #6803

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-03-09 10:08:12 -04:00
Thomas Vincent a55d72f358 fix: use require_once in plugin loader paths (#6792)
* fix: replace include_once with require_once for core and plugin loader paths

Core library files in include/global.php are unconditionally required for
Cacti to function; a missing file should produce an immediate fatal error
rather than silently continuing with undefined functions. Convert all
include_once calls to require_once.

Plugin setup.php loaders in lib/plugins.php and cli/audit_database.php
already have file_exists() guards at the two sites that need them. Add a
guard + explicit error/redirect in api_plugin_install() where none existed.
Convert include_once to require_once at all three sites so a file that
passes the existence check but fails to load produces a clear error at
the point of failure.

Closes #6788

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

* fix: guard header() in install path with is_web check; tighten test assertion

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

* fix: convert remaining include_once to require_once in hook dispatch path

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-03-09 09:39:04 -04:00
Thomas VincentandTheWitness 6ad201cc4f fix: remove deprecated curl_close() and imagedestroy() for PHP 8.5 (#6772)
* fix: remove deprecated curl_close() and imagedestroy() for PHP 8.5

PHP 8.5 deprecates curl_close(), imagedestroy(), and xml_parser_free().
Handles are automatically freed when they go out of scope.

- cli/add_site.php: remove 2 curl_close() calls
- lib/auth.php: remove curl_close() in secpass_check_pwned()
- lib/plugins.php: remove curl_close() in plugin fetch
- lib/reports.php: remove 2 imagedestroy() calls in jpeg2png/png2gif
- lib/rrd.php: remove 3 imagedestroy() calls in graph rendering

No behavioral change; resources are freed implicitly.

Made-with: Cursor

* test: add Pest tests for PHP 8.5 curl_close/imagedestroy removal

- Php85DeprecationsTest: static checks that no curl_close or imagedestroy
  calls remain in the 5 changed files
- ReportsImageConversionTest: png2jpeg/png2gif produce valid output
  without explicit imagedestroy (resources freed on scope exit)

Made-with: Cursor

---------

Co-authored-by: TheWitness <thewitness@cacti.net>
2026-03-06 08:55:54 -05:00
3e1dc3c886 fix: replace strtolower/strtoupper with locale-independent wrappers (#6717)
* cleanup: remove unreachable defensive error logs in aggregate graph item validation

* fix: replace strtolower/strtoupper with locale-independent wrappers

PHP 8.5 deprecates locale-sensitive strtolower() and strtoupper().
Add cacti_strtolower() and cacti_strtoupper() wrappers that use
mb_strtolower/mb_strtoupper with explicit UTF-8 encoding, then
replace all 185 call sites across 65 files.

Existing mb_strtolower() calls (email normalization) are left as-is
since they already specify encoding explicitly.

Includes 18 Pest tests covering ASCII, UTF-8, protocol names, config
values, hex strings, NaN detection, and file extension patterns.

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

* fix: correct docblock references and month_shift() strpos check

Fix docblocks to reference strtolower()/strtoupper() (the deprecated
PHP builtins) instead of the wrapper names. Fix month_shift() to use
strpos() !== false instead of > 0 so 'month' at position 0 is matched.

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

* chore: drop unrelated lib/api_aggregate changes

Keep PR scope focused on the issue for this branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-03-05 19:51:02 -05:00
Thomas VincentandTheWitness 4b3793e6c0 fix: 7 bugs in cli/add_site.php (#6667)
* fix: 7 bugs in cli/add_site.php

- enable SSL certificate verification on geocode API call
- escape user-supplied regex/wildcard to prevent ReDoS
- capture fixCoordinates() return value (was silently discarded)
- replace deprecated utf8_decode() with mb_convert_encoding()
- fix doDeviceMap() logic: reject when either ID is zero
- honour --no-replace flag (was parsed but never checked)
- handle curl_exec() returning false on failure

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

* test: add Pest tests for cli/add_site.php fixes

15 tests covering: DMS coordinate conversion, doDeviceMap guard
logic, regex/wildcard escaping, replaceSites flag, and curl error
handling. Uses inlined logic to avoid Cacti bootstrap dependency.

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

* fix: validate regex patterns that include delimiters

When a user supplies a regex with delimiters (e.g. /pattern/),
preg_quote was bypassed. Add syntax validation via preg_match
to reject malformed patterns early.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-02-26 20:05:32 -05:00
Thomas VincentandTheWitness 8683a420d7 fix: parameterize SQL in cli/add_device.php (#6656)
* fix: parameterize SQL in cli/add_device.php

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

* fix: use short array syntax [] in db_execute_prepared calls

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-02-25 20:49:23 -05:00
Thomas VincentandTheWitness fcbcbdd1d7 refactor: replace strpos() boolean checks with str_contains/str_starts_with (#6645)
* refactor: replace strpos() boolean checks with str_contains/str_starts_with

Completes the PHP 8.x string function migration started in lib/.
Converts remaining legacy patterns across 81 files:

- strpos($x, $y) !== false → str_contains($x, $y)
- strpos($x, $y) === false → !str_contains($x, $y)
- strpos($x, $y) != false  → str_contains($x, $y)
- bare if (strpos($x, $y)) → if (str_contains($x, $y))
- substr($x, 0, strlen($y)) == $y → str_starts_with($x, $y)

Intentionally preserves strpos() calls that use the return value as
an integer offset:
- user_admin.php / user_group_admin.php (4 calls, ternary for position)
- auth_profile.php (2 calls, offset for substr); guards fixed from
  bare if ($pos) to if ($pos !== false)

Behavior fix: ss_esxi_vhosts.php:55 bare strpos() missed position-0
matches; str_contains() now correctly detects 'not installed' at any
position. Adjacent line already used str_contains().

Ref: https://github.com/Cacti/cacti/issues/6641

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

* fix: correct dead-code elseif in plugin_display_compat() version comparison

The if/elseif chain in plugin_display_compat() checked >=, >=, >, <
— the second >= was unreachable dead code. Corrected to <= to complete
the operator set (>=, <=, >, <).

Without this fix, <= constraints (e.g. "<=1.2") fell through to the <
branch and were rendered as "< =1.2" instead of "<= 1.2".

This pre-existing bug was exposed when converting strpos() to
str_contains(), which made PHPStan detect the always-false condition.

Ref: https://github.com/Cacti/cacti/issues/6641

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

* fix: add limit to explode('=') in CLI argument parsers

17 CLI scripts used explode('=', $parameter) without a limit of 2,
silently truncating values containing = (e.g. --note=a=b parsed as
$value='a' instead of 'a=b'). The other 40 CLI scripts already had
the correct limit.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-02-25 11:38:59 -05:00
TheWitness ca8d65ea78 Update audit_database.php and audit_schema.sql (#6614)
This will ensure that when audit_database.php is run and the report shows errors, that the return code is something other than 0.
2026-02-13 02:30:48 -05:00
TheWitness 7919efd137 Issues found in testing (#6595)
* QA: Add database session clear out to change password

* QA: Can not determine average fully loaded

* QA: Synchornize Spikekill with 1.2.x

* Removing Variance as it's not mathmatically correct

* Removing Variance as it's not mathmatically correct

* Properly re-draw graph
2026-02-03 12:02:39 -05:00
TheWitness 27919c50d7 Updating Copyrights to 2026 (#6578) 2026-01-28 16:11:10 -05:00
TheWitnessandCopilot 9ed3a1af3b Allow Spikekill to work on time windows for stddev and variance (#6571)
* Fix: Defect in SpikeKill algorithm was causing a loss of data for Gap Fill and Float methods

* QA: Correct syntax error

* Feature: Make spike kill work on standard deviation and variance

* QA: php-cs-fixer changes

* Changes after Copilot review

* Update lib/spikekill.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update lib/spikekill.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* More Copilot Feedback

* More Copilot Reviews

* Fully testing Variance method

* One remaining NaN comparition

* More minor issues after Copilot review

* QA: Last check on defaults

* Add Pre-commit config

* Update lib/spikekill.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Better descriptions

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 07:02:34 -05:00
J.P. PasnakandCopilot 7e698f7c49 typos (#6565)
* typos

* Update plugins.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-25 10:31:33 -05:00
TheWitnessandCopilot b993471579 Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand,PHPdoc, and Typing (#6540)
* Fix #6539 - Allow Standard Cacti Log Files to be Rotated on Demand from the Cacti UI

* QA: php-cs-fixer updates and documentation for lib/database.php

* QA: Update .gitignore for composer files

* QA: Update .gitignore for composer files

* QA: Back off .gitignore change its duplicated

* QA: Fix typing of db_has_permissions

* QA: Fixing typing issue in db_format_index_create

* Update lib/clog_webapi.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* QA: Fix additional code issues

* QA: Applying more defaults to add device

* QA: Fix at the database layer

* QA: Fixes based upon automated testing

* QA: Fix at the database layer

* QA: After new SQL checking

* QA: Skip MacTrack oui database download warnings

* QA: Fixing type issue

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Some minor changes to avoid errors in testing

* QA: Unused variable

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Recommendation from Copilot

* QA: Minor introduced code defect

* QA: Adding some unifying space around concatination

* QA: Consistent comments and new function alias'

* QA: Multi-line comment corrections

* QA: Fix SQL Style

* QA: Missed one function alias

* QA: PHPStan Level 2 and PHPDoc fixes

* QA: PHPStan Level 2 and PHPDoc Fixes

* QA: PHPStan Level 6 Fixes

* QA: Fix lib/xml.php to PHPStan Level 6

* QA: Wrong grep -v

* PHPStan Level 6 for lib/vdef.php

* PHPStan Level 8 for lib/time.php

* QA: Raise PHPStan to Level 6 for lib/database.php

* QA: Remove @deprecated tag from grv()

* QA: Switch to htmle() alias over html_escape()

* QA: More addressing PHPStan issues at Level 6

* QA: Change recommended by Copilot for Readability

* QA: More PHPStan fixes

* QA: More PHPStan fixes

* QA: More PHPStan fixes for lib/utility.php

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: Raise PHPStan to Level 6 More Functions

* QA: More PHPStan fixes at Level 6

* QA: Some phpseclib3 fixups to update to latest

* QA: More fixups for graphing for magic strings

* QA: More PHPStan fixes at Level 6

* QA: Fixing order string type on get_allowed_graphs()

* QA: Fixing improper use of print

* QA: Optimization of color_templates

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Copilot recommendation

* QA: Raise PHPStan to Level 6 More Functions

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues insalling Cacti

* QA: Issues testing Cacti

* QA: Copilot recommendation

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: More PHPStan fixes at Level 6

* QA: Installer failing

* Undo ownership changes to remove tar errors

* QA: Updates after php-cs-fixer

* QA: Adding code checks to syntax checking

* QA Change syntax flow language

* QA: Reduce the number of cures

* QA: Use sequentional option

* Change runner to self-hosted for check job

* Fix typo in OS version in workflow file

* Add Git installation step in workflow

Added step to install Git if not already installed.

* Add '-y' flag to apt-get install and update commands

Updated apt-get commands to include '-y' for automatic confirmation.

* Change workflow OS version to 'ubuntu-latest'

Updated the workflow to use 'ubuntu-latest' instead of 'ubuntu:latest' and modified the 'runs-on' configuration.

* Remove sequential option from phpcsfixer script

* Remove PHPCS fixer step from workflow

Removed PHP coding standards check from workflow.

* Move code checks to before plugins

Moving these core checks to before the plugins are installed as we really want to check only the base Cacti here.

* Add Composer validation and caching to start of workflow

* QA: Addressing PHPStan additional

* QA: Restore composer lock file

* QA: Addressing PHPStan additional

* QA: Addressing PHPStan additional

* QA: Complete LDAP Project

* QA: php-cs-fixer doing it's thing

* QA: Syntax is already happening in PHPStan

* QA: The md5 function no longer exists

* QA: Update after ldap changes

* QA: Undo that last password hack

* QA: Attempting to switch to MariaDB 11.8

* QA: Attempting to switch to MariaDB 11.8

* QA: Resolving some issues with PHP8.4

* QA: Resolving some issues with PHP8.4

* QA: Add PHP8.5 to the matrix

* QA: Removing PHP8.5 due to dependency issues

* QA: Add the DB version to the testing output

* QA: Switch back to MySQL for the moment

* QA: More fidelity in reporting

* QA: The pipe must be mixed

* QA: Moving audit_schema.php changes from 1.2.x

* QA: Moving audit_schema.php changes from 1.2.x

* QA: Restore mysql monitoring

* QA: php-cs-fixer issues

* QA: Attempt to roll back to MariaDB 10.6

* QA: Attempt to fix check_all_pages.sh

* QA: Make installer functional again

* QA: Fix installer php-cs-fixer issues

* QA: Type to a string

* QA: Get the database version correctly

* QA: Fix issue with reports in check_all_pages.sh

* QA: Check the server version in the right place

* Feature: Support SHA256 as a new hash for passwords

* QA: Function definition and return typing audit

* QA: Refinement of form types

* QA: Refinement of form types

* QA: Consistency of comments

* QA: Issues with php-cs-fixer and more comments

* QA: More typing issues

* QA: php-cs-fixer on merge conflict resolution

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-25 10:13:56 -05:00
Petr Macek 3012089a47 add freebsd paths (#6556) 2026-01-21 16:55:58 -05:00
TheWitness db44318baa Fix #6518 - Ensure the csrf-secret file is written in less permissive way 2026-01-07 15:10:27 -05:00
Mark B cbdc4632b4 Don't create the csrf-secret.php world readable (#6518) 2026-01-07 14:54:24 -05:00
Petr Macek 677441f572 add Admin email address and nofity_admin (#6485) 2025-12-09 15:40:41 -05:00
TheWitness 422dc00ae4 QA: Updating props and changelog.php 2025-11-19 21:16:12 -05:00
TheWitness b9334a15d9 QA: Updates for files for php-cs-fixer and some php-doc changes 2025-11-18 21:01:15 -05:00
Sean ManciniandCopilot 50efc94ba7 Poller Migration CLI Script (#6439)
* Add device migration script for Cacti pollers

This script facilitates the migration of devices from one poller to another within the Cacti monitoring system. It includes options for migrating all devices or specific devices by host IDs, along with logging and validation features.

* Update cli/migrate_poller.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update cli/migrate_poller.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update cli/migrate_poller.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update migrate_poller.php

* Change migration record from CSV to log file

Updated migration logging to use log files instead of CSV files for rollback purposes.

* Improve logging by handling write errors

Add error handling for log file write failure.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-23 18:28:48 -04:00
Petr Macek b8a1656924 fix#6298 - add snmp security level to the cli script (#6436)
* fix#6298 - add snmp security level to the cli script

* formatting
2025-10-20 14:53:24 -04:00
seanmancini 803d5653d0 Dont treat current_timestamp as string (#6311) 2025-08-25 13:52:46 -04:00
TheWitness 3863c789ec QA: Mast error with 'Extra' Attribute and Timestamp Fields 2025-08-17 20:37:02 -04:00