mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
* 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>
14 KiB
14 KiB
| 1 | class | location | match | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2 | cmd_exec | ./cactid.php | exec('pgrep -a php | grep cactid.php', $output); | |||||||||
| 3 | cmd_exec | ./cli/audit_database.php | exec('php ' . CACTI_PATH_PLUGINS . '/' . $pname . '/database_upgrade.php --type=large --force-ver=' . $old, $output, $return_var); | |||||||||
| 4 | cmd_exec | ./cli/audit_database.php | $db_shell = trim((string) shell_exec('which mysql')); | |||||||||
| 5 | cmd_exec | ./cli/audit_database.php | exec($cmd, $output, $error); | |||||||||
| 6 | cmd_exec | ./cli/audit_database.php | exec('php ' . CACTI_PATH_CLI . '/upgrade_database.php --debug', $output, $return_var); | |||||||||
| 7 | cmd_exec | ./cli/batchgapfix.php | exec($command, $output, $return_var); | |||||||||
| 8 | cmd_exec | ./cli/float_rrdfiles.php | $response = exec($command, $output, $return); | |||||||||
| 9 | cmd_exec | ./cli/float_rrdfiles.php | $response = exec($command, $output, $return); | |||||||||
| 10 | cmd_exec | ./cli/push_out_hosts.php | passthru($php_binary . ' ' . CACTI_PATH_CLI . '/rebuild_poller_cache.php ' . $parameters); | |||||||||
| 11 | cmd_exec | ./cli/remove_broken_graphs.php | $stty = shell_exec('stty size'); | |||||||||
| 12 | cmd_exec | ./cli/rrdresize.php | $process = proc_open($path_rrdtool . ' -', $fds, $pipes); | |||||||||
| 13 | cmd_exec | ./cli/splice_rrd.php | $result = exec($command, $output, $return_var); | |||||||||
| 14 | cmd_exec | ./cli/splice_rrd.php | $response = shell_exec(cacti_escapeshellcmd($rrdtool)); | |||||||||
| 15 | cmd_exec | ./cli/splice_rrd.php | shell_exec(cacti_escapeshellcmd($rrdtool) . ' dump ' . cacti_escapeshellarg($newrrd) . ' > ' . cacti_escapeshellarg($newxmlfile)); | |||||||||
| 16 | cmd_exec | ./cli/splice_rrd.php | shell_exec(cacti_escapeshellcmd($rrdtool) . ' dump ' . cacti_escapeshellarg($oldrrd) . ' > ' . cacti_escapeshellarg($oldxmlfile)); | |||||||||
| 17 | cmd_exec | ./cli/update_heartbeat.php | $result = exec($command, $output, $return_code); | |||||||||
| 18 | cmd_exec | ./cmd.php | $cactiphp = proc_open($command, $cactides, $pipes); | |||||||||
| 19 | cmd_exec | ./cmd_realtime.php | $cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . CACTI_PATH_BASE . '/script_server.php realtime ' . cacti_escapeshellarg($poller_id), $cactides, $pipes); | |||||||||
| 20 | cmd_exec | ./graph_realtime.php | shell_exec($php_binary . ' -q ' . $script_path . ' ' . $args); | |||||||||
| 21 | cmd_exec | ./host.php | shell_exec(cacti_escapeshellcmd((string) read_config_option('path_php_binary')) . ' -q ' . cacti_escapeshellarg(CACTI_PATH_CLI . '/poller_reindex_hosts.php') . ' --qid=all --id=' . $host_id); | |||||||||
| 22 | cmd_exec | ./lib/dsstats.php | $process = proc_open($command, $fds, $pipes); | |||||||||
| 23 | cmd_exec | ./lib/functions.php | $output = shell_exec($script_path); | |||||||||
| 24 | cmd_exec | ./lib/functions.php | $output = shell_exec($script_path); | |||||||||
| 25 | cmd_exec | ./lib/functions.php | $shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v 2>&1'); | |||||||||
| 26 | cmd_exec | ./lib/functions.php | $shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v'); | |||||||||
| 27 | cmd_exec | ./lib/functions.php | shell_exec($killCmd); | |||||||||
| 28 | cmd_exec | ./lib/functions.php | $cpu_cores = shell_exec('/bin/nproc'); | |||||||||
| 29 | cmd_exec | ./lib/functions.php | $cpu_cores = shell_exec('/usr/bin/nproc'); | |||||||||
| 30 | cmd_exec | ./lib/functions.php | $output = shell_exec('nproc'); | |||||||||
| 31 | cmd_exec | ./lib/functions.php | exec('id -nu', $o, $r); | |||||||||
| 32 | cmd_exec | ./lib/functions.php | $cpu_cores = shell_exec('sysctl -n hw.ncpu'); | |||||||||
| 33 | cmd_exec | ./lib/functions.php | $processExists = trim((string) shell_exec($checkProcessCmd)); | |||||||||
| 34 | cmd_exec | ./lib/functions.php | exec($command_line, $out, $return_code); | |||||||||
| 35 | cmd_exec | ./lib/functions.php | return exec('whoami') ?: ''; | |||||||||
| 36 | cmd_exec | ./lib/functions.php | * On Windows, exec()/shell_exec()/popen() route through cmd.exe, which ignores | |||||||||
| 37 | cmd_exec | ./lib/installer.php | $output = shell_exec( | |||||||||
| 38 | cmd_exec | ./lib/installer.php | $output = shell_exec(cacti_escapeshellcmd(str_replace('\\', '/', $path)) . ' --version 2>&1'); | |||||||||
| 39 | cmd_exec | ./lib/installer.php | $process = proc_open( | |||||||||
| 40 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 41 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1'); | |||||||||
| 42 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 43 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 44 | cmd_exec | ./lib/ping.php | $result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 45 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 46 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 47 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 48 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 49 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 50 | cmd_exec | ./lib/ping.php | $result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true)); | |||||||||
| 51 | cmd_exec | ./lib/ping.php | $result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1'); | |||||||||
| 52 | cmd_exec | ./lib/poller.php | $output = system($php_path . ' -l ' . $tmpfile, $exit); | |||||||||
| 53 | cmd_exec | ./lib/poller.php | $fp = popen($command, 'r'); | |||||||||
| 54 | cmd_exec | ./lib/poller.php | $fp = popen($command, 'rb'); | |||||||||
| 55 | cmd_exec | ./lib/poller.php | exec($safe_filename . ' ' . $args . ' ' . $redirect_args . ' &'); | |||||||||
| 56 | cmd_exec | ./lib/poller.php | exec($safe_filename . ' ' . $args . ' > /dev/null 2>&1 &'); | |||||||||
| 57 | cmd_exec | ./lib/poller.php | $output = shell_exec($command); | |||||||||
| 58 | cmd_exec | ./lib/poller.php | $setsid_path = trim((string) shell_exec('which setsid 2>/dev/null')); | |||||||||
| 59 | cmd_exec | ./lib/poller.php | $process = @proc_open($command, $descriptors, $pipes, null, null, ['bypass_shell' => true]); | |||||||||
| 60 | cmd_exec | ./lib/poller.php | $process = proc_open('exec ' . $cmd_full, $descriptors, $pipes); | |||||||||
| 61 | cmd_exec | ./lib/poller.php | * of the proc_open() and proc_close() functions (php 4.3+) | |||||||||
| 62 | cmd_exec | ./lib/poller.php | * @param array $pipes The array of r/w pipes returned from proc_open() | |||||||||
| 63 | cmd_exec | ./lib/poller.php | * @param mixed $proc_fd The file descriptor returned from proc_open() | |||||||||
| 64 | cmd_exec | ./lib/rrd.php | $process = proc_open([$path, '-'], $descriptors, $pipes, null, null, ['bypass_shell' => true]); | |||||||||
| 65 | cmd_exec | ./lib/rrdcheck.php | $process = proc_open($command, $fds, $pipes); | |||||||||
| 66 | cmd_exec | ./lib/snmp.php | // exec() does not capture, so the walk output can never contain either. | |||||||||
| 67 | cmd_exec | ./lib/snmp.php | exec($command, $snmp_value, $return_var); | |||||||||
| 68 | cmd_exec | ./lib/snmp.php | exec($command, $snmp_value, $return_var); | |||||||||
| 69 | cmd_exec | ./lib/snmp.php | exec($command, $snmp_value, $return_var); | |||||||||
| 70 | cmd_exec | ./lib/snmpagent.php | // add pollers of a distributed system (future) | |||||||||
| 71 | cmd_exec | ./lib/spikekill.php | $dump_output = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' dump ' . cacti_escapeshellarg($this->rrdfile) . ' 2>&1 > ' . cacti_escapeshellarg($xmlfile)); | |||||||||
| 72 | cmd_exec | ./lib/spikekill.php | $response = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' restore -f -r ' . cacti_escapeshellarg($xmlfile) . ' ' . cacti_escapeshellarg($rrdfile)); | |||||||||
| 73 | cmd_exec | ./lib/utility.php | $nproc = system('nproc'); | |||||||||
| 74 | cmd_exec | ./lib/utility.php | exec('/usr/bin/free', $output, $exit_code); | |||||||||
| 75 | cmd_exec | ./lib/utility.php | exec('wmic os get FreePhysicalMemory', $memInfo['FreePhysicalMemory']); | |||||||||
| 76 | cmd_exec | ./lib/utility.php | exec('wmic os get FreeSpaceInPagingFiles', $memInfo['FreeSpaceInPagingFiles']); | |||||||||
| 77 | cmd_exec | ./lib/utility.php | exec('wmic os get FreeVirtualMemory', $memInfo['FreeVirtualMemory']); | |||||||||
| 78 | cmd_exec | ./lib/utility.php | exec('wmic os get SizeStoredInPagingFiles', $memInfo['SizeStoredInPagingFiles']); | |||||||||
| 79 | cmd_exec | ./lib/utility.php | exec('wmic os get TotalVirtualMemorySize', $memInfo['TotalVirtualMemorySize']); | |||||||||
| 80 | cmd_exec | ./lib/utility.php | exec('wmic os get TotalVisibleMemorySize', $memInfo['TotalVisibleMemorySize']); | |||||||||
| 81 | cmd_exec | ./lib/utility.php | $json = shell_exec($php . ' -q ' . $php_file); | |||||||||
| 82 | cmd_exec | ./lib/utility.php | $json = shell_exec($php . ' -q ' . $php_file); | |||||||||
| 83 | cmd_exec | ./lib/utility.php | $json = shell_exec($php . ' -q ' . $php_file); | |||||||||
| 84 | cmd_exec | ./poller_spikekill.php | $response = exec(cacti_escapeshellcmd((string) read_config_option('path_php_binary')) . ' -q ' . | |||||||||
| 85 | cmd_exec | ./remote_agent.php | $cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . CACTI_PATH_BASE . '/script_server.php realtime ' . cacti_escapeshellarg($poller_id), $cactides, $pipes); | |||||||||
| 86 | cmd_exec | ./scripts/sql.php | $sql = shell_exec($cmd); | |||||||||
| 87 | cmd_exec | ./scripts/ss_netsnmp_lmsensors.php | $virtual = intval(shell_exec('/usr/bin/hostnamectl 2> /dev/null | grep -i virtual | wc -l')); | |||||||||
| 88 | cmd_exec | ./scripts/ss_sql.php | $result = shell_exec($cmd) ?? ''; | |||||||||
| 89 | cmd_exec | ./snmpagent_mibcache.php | exec($php . ' ' . $extra_args . ' > /dev/null &'); | |||||||||
| 90 | cmd_exec | ./snmpagent_persist.php | exec($php . ' ' . $extra_args . ' > /dev/null &'); | |||||||||
| 91 | cmd_exec | ./support.php | $out = shell_exec(cacti_escapeshellarg($path) . ' ' . $arg . ' 2>&1'); | |||||||||
| 92 | cmd_exec | ./support.php | print '<td>' . tech_env_status(DB_STATUS_WARNING, __('Version unavailable: shell_exec() is disabled')) . '</td>'; | |||||||||
| 93 | cmd_exec | ./support.php | exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')), $out_array); | |||||||||
| 94 | cmd_exec | ./support.php | $cli_json = shell_exec(cacti_escapeshellcmd($php_binary) . ' -q ' . cacti_escapeshellarg(CACTI_PATH_INSTALL . '/cli_check.php') . ' extensions'); | |||||||||
| 95 | cmd_exec | ./support.php | $snmp_version = (string) shell_exec(cacti_escapeshellcmd($snmpget_path) . ' -V 2>&1'); | |||||||||
| 96 | cmd_exec | ./support.php | exec(cacti_escapeshellcmd(read_config_option('path_spine')) . ' --version', $out_array); | |||||||||
| 97 | dynamic_include | ./cli/audit_database.php | require_once($plugin . '/includes/database.php'); | |||||||||
| 98 | dynamic_include | ./cli/audit_database.php | require_once($plugin . '/setup.php'); | |||||||||
| 99 | dynamic_include | ./cli/upgrade_database.php | include($upgrade_file); | |||||||||
| 100 | dynamic_include | ./include/global.php | require_once($vendor_autoload); | |||||||||
| 101 | dynamic_include | ./include/global_languages.php | require_once($providerFull); | |||||||||
| 102 | dynamic_include | ./index.php | require_once($file); | |||||||||
| 103 | dynamic_include | ./lib/installer.php | include_once($upgrade_file); | |||||||||
| 104 | dynamic_include | ./lib/plugins.php | include_once($full_path); | |||||||||
| 105 | dynamic_include | ./lib/plugins.php | require_once($setup_file); | |||||||||
| 106 | dynamic_include | ./lib/renderer.php | include $__cacti_template_file; | |||||||||
| 107 | dynamic_include | ./lib/rrd.php | include($rrdtheme); | |||||||||
| 108 | dynamic_include | ./lib/rrd.php | include($themefile); | |||||||||
| 109 | dynamic_include | ./link.php | require_once($file); | |||||||||
| 110 | dynamic_include | ./plugins.php | require_once($setup_file); | |||||||||
| 111 | dynamic_include | ./reports.php | require_once($reportit_api); | |||||||||
| 112 | dynamic_include | ./script_server.php | require_once($include_file); | |||||||||
| 113 | dynamic_include | ./scripts/ss_mikrotik_snmpget.php | include_once($config['base_path'] . '/lib/snmp.php'); | |||||||||
| 114 | dynamic_include | ./scripts/ss_netsnmp_lmsensors.php | include($config['base_path'] . '/lib/snmp.php'); | |||||||||
| 115 | dynamic_include | ./snmpagent_persist.php | require($path_mibcache); | |||||||||
| 116 | host_header | ./lib/auth.php | $result['link'] = \Sonata\GoogleAuthenticator\GoogleQrUrl::generate($current_user['username'] . '@' . $_SERVER['HTTP_HOST'], $current_user['tfa_secret'], 'Cacti'); |