16230 Commits
Author SHA1 Message Date
Thomas VincentandTheWitness 3c20f4ce7e Harden Boost data handoffs and graph cache writes (#7729)
* fix(boost): harden data handoffs and cache writes

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

* Define $updates_ok when there is nothing to flush

PHPStan level 6 also flags the poller ownership check, which is the POLLER_ID
constant case the file already annotates above.

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

* Use a line comment to match the fixer

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

* Record the boost process columns in the audit schema

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

* test: resolve includes through Cacti path constants

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

* Fix Boost handoff review concerns

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

* test: seed settings in Boost database fixture

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

* test: refresh SQL interpolation baseline

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

* docs: record Boost handoff hardening

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

* fix: stop boost discarding a cycle and wedging a shard

Three defects in the handoff hardening.

A rejected ownership handoff returned false, which the poller reads as boost
having consumed the data, so it skipped the RRD write. The poller_output rows
were already deleted by then, so a stale poller_item copy on a remote poller
silently lost the whole cycle. Return true instead and let the poller write
directly, matching the database failure path.

Any non-OK RRDtool response retained the shard, and the retry then replayed rows
already written, which RRDtool rejects as an illegal update time. That failed
again on every pass, so the archive tables accumulated and boost never drained.
Treat an at-or-behind timestamp as applied, since the sample is in the RRD;
genuine errors still retain the shard.

The two new poller_output_boost_processes columns passed 'null' where
db_install_update_table reads 'NULL', so NOT NULL was never emitted and upgraded
installs diverged from cacti.sql.

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

* fix: spell the NULL column key so upgrades emit NOT NULL

db_add_column() reads $column['NULL'] and PHP array keys are case sensitive, so
the thirteen definitions in the 1.3.0 upgrade that passed a lowercase 'null' key
had the flag discarded and created nullable columns. Fresh installs built from
cacti.sql got NOT NULL, upgraded installs did not, and Cacti's own schema audit
reports the difference.

The same file already spells the key in upper case 144 times, and the wrapper's
own docblock example uses the upper case form.

Guard the class with a test over every upgrade file rather than only the one
that was wrong.

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

* test: assert the NULL key plainly

toContain() takes needles rather than a failure message.

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

* fix(boost): close remaining handoff review gaps

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

* fix(boost): guard nullable graph output

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

* fix(boost): keep in-flight completion rows through the 1.3.0 upgrade

The truncate only existed so the run_child UNIQUE key could build over rows
that predate run_id; deleting just those rows leaves a running boost its
completion records.

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

* refactor(boost): drop the poller ownership gate

Its boost_poller_on_demand() call sits behind the boost_redirect early return
that poller.php forces on for every multi-collector install, and its recovery
call turns a device deletion on main into a recovery that never brings the
collector back online.

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

* fix(boost): bound atomic cache write allocations

Guard upgrade source reads so failures report clearly, write graph cache data in fixed-size chunks, and exercise a multi-chunk payload.

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

* fix(boost): support Windows cache replacement

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

* test(boost): expand handoff failure coverage

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

* test(boost): support ANSI_QUOTES upgrade checks

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

* fix(boost): stop staging after chunk failure

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

* fix(recovery): use numeric poller status

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

* fix(sql): support ANSI_QUOTES in recovery paths

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

* fix(boost): preserve cache on replacement failure

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

* fix(boost): retain readable cache permissions

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

* fix(boost): repair completion schema at runtime

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

* fix(recovery): bound timestamp batch fetches

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

* fix(boost): satisfy runtime schema analysis

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

* fix(recovery): restore status after transfer failure

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

* test(boost): rely on unit bootstrap helpers

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

* fix(boost): refresh generated cache secret

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

* fix(boost): normalize cache path separators

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

* test(boost): fail clearly on unreadable fixtures

* fix(boost): name ignored duplicate sample keys

The staging warning only said rows were dropped, so a retry could not be told from a colliding writer.

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

* Lack of pre-commit hook execution allowed lint failure

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-09-12 20:17:35 -04:00
Thomas Vincent 428b56e04d chore: replace removed jQuery $.isFunction() with typeof checks (#7917)
* chore: replace removed jQuery $.isFunction() with typeof checks

jQuery 4.0 removes $.isFunction(). Swap the three call sites in navigationBox.js
and navigationBox.tree.js for behaviour-identical 'typeof x === function' checks,
removing the only jQuery-4-removed API in Cacti's own front-end code.

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

* fix: correct typeof negation in the mark.js availability check

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-12 15:25:01 -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 a2c8df612e fix(csrf): require POST tokens for state-changing actions (#7991)
* 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>
2026-09-12 15:09:25 -04:00
Daniel Nylander 64f9c5903e Translated using Weblate (Swedish)
Currently translated at 100.0% (5272 of 5272 strings)

Translation: Cacti/core
Translate-URL: https://translate.cacti.net/projects/cacti/core/sv/
2026-09-12 03:18:22 -05:00
dependabot[bot] ff38a6d12e ci(deps): bump trufflesecurity/trufflehog in the github-actions group (#8014)
Bumps the github-actions group with 1 update: [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog).


Updates `trufflesecurity/trufflehog` from 3.97.1 to 3.97.4
- [Release notes](https://github.com/trufflesecurity/trufflehog/releases)
- [Commits](https://github.com/trufflesecurity/trufflehog/compare/20652fbbdefffcdaa493a5bf57ab2ac6b1db715b...363923b901c911a9164f50b6c423f47c15372b1c)

---
updated-dependencies:
- dependency-name: trufflesecurity/trufflehog
  dependency-version: 3.97.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-11 06:28:31 -04:00
Thomas Vincent 4ac46bb28f ci: restore develop dependency compatibility (#8010)
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-08 00:14:48 -05:00
dependabot[bot]andThomas Vincent aa2e34c40b deps(deps): bump screenfull from 3.3.3 to 6.0.2 in the npm group (#8006)
Bumps the npm group with 1 update: [screenfull](https://github.com/sindresorhus/screenfull).


Updates `screenfull` from 3.3.3 to 6.0.2
- [Release notes](https://github.com/sindresorhus/screenfull/releases)
- [Commits](https://github.com/sindresorhus/screenfull/compare/v3.3.3...v6.0.2)

---
updated-dependencies:
- dependency-name: screenfull
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-07 18:37:22 -07: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
dependabot[bot] 49b10f8046 deps(deps): bump the composer group with 2 updates (#8004)
Bumps the composer group with 2 updates: [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) and [rector/rector](https://github.com/rectorphp/rector).


Updates `phpseclib/phpseclib` from 4.0.0 to 4.0.1
- [Release notes](https://github.com/phpseclib/phpseclib/releases)
- [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpseclib/phpseclib/compare/4.0.0...4.0.1)

Updates `rector/rector` from 2.6.3 to 2.6.5
- [Release notes](https://github.com/rectorphp/rector/releases)
- [Commits](https://github.com/rectorphp/rector/compare/2.6.3...2.6.5)

---
updated-dependencies:
- dependency-name: phpseclib/phpseclib
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: composer
- dependency-name: rector/rector
  dependency-version: 2.6.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-07 17:35:43 -07:00
Thomas Vincent 79a9ae0351 build: raise the 1.3 floor to PHP 8.3 (#7939)
* 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>
2026-09-06 08:44:40 -07:00
Thomas Vincent 08c02d3322 security: resolve open code scanning alerts (#8003)
* security: resolve open code scanning alerts

* test(security): constrain SQL helper sanitizer

* fix(security): address follow-up review
2026-09-06 08:41:47 -07:00
Thomas Vincent 4744d0d9db Forward-port data query source guard (#7985)
* issue#7809: stop reading a missing source key on output-only data query fields

query_snmp_host() tested $field_array['source'] as the left-most term of the
method-fix guard, so PHP raised "Undefined array key source" for every
output-only field before the direction test could short-circuit. Testing
direction first reads source only for input fields, which always define it.
The boolean result is unchanged.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
(cherry picked from commit 5f25145ba0)

* docs: restore changelog issue ordering

* Tighten data query regression error handling

* Clarify data query regression comment

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-06 08:41:27 -07:00
github-actions[bot] dd61dd7a31 ci: refresh security/SQL-interpolation baselines (#8001)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-06 08:36:54 -07:00
Thomas Vincent 95060c49ba Close test-suite discovery and coverage gaps (#7970) 2026-09-05 18:38:42 -07:00
Thomas Vincent 3f52135d36 Harden installer cleanup and enforce coverage collection (#7993) 2026-09-05 18:38:09 -07:00
Thomas Vincent 1fdf9acf3b Fix detached select menus on scroll (#8000) 2026-09-05 18:37:42 -07:00
Thomas Vincent 130f58243b Fix SNMP uptime selection across polling paths (#7984) 2026-09-05 18:25:43 -07:00
Thomas Vincent 770c503f3c ci: stop the nightly lock job failing on discussions (#7943)
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>
2026-09-04 19:37:04 -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
github-actions[bot]andThomas Vincent 5c63b65680 chore(contributors): update contributor list (#7945)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-04 19:31:18 -07:00
Thomas Vincent c07eb66398 chore: file new issues with the namespaced labels (#7942) 2026-09-04 14:10:10 -07:00
Thomas Vincent d8d81f273d build: make the containerised test image run the whole suite (#7953) 2026-09-04 13:37:37 -07:00
Thomas Vincent b489dbfab3 fix(build): stop tracking partial Composer vendor tree (#7960) 2026-09-04 08:57:01 -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 66a2808cc9 Let one notification address several recipients (#7782)
* feat(notify): let one notification address several recipients

Symfony calls a channel once per recipient, so a caller that needs a single
message sent to several people could not express it. The email 'to' option
folds the extra addresses into the same mailer() call and drops any that
match the recipient.

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

* fix(notify): compare the local part of an address exactly

RFC 5321 leaves local part case to the receiving host, so folding the whole
address deduplicated OPS@example.com and ops@example.com into one mailbox.
Only the domain folds now.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 21:01:03 -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 a9f8e6d002 docs: record how to refresh the security baselines and verify a branch (#7935)
Three things that cost real time and are not written down anywhere.

The sink and hotspot guards compare against checked-in baselines and gate every
pull request, but the refresh command is only printed by the verifier on
failure. A branch can go red on its own guard when a merged change moves a sink,
and every pull request then inherits the failure. Record the command, and note
that the verifiers strip the line number before comparing so most of the diff is
renumbering they ignore.

A defect reported against one branch is often already fixed on the other, in one
case by a pull request that had merged. Check the target still exists before
writing the fix.

A pull request whose workflow runs are held in action_required reports no checks
at all, which reads as passing. Say plainly that zero checks means unverified.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 20:34:32 -07:00
Thomas Vincent 1f5bc84a29 ci: share one body across the per-script coverage workflows (#7926)
* 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>
2026-09-02 20:34:16 -07:00
Thomas Vincent 97b5a1d4f5 chore: add a single entry point for local development (#7922)
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>
2026-09-02 20:34:00 -07:00
dependabot[bot]andThomas Vincent c54a5595de deps(deps): bump the npm group with 8 updates (#7919)
* deps(deps): bump the npm group with 8 updates

Bumps the npm group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [big.js](https://github.com/MikeMcl/big.js) | `6.2.2` | `7.0.1` |
| [billboard.js](https://github.com/naver/billboard.js) | `3.18.0` | `4.0.3` |
| [d3](https://github.com/d3/d3) | `7.8.2` | `7.9.0` |
| [htmx.org](https://github.com/bigskysoftware/htmx) | `2.0.6` | `2.0.10` |
| [jquery](https://github.com/jquery/jquery) | `3.7.1` | `4.0.0` |
| [jquery-validation](https://github.com/jquery-validation/jquery-validation) | `1.21.0` | `1.22.1` |
| [screenfull](https://github.com/sindresorhus/screenfull) | `3.3.3` | `6.0.2` |
| [tablesorter](https://github.com/Mottie/tablesorter) | `2.31.3` | `2.32.0` |


Updates `big.js` from 6.2.2 to 7.0.1
- [Changelog](https://github.com/MikeMcl/big.js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/MikeMcl/big.js/compare/v6.2.2...v7.0.1)

Updates `billboard.js` from 3.18.0 to 4.0.3
- [Release notes](https://github.com/naver/billboard.js/releases)
- [Changelog](https://github.com/naver/billboard.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/naver/billboard.js/compare/3.18.0...4.0.3)

Updates `d3` from 7.8.2 to 7.9.0
- [Release notes](https://github.com/d3/d3/releases)
- [Changelog](https://github.com/d3/d3/blob/main/CHANGES.md)
- [Commits](https://github.com/d3/d3/compare/v7.8.2...v7.9.0)

Updates `htmx.org` from 2.0.6 to 2.0.10
- [Release notes](https://github.com/bigskysoftware/htmx/releases)
- [Changelog](https://github.com/bigskysoftware/htmx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bigskysoftware/htmx/compare/v2.0.6...v2.0.10)

Updates `jquery` from 3.7.1 to 4.0.0
- [Release notes](https://github.com/jquery/jquery/releases)
- [Changelog](https://github.com/jquery/jquery/blob/main/changelog.md)
- [Commits](https://github.com/jquery/jquery/compare/3.7.1...4.0.0)

Updates `jquery-validation` from 1.21.0 to 1.22.1
- [Release notes](https://github.com/jquery-validation/jquery-validation/releases)
- [Changelog](https://github.com/jquery-validation/jquery-validation/blob/master/changelog.md)
- [Commits](https://github.com/jquery-validation/jquery-validation/compare/1.21.0...1.22.1)

Updates `screenfull` from 3.3.3 to 6.0.2
- [Release notes](https://github.com/sindresorhus/screenfull/releases)
- [Commits](https://github.com/sindresorhus/screenfull/compare/v3.3.3...v6.0.2)

Updates `tablesorter` from 2.31.3 to 2.32.0
- [Changelog](https://github.com/Mottie/tablesorter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Mottie/tablesorter/compare/v2.31.3...v2.32.0)

---
updated-dependencies:
- dependency-name: big.js
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: billboard.js
  dependency-version: 4.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: d3
  dependency-version: 7.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: htmx.org
  dependency-version: 2.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: jquery
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: jquery-validation
  dependency-version: 1.22.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: screenfull
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: tablesorter
  dependency-version: 2.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>

* Keep screenfull on browser-compatible release

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

* Refresh htmx integrity metadata

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 20:33:39 -07:00
Thomas Vincent 4afe89966e build: analyse at PHPStan level 8 and unpin the analyser (#7918)
* build: analyse at PHPStan level 8 and unpin the analyser

The level lived in the composer script as --level 6 while .phpstan.neon
declared none, so anyone running phpstan directly analysed at level 0 and saw
nothing. The level now sits in the config where both CI and a developer get
the same answer.

Level 8 finds 2787 issues on the current tree, so the baseline is regenerated
rather than the debt fixed here. The gate passes on what exists and fails on
anything new, which is the point: a fresh offset access on a db_fetch_row()
result is now caught in review instead of at runtime.

The debt is not 2787 unrelated problems. 1556 of them are the same shape,
"Cannot access offset X on array|bool", from the db_fetch_* family declaring
bool|array and call sites indexing the result without testing it. Narrowing
those return types would clear more than half in one change, which is worth
doing on its own.

phpstan was pinned to an exact 2.2.9, so patch releases were not reaching CI.
It now floats on ^2.2.12.

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

* build: refresh the PHPStan baseline against current develop

The baseline was generated while phpseclib was temporarily pinned back to the
3.x line. develop has since migrated the call sites to phpseclib4, so three
entries no longer match anything.

reportUnmatchedIgnoredErrors is false in .phpstan.neon, so stale entries are
ignored rather than reported and would have sat there indefinitely. The
migration introduced no new level 8 findings: 1483 entries become 1480, with
nothing added.

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

* build: refresh PHPStan baseline after rebase

* build: refresh baseline after transaction fix

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 20:33:22 -07:00
Thomas Vincent ba7b8ef818 hardening: predictable import temp name and unsanitized table-filter search (#7866)
* issue#7492: use tempnam for import temp files instead of rand()

The import wrote to sys_get_temp_dir()/package_import_<rand()>, a predictable
name a local user could pre-create or symlink. tempnam() creates the file
atomically with an unpredictable name.

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

* issue#7451: sanitize the CactiTableFilter search term

The shared table-filter Search box registered its term with FILTER_DEFAULT
(no sanitization), so the raw term reached filter processing. Validate it with
sanitize_search_string like the other search filters.

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

* issue#7450: confine the auth-none change-password referer redirect

check_reset_no_authentication() placed the raw HTTP_REFERER into the
auth_changepassword ref= parameter, an open redirect and header-injection
vector. Wrap it in urlencode(validate_redirect_url()), restoring the guard
1.2.x already has (this regressed on develop).

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

* issue#7490: anchor the graphs.php template_id filter regex

The template_id filter used an unanchored regex, so any value containing a
cg_/dq_/digit substring passed validation and reached explode()/SQL. Anchor it
to match the hardened sibling filters in lib/html_graph.php.

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

* issue#7453: escape template and query names in the package preview

get_package_contents() escaped file names with htmle() but emitted graph
template, data query, and data-query graph-template names raw, so a package
whose template name contains markup produced stored XSS in the import preview.
Wrap the names in htmle() like the file rows.

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

* test: refresh sink inventory baseline

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 20:33:06 -07:00
Thomas Vincent d07758c48b security: substitute query data before escaping graph titles and axis labels (#7846)
* security: substitute query data before escaping graph titles and labels

rrd_substitute_host_query_data() ran over the whole graph command after each
argument was cacti_escapeshellarg'd, so a device-supplied SNMP value expanded
from a |query_*| token in a graph title, vertical-label or right-axis-label
could contain a quote and break out of the RRDtool argument. Move the
substitution inside cacti_escapeshellarg at those three fields and drop the
post-escape whole-command pass. Forward-port of the 1.2.x fix.

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

* security: finish resolve-before-escape refactor, drop redundant inner substitution

$value is already resolved (credential-stripped and host/query substituted) by
rrdtool_resolve_graph_text() at the top of the graph-options loop, so the inner
rrd_substitute_host_query_data() in the title, vertical-label and right-axis-label
argument builders was a redundant second pass. Reconcile the older escape test to
match; the device-quote-neutralisation behavior test is unchanged.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 20:32:47 -07:00
Thomas Vincent cb5ef7ba4a fix: commit transactions on MySQL as well as MariaDB (#7928)
* fix: commit transactions on MySQL as well as MariaDB

db_commit_transaction() gated the commit on SELECT @@in_transaction. That
variable is MariaDB only; MySQL answers 'Unknown system variable
in_transaction', so the guard was false on every MySQL install and the commit
never ran. The transaction was left open and the function reported failure.

Verified against both engines: MariaDB returns 0, MySQL 8 raises ERROR 1193.

Every caller was affected, including device removal, which reports 'Unable to
remove Device N because its database transaction could not be committed' and
leaves the device in place.

Use PDO::inTransaction(), which tracks the state itself and answers the same
question on both engines.

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

* Update transaction regression assertion

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 16:54:17 -07:00
Thomas Vincent ed5ab24474 fix: detect a failed SNMP walk by exit code, not by device data (#7929)
* fix: detect a failed SNMP walk by exit code, not by device data

cacti_snmp_walk() discarded the entire walk when its output contained the word
Timeout. That output is the walk itself, one line per OID, so a device with an
ifAlias of 'Timeout Monitor' or a matching sysDescr emptied the result and
data_query.php then dropped every index for the device.

The check could not detect a real timeout either. net-snmp writes that to
stderr and neither walk command redirects it, so the matched string was always
device data. cacti_snmp_get() already reads the exit code for exactly this
reason; exec_into_array() exposes it by reference.

Also refuse a walk with empty credentials, matching cacti_snmp_get, get_raw and
getnext. An unknown v3 protocol previously built a credential-less snmpwalk and
failed with no log line.

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

* test: cover the credential guard and refresh the security baselines

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

* test: give the exec_into_array stub the real signature

The stub dropped the by-reference exit code that lib/functions.php exposes, so
any code reading it saw 0 regardless of what the command returned. That is why
the walk's failure detection could regress to matching stdout without a test
noticing.

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

* test: expect the exit-code wording for a failed walk

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-02 16:36:43 -07:00
Thomas Vincent 2a7da82bda ci: skip the page crawl when nothing it exercises changed (#7933)
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>
2026-09-02 16:33:05 -07:00
Thomas Vincent 82c2d93313 fix: relaunch RRDtool after a crash instead of failing the rest of the run (#7927) 2026-09-02 13:22:32 -07:00
Thomas Vincent 3fafd5476c chore: exclude development-only files from the release archive (#7921) 2026-09-02 13:22:07 -07:00
Thomas Vincent 063a385a94 test: add unit tests for layout.js and realtime.js helpers (#7911) 2026-09-02 13:21:33 -07:00
dependabot[bot] b7b3fd8808 ci(deps): bump the github-actions group with 4 updates (#7920)
Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action), [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.37.8 to 4.37.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938)

Updates `github/codeql-action/analyze` from 4.37.8 to 4.37.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938)

Updates `trufflesecurity/trufflehog` from 3.97.0 to 3.97.1
- [Release notes](https://github.com/trufflesecurity/trufflehog/releases)
- [Commits](https://github.com/trufflesecurity/trufflehog/compare/bcfcf73aaf4759d4dadc2783177c245a02792318...20652fbbdefffcdaa493a5bf57ab2ac6b1db715b)

Updates `github/codeql-action/upload-sarif` from 4.37.8 to 4.37.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: trufflesecurity/trufflehog
  dependency-version: 3.97.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.37.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 23:52:01 -07:00
Thomas Vincent ca2e4f07c2 Build releases from managed dependencies (#7888)
* 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>
2026-09-01 23:11:49 -07:00
Thomas Vincent ac0b2c9515 Fix confirmed poller, database, automation, and UI defects (#7861)
* issue#7024: do not delete still-running poller_time rows on cycle start

The overrun path deleted every poller_time row for the poller, including the
in-flight ones it had just counted, so overrunning processes lost their
tracking. Restrict the delete to completed rows.

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

* issue#7028: restore identifier and connection safety in the db layer

_db_replace interpolated caller-supplied column keys as backtick identifiers
without escaping, and db_commit_transaction checked @@in_transaction on the
default connection rather than the one passed in. Both were correct on 1.2.x
and regressed on develop.

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

* issue#7341: size the CSV export maxrows to the requested span

The export hardcoded --maxrows=10000, truncating wide date ranges regardless
of the data source profile step.

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

* issue#7421: offset automation_get_next_host through the 32-bit integer

The hand-rolled octet walk subtracted the wrong magnitude per octet and
emitted out-of-range octets for ranges wider than a /16.

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

* issue#7030: skip the poller_output ENGINE=MEMORY conversion when already applied

The output-table refresh ran ALTER TABLE poller_output ENGINE=MEMORY every
cycle even though the swapped-in copy already inherits the engine, adding a
metadata-locking DDL to the hot path. Probe the current engine and convert
only when needed.

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

* issue#7342: prefer snmpEngineTime only when it covers system uptime

update_system_mibs preferred snmpEngineTime (x100) over sysUpTime whenever it
was present, so an SNMP engine that restarts independently of the system (for
example OpenBSD snmpd) made the stored uptime drop, which reads as a reboot
and forces a perpetual reindex with mass 'U' results. Capture both values and
use snmpEngineTime only when it is at least the system uptime, which still
covers the 32-bit sysUpTime wrap.

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

* issue#7403: scale lmsensors readings in the get path like the query path

The get path returned the raw milli-unit SNMP counter while the query path
divided voltage and temperature by 1000 (and corrected negative voltages), so
a get gave values 1000x too large. Apply the same scaling before returning.

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

* issue#7029: propagate the reconnected handle back to the caller

db_check_reconnect() closed the dead connection, opened a new one, and
returned true without giving the new handle back, so the db_execute_prepared
retry ran its query against the connection that was just closed. Take $db_conn
by reference and assign the fresh handle; the one caller that passed a literal
false now passes a variable.

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

* test: refresh sink inventory baseline

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

* test: refresh architectural hotspots baseline

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

* issue#7474: return valid after correcting the theme to selected_theme

is_valid_theme() corrected an invalid theme to selected_theme but left $valid
false, so callers saw the theme as invalid even though it had been fixed.

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

* issue#7462: allow SELECT aliases as sortable automation columns

site_name and host_template_name are aliases (s.name AS site_name), valid in
ORDER BY but not real columns, so api_automation_column_exists() rejected them
and the site-name sort silently failed.

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

* issue#7456: bind the OS value in the automation OS filter

The OS filter built 'os = ?' but bound the network value, so filtering by OS
matched on the wrong input.

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

* issue#7491: filter host_template_id against its own column

html_make_device_where() compared h.location to the host_template_id value
instead of h.host_template_id.

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

* issue#7479: apply the hostname and description filters in get_hosts

The REST get_hosts() honored host_id/poller/site/template/location but ignored
the hostname and description filters its callers pass; add them as bound LIKE.

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

* issue#7475: authorize a direct client-IP poller match before the hostname guard

A poller configured by IP with no hostname-based pollers was rejected because
the empty-hostname guard ran before the direct-match check, even though the
client address exactly matched a poller's configured address.

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

* Guard two divisors that PHP 8 makes fatal

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

* docs: consolidate functional fix changelog

* test: cover consolidated functional fixes

* docs: identify consolidated functional fixes

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-01 21:01:28 -07:00
Thomas Vincent 22d19cfcc6 issue#7355: add ENTITY-SENSOR, JUNIPER and F5-BIGIP script data queries (#7839)
* 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>
2026-09-01 21:00:18 -07:00
dependabot[bot] 648ae108af deps(deps): bump the composer group across 1 directory with 3 updates (#7867)
Bumps the composer group with 3 updates in the / directory: [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib), [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) and [rector/rector](https://github.com/rectorphp/rector).


Updates `phpseclib/phpseclib` from 3.0.56 to 4.0.0
- [Release notes](https://github.com/phpseclib/phpseclib/releases)
- [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpseclib/phpseclib/compare/3.0.56...4.0.0)

Updates `phpstan/phpstan` from 2.2.8 to 2.2.9
- [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits)

Updates `rector/rector` from 2.6.2 to 2.6.3
- [Release notes](https://github.com/rectorphp/rector/releases)
- [Commits](https://github.com/rectorphp/rector/compare/2.6.2...2.6.3)

---
updated-dependencies:
- dependency-name: phpseclib/phpseclib
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: composer
- dependency-name: phpstan/phpstan
  dependency-version: 2.2.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: composer
- dependency-name: rector/rector
  dependency-version: 2.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 11:13:16 -07:00
Thomas Vincent a5098bbb6c issue#7026: verify package signatures with SHA-256, drop the SHA-1 fallback (#7864)
The verifier chose SHA-1 for short public keys, so a package signed with the
weak, forgeable SHA-1 was accepted. Always verify with SHA-256.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-31 17:13:17 -04:00
Thomas Vincent 668333b7c9 security(import): validate Data Source item fields coming from Template XML (#7720)
* security(import): validate Data Source item fields coming from Template XML

data_source_name, rrd_minimum and rrd_maximum reach an RRDtool command line
but never pass through the data_sources.php form on the import path, so the
same field rules are applied at the XML handoff instead.

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

* chore: add the CHANGELOG entry

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

* fix(import): name the rejection and keep the value out of the log lines

Code 7 reads as an XML parse error, which this is not, and the value comes
from the file being imported.

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>

* ci: restore changelog entry ordering

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

* fix(changelog): order the 7720 issue entry before features and drop a duplicate

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-31 17:08:26 -04: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 3045b4665e Add fail2ban support for authentication failures (#7676)
* Add fail2ban support for authentication failures

Emit a fixed, machine-parseable AUTH FAILURE line (user, realm, ip, reason)
from the universal login-failure path and the 2FA branch, normalise the human
AUTH strings, and ship a fail2ban filter, jail example, and README. The IP
comes from get_client_addr(); a crafted username cannot forge fields.

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

* test: resolve includes through Cacti path constants

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-31 17:02:27 -04:00
Thomas Vincent a4010e64a6 security: confine rrd data source paths to the RRA directory (#7665)
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-08-31 17:00:25 -04:00