* security: consolidate verified GHSA fixes for develop (batch 4)
Develop counterparts of the 1.2.x batch-4 fixes in #8144, re-verified against
current develop HEAD.
- GHSA-4rmr-wvjq-qxc2: include/auth.php Basic Auth and guest branches fall
through to the per-page realm check instead of returning early.
- GHSA-j67j-wpm4-9g3x: the pre-dispatch update_policy branch in user_admin.php
and user_group_admin.php requires a CSRF-token POST. The denylist -> allowlist
redesign is tracked for 1.3 in #8143.
- GHSA-2p3x-f5jm-rg6p: the View-Graphs -> poller RCE chain is broken by the
realm check (4rmr), the update_policy guard (j67j) and shell-free webhits (x5pw).
- GHSA-x5pw-cg7j-mj4r: scripts/webhits.pl runs wc through a shell-free list-form open.
- GHSA-9737-rqh3-h7fh: names interpolated into raise_message() now use __esc()
across the UI; URL- and HTML-bearing messages are excluded.
- GHSA-9x9h-2577-9w86: the colordropdown input is built as a node and the label
is no longer round-tripped through parseHTML (the list item already used
createTextNode on develop).
- GHSA-h96j-8xhc-2p3w: remote_agent.php authorizes on $_SERVER['REMOTE_ADDR'],
not get_client_addr(), which honours client-settable proxy headers.
Regression guards under tests/Unit/Security/{Auth,Csrf,Xss,ShellInjection}.
php -l, check-changelog.py and the sink/architectural baselines all pass.
* chore: reference PR #8145 on the batch-4 1.3.0-dev CHANGELOG entries
* security: resolve develop merge - keep id-only xmlerror unescaped (matches #8141 h745 test)
* security(GHSA-j67j): use REQUEST_METHOD POST guard instead of raw \ (hardening-pattern-guard)
* security(GHSA-9737): fix review findings - drop double-encoding in __esc, escape missed name sinks, correct plugins format-arg count
* 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>
* 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>
* 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>
* fix(nimble): assemble 64-bit volume value from high and low words
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(nimble): cover high/low word assembly in ss_nimble_alletra_volumes
Regresses the operator-precedence bug where concatenation bound
tighter than the ternary, silently dropping the low word whenever a
high word was present.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix: use gmp for Counter32/Counter64 wrap math in ss_net_snmp_disk_*
The wrap-correction expressions in ss_net_snmp_disk_bytes.php embedded
the literal 18446744073709551616 (2^64), which exceeds PHP_INT_MAX on
every supported runtime and is silently promoted to float. Each Counter64
wrap therefore lost ~3 decimal digits of precision, producing spurious
zero, negative, or NaN deltas on busy storage arrays.
ss_net_snmp_disk_io.php used intval() on Counter32 values, which
saturates at 2^31-1 on 32-bit PHP and on the wrap branch silently
truncates legitimate values into negative deltas.
Both scripts now route every wrap correction through a small
ss_counter_step() helper that uses ext-gmp (already required in
composer.json) for arbitrary-precision arithmetic, and falls back to
float when gmp is missing. Same control flow, same accumulator
semantics, no behavioural change on the no-wrap branch.
Test plan: smoke-checked the helper against synthetic Counter64 wrap
(current=100, previous=18446744073709551500) and confirmed the delta
returns the expected 216 byte difference instead of a float collapse.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* style: document disk counter running value
* ci: update phpstan baseline
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix(scripts): return 'U' on error in ss_webseer, ss_gexport, query_host_cpu
Data source scripts must return 'U' (RRDtool unknown) on error rather than
'0', which is a valid data value and silently poisons graphs.
- ss_webseer.php: replace empty() guard with strict $value === '' comparison
(empty('0') is true in PHP, corrupting a valid zero reading); return 'U'
on fallthrough instead of false
- ss_gexport.php: return 'U' on fallthrough instead of null
- query_host_cpu.php: print 'U' when requested index absent in get handler
Adds DatasourceScriptErrorReturnTest.php with source-scan tests for each
corrected return path including regression guards for the empty() trap.
Uses tab indentation to match repo conventions.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix(ss_webseer): catch false/null returns from db_fetch_cell_prepared
db_fetch_cell_prepared() returns false when the query finds no row or
fails. The previous check ($value === '') passed false and null through
as-is, returning a non-'U' value to rrdtool and causing NaN spikes.
Extend the guard to treat false and null the same as empty string.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(scripts): add GPL header to DatasourceScriptErrorReturnTest
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test(scripts): add false/null guard tests for ss_gexport and ss_webseer
Adds source-scan assertions verifying both scripts handle false and null
returns from db_fetch_cell_prepared, and a behavioural dataset confirming
the guard expression maps all three sentinel values to 'U'.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
- Add scripts/pre-commit-check.sh with pre-flight validation:
- PHP version check (warns if not 8.1-8.4)
- Merge conflict marker detection in staged PHP files
- composer.lock staging prevention
- Vendor dev-dep staging prevention
- Autoload freshness check (composer.json vs vendor/autoload.php)
- Tool availability + loadability check (catches stale autoload)
- php -l fallback when phplint is broken or missing
- Auto-fix suggestion when php-cs-fixer fails
- Fix .pre-commit-config.yaml:
- Change file filter from ^app/ to types: [php]
- Route all hooks through wrapper script
Gives clear actionable error messages instead of PHP fatal errors
when dev dependencies are missing or out of sync.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* fix: shellcheck fixes, codespell config, and actionlint config
- Add .github/actionlint.yaml with no-shellcheck to disable
false positives in run: blocks
- Update .codespell.cfg to skip third-party assets, themes,
mock data; add intentional words to ignore list
- Fix shellcheck issues in all shell scripts: quoting, egrep
to grep -E, glob iteration, bare redirects, SIGKILL trap
Does not touch workflow YAML files (handled in #6874).
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* hardening: move to argument array pattern for shell execution (#6854)
* docs: update exec_background docblock for string|array param types
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* docs: add PHPDoc to __rrd_execute for PHPStan
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: resolve php-cs-fixer formatting issues
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* revert: remove oauth2.php fix (moved to standalone PR #6886)
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* chore: remove files that belong in other PRs
- Revert lib/poller.php and lib/rrd.php arg-array changes (belong in PR #6855)
- Remove ArgArrayHardeningTest.php (belongs in PR #6855)
- Revert vendor/composer updates (should be a separate PR)
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* chore: remove composer.lock from tracking
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
Add script to validate PO files for format-string mismatches.
Runs msgfmt --check-format on all locale files.
- scripts/validate_cacti_po.py
- Can be run locally or in CI
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* 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>
* hardening: use cacti_escapeshellarg() in sql.php and ss_sql.php
Bare escapeshellarg() bypasses Cacti's internal escaping contract.
Replace all three call sites in each script with cacti_escapeshellarg()
so any future Cacti-level escaping hooks apply consistently.
Update SqlScriptsTest to assert cacti_escapeshellarg() is present and
that no bare escapeshellarg() calls (negative lookbehind) remain.
Closes#6785
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: return 'U' on empty shell_exec output in scripts/sql.php
Cacti data source scripts must return 'U' on error, never empty string.
shell_exec() returns null on failure; the null-coalesce produced '' which
Cacti treats differently from 'U'. Add ?: 'U' fallback and a test.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: add ss_sql.php U-on-error assertion and fix interpolation guard regex
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* cleanup: remove unreachable defensive error logs in aggregate graph item validation
* fix: parameterize $index in mikrotik script SQL queries
Replace direct string interpolation of $index in WHERE clauses
with db_qstr() to prevent SQL injection in ss_mikrotik_qtrees,
ss_mikrotik_interfaces, and ss_mikrotik_queues.
Also add column name validation in ss_mikrotik_interfaces (regex
allow-list) and a default return in ss_mikrotik_queues switch to
prevent unvalidated column names from reaching SQL.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: parameterize $index in WHERE IN clause for mikrotik scripts
Addresses Copilot review: use prepared statement placeholders
instead of db_qstr() for $index and $index2 in the WHERE IN clause.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: add 19 Pest tests for mikrotik SQL parameterization
Cover column validation regex, allow-list, and index parameterization
guards. Verifies SQL injection payloads stay in params, not in SQL.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* revert: restore api_aggregate.php to develop state
Remove accidental changes to aggregate graph item validation that
were not part of this PR's scope (mikrotik SQL parameterization).
Also fix pre-existing lib/dsstats.php alignment and PHPStan bootstrap
order (lib/functions.php must load before include/global.php).
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
---------
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* cleanup: remove unreachable defensive error logs in aggregate graph item validation
* fix: harden data collection scripts with correct SNMP retry parameter
Replace $host['ping_retries'] with $host['snmp_retries'] across all
SNMP data collection scripts. Add column allow-lists for MikroTik
scripts to prevent SQL injection via column name interpolation.
Add Pest tests verifying column allow-lists reject injection attempts
and all scripts reference snmp_retries instead of ping_retries.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* fix: consolidate disk device detection with preg_match
Replace 8 str_starts_with() calls with a single preg_match() in
ss_net_snmp_disk_io.php for readability. Align ss_net_snmp_disk_bytes.php
with the same device prefixes (xvd, vd, hd, md, dm-) and improved
nvme-aware partition filter.
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>
* cleanup: remove unreachable defensive error logs in aggregate graph item validation
* refactor: consolidate duplicate switch cases in ss_hstats.php
Extract ss_hstats_map_stat_to_column() helper to eliminate 8
duplicate switch cases. Add Pest tests for all stat-to-column
mappings.
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>
* 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>
* cleanup: remove unreachable defensive error logs in aggregate graph item validation
* fix: replace backtick operator with shell_exec() and escape args
Replace deprecated backtick execution operator with shell_exec() in
scripts/sql.php and scripts/ss_sql.php. Add escapeshellarg() to all
interpolated database credentials to prevent shell injection. Handle
null return from shell_exec() with null coalescing.
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
* test: verify sql scripts use shell_exec with escaped arguments
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>
* 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>
* add perl syntax checking for scripts
* Update .github/workflows/syntax.yml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* only show syntax errors
* Add support for ss
Fixed shell injection risk from piped netstat | grep by removing shell pipelines.
Added use strict; use warnings; and localized variables.
Added runtime fallback to ss when netstat is unavailable, with a clear failure when neither exists.
* Fix shell injection risk
apply fixes (safe open, strict/warnings, key whitelist, error handling).
* Update query_unix_partitions.pl
* Update unix_users.pl
add use strict/warnings to flag issues
add better error handling
* Update weatherbug.pl
* add better suport for gree bsd
* Update loadavg.pl
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
* Allow form_selectable_cell to more easily port
* Removing exclusion for scripts directory
* More fixes for scripts
* Some issues with ss_host_disk.php that passed locally
* More issues not detected locally
* Issues with typing in cacti_snmp_walk
* Adding base files for non-savy composer users
* One more try on ss_host_disk.php
* Aparantly I have to add the lock file
* More changes to lib/snmp.php after testing
* More changes to lib/snmp.php after testing
* Fix number script from Copilot review
* More Composer reviews
* Update scripts/ss_apache_stats.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update scripts/motorola_surfboard_SB6141.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update scripts/motorola_surfboard_SB6141.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Changing syntax of PHP requirement
* Updating composer
* Attempt to quiet some warnings
* Add Syntax Check
* Wrong file name
* Wrong file name
* Better to report before and after
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 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>
* Add Rector
* Add Rector
* Fix "Cashed" misspelling
Also harmonize the phrase with the one below it
* Fix implode error in db_table_create
Only relevant when multiple primary keys
* Fix auth issue, parentheses in the wrong location
Was causing too many arguments to be sent to cacti_sizeof which only take 1, instead of get_allowed_devices.
* Fix issue with session_var not properly using class variable
* Fix pass by reference error in get_allowed_devices
get_allowed_devices has a pass by reference on the "total_rows" field. It is used in several places to pass the total_rows back to a counter. Here though, we are passing a number directly to the function, which errors out with "Uncaught Error: get_allowed_devices(): Argument #4 ($total_rows) cannot be passed by reference". So we will just place a dummy variable in front to catch the pass.
This wasn't caught before because the previous parentheses issue.
* Fix undefined variable "field_array" in html_filter
* Fix undefined function 'get_nfilter_filter_request_var'
* Fix undefined variable 'resizable'
I corrected the function parameter, as that was technically the incorrect spelling
* Fix invalid variable $user_id
* Fix undefined vars in spikekill class
* Fix copy and paste error
Oops
* Forgot to use the dummy variables
* Add missing $
Oops
* Start converting to newer PHP Syntax and Functions
* Test fixing the workflow by install composer
* Lets not valid the composer file right now
Seems we have a few things to fix it in
* Validate composer
* Lite formating changes
* lite formatting
* lite formatting
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
* lite format changes
---------
Co-authored-by: TheWitness <thewitness@cacti.net>
* QA: Broken Function
* QA: Two additional QA Items
- Do a check for IP address as dns_get_records() does not have a timeout and I don't use DNS
- Properly display information about MariaDB 11.
* Fixing #5475 - Ping Issues with Advanced Ping an PHP8
PHP 8.x error - Unsupported operand types: float + string
* QA: Fix two typos