mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-31 00:57:11 +09:00
fix/graph-wrapper-date-range-develop
434
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7b551eea72 |
chore: normalize CRLF line endings to LF across Cacti-owned files (#8156)
Excludes vendored packages, third-party JS, patches, and the Win32 probe fixture. Pure EOL change; guards pass and php -l is clean. |
||
|
|
cff89ca9d6 |
security: batch 8 (develop) - data-query XML oracle (GHSA-2x86) + batch-8 GHSAs under 1.2.32 CHANGELOG (#8151)
* security: batch 8 develop - confine data-query XML existence check (GHSA-2x86); list batch-8 GHSAs under the 1.2.32 CHANGELOG release * security: batch 8 (develop) - address review: use cacti_path_is_within() for XML existence-oracle containment with a NUL-byte guard (no realpath ValueError); add data_query_edit() UI hardening regression test --------- Co-authored-by: Larry Adams <TheWitness@users.noreply.github.com> |
||
|
|
9facd9b500 |
security: batch 7 develop - consolidate remaining develop-only GHSA advisories (12 fixes + 8 confirmations) (#8149)
* security: batch 7 develop - consolidate remaining develop-only GHSA advisories (12 fixes + 8 regression confirmations) Fixes: f7jw (automation preview XSS), rp5g (db_column_exists LIKE), vx2m (automation pivot CASE WHEN), h5wg (plugin DDL identifiers), 929h (aggregate LIKE filter), mfw8 (add_graphs REGEXP), m49v (user_admin sort_column allowlist), mpfm (rrd error-image theme allowlist), m3fh (SNMPv3 control chars), cg45 (remember-me row revoke), xq26 (remember-me retention window), m67r (import xml_path containment). Confirmed already fixed on develop, pinned with regression tests: 4gph, 977w, mgcq, 5p79, cfhh, wfcf, pjq5, wp33. Excluded: 227p (1.2.x-only), mx5c (maintainer private branch). * security: address batch-7 review feedback (lexical-first xml_path containment, session-wide sort allowlist, connection-aware db_column_exists quoting, reject-not-normalize plugin DDL identifiers, behavior-based test names) * style: apply php-cs-fixer single-quote fix to api_automation pivot builders; add WSL dev-environment note to copilot instructions * fix phpstan string typing * test: refresh SQL interpolation baseline for db_column_exists db_qstr change (database.php 9->8) --------- Co-authored-by: ci <ci@local> |
||
|
|
5e870a91df |
security: batch 6 develop - path_php_binary escape (GHSA-4p7f), boost-log .log guard (GHSA-m6wx), tree lock/unlock/get_node authz (GHSA-73qf), rrd create strip (GHSA-967c) (#8148)
* security: batch 6 develop - escape path_php_binary (GHSA-4p7f), boost-log .log guard (GHSA-m6wx), tree lock/unlock/get_node authz (GHSA-73qf), confirm rrd create control-char strip (GHSA-967c) * security: cast path_php_binary before escaping in resource_cache_out (GHSA-4p7f) |
||
|
|
9d628b4959 |
security: batch 5 develop - fail closed on No-Auth admin bypass (GHSA-69vw), tag GHSA-3jj2, add 1.2.32 CHANGELOG section (#8146)
* security(GHSA-69vw): fail closed on auth_method None instead of auto-granting an admin session - check_reset_no_authentication() no longer wipes the admin password, flips auth_method, or hands an unauthenticated request an admin session; it fails closed and requires a manual auth_method reset (GHSA-69vw-qxxv-p8w2). - Tag GHSA-3jj2-v5ch-wmq5 in the CHANGELOG: the domains_login_process() realm bypass is already closed by the Login Providers rewrite (realm < 1000 fails closed). - CHANGELOG: add a 1.2.32 section below 1.3.0-dev with the GHSAs also fixed in 1.2.x, and de-duplicate the security entries the #8145 merge doubled. * docs(CHANGELOG): file host_id SQLi fix under 1.2.31 as GHSA-r7hc-hrpv-52gf; drop plain issue#7693 entry |
||
|
|
41e6d9e033 |
security: consolidate verified GHSA fixes for develop (batch 4) (#8145)
* 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 |
||
|
|
0055dd00cc |
security: consolidate verified GHSA fixes for develop (batch 3) + CHANGELOG CVE reconciliation (#8141)
* security: consolidate verified GHSA fixes for develop (batch 3) Develop counterparts of the 1.2.x fixes in #8138, each re-verified against develop HEAD. - GHSA-77pr-g949-4v5f: spikekill.php also requires is_graph_allowed() on the target graph, not just the Spike Kill realm. - GHSA-w3qw-762w-6g9v: the include/global.php GET action guard fails closed on an array-form action[] before the scalar denylist comparison. - GHSA-xhpr-w454-cc9w: update_poller_cache() casts the stored output_type to int before interpolation. - GHSA-j3px-vw6r-g25x: graphs_new.php escapes stored snmp_index via db_qstr() in the data-query IN() pivot. - GHSA-h745-3r76-g253: graphs_new.php escapes the data-query and field names in the XML parse error messages (__ -> __esc). GHSA-297h-95ff-r767 does not apply on develop: the clog log-file selector renders through the drop_array filter framework, which escapes. tests/Unit/Security/ConsolidatedBatch3Test.php adds source-scanning regression guards. CHANGELOG (1.3.0-dev) updated; php -l, the security baselines, and check-changelog.py all pass. * chore: lead 1.3.0-dev security CHANGELOG entries with the GHSA id Normalize the 1.3.0-dev -security entries to the approved "-security: GHSA-xxxx [CVE-xxxx] Description" form, moving the advisory id to the front. CVE tokens are omitted because none of these advisories have a CVE issued yet (verified via the repository security-advisories API). Covers the five batch-3 entries plus the pre-existing entries that still used the trailing (GHSA-xxxx) form. * chore: use the -security#GHSA-xxxx: form for the 1.3.0-dev CHANGELOG entries Correct the batch-3 and reconciled 1.3.0-dev -security entries to the approved "-security#GHSA-xxxx: CVE-yyyy Description" form (GHSA id in the prefix). No CVE tokens are added because none of these advisories have a CVE issued yet (verified via the repository security-advisories API). * test: split batch-3 security guards into vulnerability-class files; reconcile CHANGELOG - Move the ConsolidatedBatch3Test.php guards into the matching Auth, Csrf, SqlInjection and Xss folders with behavior-based filenames, and strengthen them per review: the GET-guard test asserts the array branch actually returns 405 and exits; the snmp_index test rejects both raw concatenation branches; the parse-error test asserts every name-bearing message uses __esc(). - Append this pull request number to the five new 1.3.0-dev security entries. - Add issued CVE identifiers to the older -security#GHSA CHANGELOG entries (verified via the repository security-advisories API). Entries with no CVE yet, entries that already carried a CVE, and non-GHSA lines are unchanged. |
||
|
|
850778aa5b |
ci: refresh security/SQL-interpolation baselines (#8140)
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> |
||
|
|
3ab4c69fb7 |
PHPStan Level 8 - Phase 1 Conversion (#8135)
* PHPStan Level 8 - Phase 1 Conversion
Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).
Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
return array (was mixed); the _prepared workers coerce the connection-failure
path to an empty array so the declared type is honest. empty()/cacti_sizeof()
checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
now return self instead of a bare object so chained calls type-check.
Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
(json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
wrap foreach over array|false/list|false results with `?: []`.
Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
entry (passing null to api_tree_item_save(int $type) -> TypeError), and
api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
(an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.
Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
existing always-true/false ignore family (same "PHPStan gets confused"
dynamic-constant cause already covered by identical.alwaysTrue,
notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
untouched long tail for Phase 2.
* Remove redundant @phpstan-ignore comments
Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:
- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
match any reported error after the type-safety fixes (e.g. the
db_fetch_row_prepared() return-type narrowing removed the offset-assign
errors the api_automation ignores were suppressing).
Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.
* ci: satisfy validator ratchet and refresh security sink baselines
- lib/api_tree.php: give the new tree-item site_id a real numeric
constraint ('^[-0-9]+$', matching html_reports.php) instead of an
empty regex, so the validator constraint ratchet passes without
bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
whose code text changed from the (string) casts added in this branch
(lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
simplexml_load_string).
* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test
The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.
* test: update sql.php null-handling assertion for the (string) cast
This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.
* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound
- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
(guarding the false lookup) instead of string-casting them, which made
them resolve account/group names like '1001' and fail. Covers
lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
lib/package.php returns false, lib/clog_webapi.php and utilities.php
render an error, and snmpagent_mibcachechild.php skips the cache write
and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
only the array offset, so a non-even entries/columns split no longer
truncates the loop and drops a cached row.
* revert: keep db_fetch_* returning false on error; baseline Level 8 instead
Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.
Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.
---------
Co-authored-by: TheWitness <copilot@users.noreply.github.com>
|
||
|
|
6251fed1dc |
Make the drop_multi form method use Select2 by default (#8130)
* form_multi_dropdown: default drop_multi to select2, keep legacy opt-in The drop_multi form method now renders a select2 'N Selected' multi dropdown by default instead of the legacy jquery-multiselect widget, closing a gap left from the earlier select2-multi-count work (html_graph/html_tree already opted in explicitly). A caller keeps the legacy jquery-multiselect widget by including 'multiselect' in its field class; the Scheduler fields used by Automation Networks (lib/api_scheduler.php), Data Source Profiles consolidation functions (global_form.php) and the Settings selective-debug/spikekill pickers (global_settings.php) are marked accordingly so their existing per-page .multiselect() initializers keep working unchanged. Also drops the duplicate class concatenation the old code emitted. * settings: convert selective debug and spikekill template pickers to select2 Mirror of the 1.2.x conversion. Drop the inline .multiselect() initializers for the Logging-tab Selective File/Plugin Debug pickers and the Spikes-tab 'Graph Templates to Spike Kill' picker, and remove their 'multiselect' opt-in class, so applySkin() renders them as the select2 'N Selected' multi dropdown. Each keeps its wording via per-field select_all_text/select_count_text (the select2-multi-count data-attribute mechanism used for graph_template_id): 'Select File(s)'/'Files Selected', 'Select Plugin(s)'/'Plugins Selected', and 'Select Template(s)'/'Templates Selected'. Empty stays the placeholder text (poller_spikekill.php treats no selection as none). * settings: align array operators in debug/spikekill defs (php-cs-fixer) * html_form: legacy opt-in strips select2-multi-count; add drop_multi tests; fix stale docblock When a drop_multi field carries both 'multiselect' and 'select2-multi-count', the legacy opt-in now wins: the select2-multi-count token is stripped so applySkin() does not also decorate the field and collide with the page's own .multiselect() init. Adds tests/Unit/Ui/Html/FormMultiDropdownSelect2Test.php covering the default injection, legacy opt-in, preserved plugin class, no duplication, and the both-token precedence (either order collapses to the legacy widget). Also drops the stale 'Settings selective debug' example from the helper comment since this PR converts those fields to select2. |
||
|
|
bb92c46e78 |
feat(auth): automatic User Group assignment from Login Provider groups (#8129)
* feat(auth): automatic User Group assignment from Login Provider groups Add per-Login-Provider "Auto Assignment" rules to the User Group editor so a user is added to (or removed from) a Cacti User Group at every login based on the groups their Login Provider reports. User Group editor (user_group_admin.php): - Add General / Auto Assignment / Rights spacers to the General tab and rename "General Group Options" to "Group Options". - Render one text field per eligible Login Provider under Auto Assignment (label = provider name). The admin enters a plain Group Name (e.g. "Cacti Admins"), never a DN. - Store the rules as JSON in the new user_auth_group.auto_assignments column, keyed by login_providers.id. Group resolution: - SAML2 and OpenID match the configured name against the group attribute / group claim values. - LDAP/AD resolve membership live against the directory using a raw group name plus the Search Base, trying AD (memberOf + nested LDAP_MATCHING_RULE_IN_CHAIN), FreeIPA / 389 DS (groupOfNames/groupOfUniqueNames) and RFC2307 posixGroup shapes so one plain name works everywhere. This requires a directory-read bind, so the field is only offered for Anonymous Searching or Specific Searching with a stored service account. - A directory that cannot be reached returns "undetermined" so existing memberships are left untouched rather than revoked on a transient outage. Lifecycle: - auto_assignments is reconciled on every login (lib/auth.php credential flow and login_sso.php redirect flow). - Deleting a Login Provider strips its key from every group's auto_assignments. Schema: add user_auth_group.auto_assignments (cacti.sql + 1_3_0 upgrade). * ci: document free-text group-name validateInput for constraint ratchet * fix(auth): address PR review on automatic User Group assignment - LDAP: add an Active Directory bare-name query (objectClass=group with the member:1.2.840.113556.1.4.1941 nested-membership rule) so a plain group name resolves on AD, which neither the posixGroup nor groupOfNames probe could match. - LDAP: make single-group membership tri-state and propagate an "undetermined" result, so a search that fails after binding (timeout/dropped connection) no longer looks like confirmed non-membership and revokes assignments. - SAML2/OpenID: supportsAutoAssignment() now requires a configured group claim, and applyAutoAssignments() skips any provider that is not currently capable, so a blank claim can never strip users from their groups. - Only reset the permission cache when a membership row actually changed (accumulate db_affected_rows) rather than on every login that has a rule. - Add unit tests for the reconciliation boundaries (add / remove / no-op / incapable / undetermined) and for the LDAP group-query construction (AD plain + nested, posixGroup, groupOfNames, DN mode). - Regenerate locales/po/cacti.pot for the new UI strings. * fix(phpstan): resolve level-8 errors from auto group assignment - Guard supportsAutoAssignment() behind an instanceof AbstractLoginProvider check so it is not called on the base LoginProviderInterface. - Assign the injected field list to a variable (inject_form_variables takes it by reference) and pass an is_array-guarded group row. - Bump the lib/ldap.php ldap_get_entries baseline count 3->6 for the three new group-membership query helpers. |
||
|
|
0415606710 |
fix: skip replication-ignored files in cache_in_path() to stop poll-cycle log spam (#8127)
* fix: skip replication-ignored files in cache_in_path() to stop poll-cycle log spam cache_in_path() emitted a 'Detecting Resource Change' log and called update_db_from_path() for tooling artifacts (.gitignore, .mdlrc, tests/, ...) that update_db_from_path() then declines to cache. Because their md5 is never stored, every poll cycle re-detected them as changed, spamming the Cacti log once per minute. Guard cache_in_path() with should_ignore_from_replication() up front so these paths are skipped entirely. * test: assert cache_in_path() consults the ignore predicate before logging/updating Regression guard for the per-poll-cycle log spam fix. cache_in_path() runs change detection before update_db_from_path(), so it must short-circuit on should_ignore_from_replication() up front. Without that early return an ignored dotfile (.gitignore, .mdlrc, ...) is logged and re-processed every cycle because its md5 is never stored and it is forever re-detected as "changed". The new test pins the guard ahead of the first change-detection log line in cache_in_path(). * fix: restore literal <?php opening tag in replication exclusion test The previous commit stored the opening tag as an HTML entity, which would make the test file fail to parse. |
||
|
|
462a637367 |
fix: accept scalar or array primary in api_plugin_db_table_create() (#8124)
* fix: accept scalar or array primary in api_plugin_db_table_create() api_plugin_db_table_create() inline-interpolated $data['primary'] as a string, so a plugin passing an array primary produced PRIMARY KEY (`Array`) and table creation failed on a fresh install. Route the primary through db_format_index_create() (already used for the keys just below), which accepts either a scalar column name (the legacy string form, required for backward compatibility) or an array of columns -- matching db_table_create() and db_update_table(). * tests: cover scalar and array/composite primary DDL in api_plugin_db_table_create() |
||
|
|
d4f2bd1f3b |
Restore legacy unique_keys[] support in the schema API (#8116)
* Restore legacy unique_keys[] support in the schema API develop expresses a unique index as a `keys[]` entry with `'unique' => true`, and both db_table_create() and db_update_table() were rewritten to only read that form. Plugins that still use the legacy `unique_keys[]` definition style (supported on 1.2.x) therefore get no UNIQUE index created, and any existing one dropped on the next schema refresh. Fold a legacy `unique_keys[]` definition into `keys[]` with the unique flag at the top of both functions, so the existing unique-aware key handling applies to it. New-style `keys[]` with `'unique' => true` continues to work unchanged. * CHANGELOG: reference PR #8116 * Fix CHANGELOG format and add unique_keys[] regression coverage CHANGELOG: use the -issue#8116 entry form (the -bug prefix and trailing (#NNNN) failed the CHANGELOG format check) and place it in the issue block so the security-issue-feature ordering holds. Adds DbUpdateTableUniqueKeysTest covering db_table_create() emitting the UNIQUE index and db_update_table() adding a missing one and preserving an existing one for a legacy unique_keys[] definition. FakeMySQLPDO now projects Seq_in_index/Column_name/Non_unique for named secondary indexes so the reconciliation path can be exercised. |
||
|
|
ac2445fab5 |
Exclude 'tests' directories from resource replication (#8118)
* poller: exclude 'tests' directories from resource replication should_ignore_from_replication() now skips any path with a 'tests' segment, covering Cacti's own tests/ and every plugin's tests/ for both the cache-in (md5sum_path/update_db_from_path) and cache-out (resource_cache_out) directions. update_resource_cache()'s plugin scan also adds 'tests' to its directory exclusions so a plugin's tests/ tree is never cached in the first place. Adds a Pest unit test covering both directions and the plugin-scan exclusion. * poller: purge stale excluded rows, guard collector dirs, strip install root Address review feedback on #8118: - should_ignore_from_replication() now strips CACTI_PATH_BASE from absolute file paths before segment matching, so an installation living under a 'tests' ancestor (e.g. /srv/tests/cacti) no longer rejects ordinary files. - update_resource_cache() purges rows that are now excluded even when their source file still exists, dropping tests/* rows cached before the exclusion. - The remote collector skips excluded plugin rows before building directories, so it no longer recreates plugins/foo/tests/ from stale cache rows. Adds regression tests for the absolute-path handling and both source-level guards. |
||
|
|
44a14fb5e8 |
ci: refresh security/SQL-interpolation baselines (#8108)
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> |
||
|
|
599c76b369 |
Add Force Uninstall for orphaned plugins whose directory is missing (develop) (#8107)
* Add Force Uninstall for orphaned plugins with a missing directory (#8105) The Plugin Management page showed a directory-missing plugin (status -5) with only an inert info icon, so its stale plugin_config/hooks/realms/db_changes entries could not be cleaned up from the UI. Make that icon a Force Uninstall action guarded by the existing displayDialog() modal confirmation; on confirm it POSTs action=remove, which calls api_plugin_uninstall(plugin, true) - that skips the unavailable uninstall hook and removes the plugin's hooks, realms, plugin_config row, and the tables/columns it created (via plugin_db_changes). * Force Uninstall: POST via dialog form to satisfy CSRF action guard; add cacti.pot strings * Force Uninstall: restrict to truly-orphaned plugins, block if required by others, replicate config to remote pollers * Refresh sink inventory baseline for Force Uninstall header redirects in plugins.php * Force Uninstall: post action=remove via dialog form fields (not a URL) and JSON-inject dialog strings to satisfy CsrfGetDeleteTest and the inline-JS hardening guard --------- Co-authored-by: Copilot <copilot@users.noreply.github.com> |
||
|
|
54f1dbabdd |
security: validate host_id before the graph_view filter WHERE clauses (GHSA-r7hc-hrpv-52gf) (#8100)
* security: validate host_id before the graph_view filter WHERE clauses (GHSA-r7hc-hrpv-52gf) * security: cover graph_view host_id WHERE sinks with regression tests and add PR ref (GHSA-r7hc-hrpv-52gf) --------- Co-authored-by: Copilot <copilot@example.com> |
||
|
|
e66a324d1a |
security: validate local_graph_id before the aggregate graph item sequence query (GHSA-6mjm-5vwr-3fr2) (#8099)
* security: validate local_graph_id before the aggregate graph item sequence query (GHSA-6mjm-5vwr-3fr2) * security: bind local_graph_id via array filter and add regression test (GHSA-6mjm-5vwr-3fr2) |
||
|
|
46908e350b |
fix(ci): pin htmx.org back to 2.0.10 to match vendored asset and SRI (#8091)
* fix(ci): pin htmx.org back to 2.0.10 to match vendored asset and SRI The npm group bump #8085 raised htmx.org to 4.0.0, but the vendored include/js/htmx.js, the HTMX_2_0_10_SRI integrity constant, and the htmx.js.version file were not regenerated. CI's 'Build managed JavaScript' step (build/sync-js.mjs) copies node_modules/htmx.org/dist/htmx.js, so it shipped 4.0.0 while htmx_script_tag() still emitted the 2.0.10 SRI, failing tests/Unit/Ui/Html/HtmxTest. Revert htmx.org to the vendored/pinned 2.0.10; a real htmx 4.x upgrade should be a separate, tested PR. * fix(htmx): sync version file, tests and docs to vendored 2.0.10 |
||
|
|
fe45229820 |
ci: refresh security/SQL-interpolation baselines (#8086)
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> |
||
|
|
d57431371b |
perf(poller/boost): cached per-data-source field-name lookups, remove batched prefetch (#8078)
* perf(poller/boost): replace batched field-name prefetch with cached per-data-source lookups poller_prefetch_rrd_field_names() (issue#7534) batched the unused/nt field-name lookups into large IN() lists and rebuilt them on every process_poller_output()/boost_process_local_data_ids() call, so the completion-wait loop re-ran them each pass (DB thrash) and the IN() lists risk crashing some MariaDB/MySQL releases and do not scale to multi-million-data-source installs. Option B (full): remove poller_prefetch_rrd_field_names() and replace it with two small per-data-source helpers - poller_get_unused_data_source_names() and poller_get_nt_rrd_field_names() - each a single indexed WHERE local_data_id = ? query cached by local_data_id in a caller-held static, so every data source is queried at most once per poller process. Templated field names resolve from the static poller_data_template_field_mappings cache (the Data Template dictates); only non-templated (data_template_id == 0) manual sources hit the per-instance query - a rare exception. Both process_poller_output() (lib/poller.php) and boost_process_local_data_ids() (poller_boost.php) now use the helpers. Added a WARNING documentation block to both bulk hot-path functions spelling out the query-discipline rules for future maintainers/AI (minimal queries, no query in the per-row loop, avoid large IN() lists and full-table scans, single pass, static template-dictated caching). Updated BoostProcessBugsTest assertions to the new helpers and removed the now-obsolete BoostPrefetchRrdFieldNamesTest integration file. * fix(poller/boost): split GROUP_CONCAT'd field mappings; guard orphan-name query to templated sources; add coverage * boost: normalize orphan-name guard to ternary at third call site The reset_template branch in boost_process_local_data_ids() guarded the poller_get_unused_data_source_names() call with an if/else block rather than the ternary used at the other two call sites. Normalize it so all three sites read identically and the template-id guard is uniform. |
||
|
|
f030e5cc60 |
feat(login_providers): form field hints, cert/private-key display, and encrypted storage (#8074)
* feat(login_providers): add form hints, cert/private-key display, and Weblate strings
- Adds example.com-style placeholders across the LDAP/SAML2/OpenID Login Provider fields (server, DN/group DN/search filter, entity IDs, SSO/SLO/discovery URLs, group claims), so the expected syntax is visible without opening the docs.
- Adds a 'textcert' form method (form_cert_box()) for pasted PEM certificates: styled like filepath/dirpath, it parses the certificate and shows its expiration date, using the deviceUp/deviceDown status classes ('Good Till: YYYY-MM-DD' / 'Expired: YYYY-MM-DD'), or flags invalid input. Applied to SAML2's SP/IDP certificate fields.
- Adds a 'privkey' form method (form_privkey_box()) for the SAML2 SP Private Key: never redisplays the stored value (leaving it blank on save keeps the existing key); the status area shows '[stored]' or '[not set]' instead.
- Adds cacti_encrypt_secret()/cacti_decrypt_secret() (lib/auth.php), an AES-256-CBC envelope with a per-installation key persisted in \settings\ on first use, modeled on plugin_servcheck's servcheck_encrypt_credential()/servcheck_decrypt_credential(). The SP Private Key is now encrypted at rest; AbstractLoginProvider::existingParameter()/encryptOrKeepExisting() preserve the existing encrypted value when the privkey field is left blank.
- Regenerates locales/po/cacti.pot so Weblate picks up the new strings.
- Adds tests/Unit/Security/Auth/LoginProviderSecretFieldsTest.php (10 tests) covering the encrypt/decrypt round-trip, IV uniqueness, failure modes, and both new form widgets' rendering.
Verified: PHPStan clean, full Pest Security suite passes (835 tests including the 10 new ones), php-cs-fixer clean.
* fix(login_providers): never redisplay stored cert/key, preserve on blank save
form_cert_box() previously echoed the stored PEM certificate back into the textarea, and form_save() overwrote sp_x509cert/idp_x509cert with an empty string whenever the field was submitted blank - unlike sp_private_key, which already had blank-preserves-existing handling.
- form_cert_box() now behaves like form_privkey_box(): the textarea is always rendered blank (only repopulating from a failed same-submission's SESS_FIELD_VALUES, never the stored certificate); the expiration/status indicator is still computed from the stored value.
- Added AbstractLoginProvider::keepExistingIfBlank(), the non-secret counterpart to encryptOrKeepExisting(): a blank submission keeps the existing stored value instead of clearing it. Wired into SamlLoginProvider::collectParameters() for both sp_x509cert and idp_x509cert.
- Extended tests/Unit/Security/Auth/LoginProviderSecretFieldsTest.php: form_cert_box() never leaks the stored certificate into the rendered HTML, and keepExistingIfBlank()/encryptOrKeepExisting() resolve correctly for both a fresh submission and a blank field with no existing row (no database access needed for either case).
Verified: PHPStan clean, full Pest Security suite passes (838 tests), php-cs-fixer clean.
* fix(login_providers): address Copilot review round 1 + CI formatting
- CI: fixed a php-cs-fixer docblock indentation mismatch in lib/auth.php.
- Upgraded to actual authenticated encryption: the secret envelope is now AES-256-GCM (IV + 16-byte tag + ciphertext) instead of plain AES-256-CBC, which had no MAC/tag - a tampered database value now fails to decrypt instead of silently producing modified plaintext.
- cacti_secret_key() now fails closed on a corrupt persisted key (not valid base64, or not exactly 32 bytes) by throwing, instead of silently substituting an unpersisted random key that would desync from every existing/future ciphertext. cacti_decrypt_secret() catches this and degrades to false (logged); cacti_encrypt_secret() lets it propagate so a broken key never results in a secret being silently stored insecurely or lost.
- Added a backward-compatible read path for the SP Private Key: a provider whose parameters still hold the raw PEM from before this feature existed (cacti_decrypt_secret() returns false because it isn't a valid envelope) now falls back to using that value directly instead of losing the key. AbstractLoginProvider::encryptOrKeepExisting() opportunistically re-encrypts such a legacy value the next time the form is saved (even with the privkey field left blank), rather than requiring the admin to re-paste it.
- form_cert_box()/form_privkey_box() no longer repopulate from SESS_FIELD_VALUES: form_input_validate() writes that session value on every submission (including a successful save), and the save's redirect runs before bottom_footer() clears it - the immediate next page load was echoing the just-submitted plaintext private key/certificate back into the textarea. Both fields are now unconditionally blank.
Verified: PHPStan clean, full Pest Security suite passes (841 tests, 6 new), php-cs-fixer clean (fresh cache, whole-repo dry-run).
|
||
|
|
a52368125e |
feat(auth): rename User Domains to Login Providers, add SAML2 and OpenID Connect (#8066)
* feat(auth): rename User Domains to Login Providers Collapses user_domains/user_domains_ldap into a single login_providers table with a JSON parameters column, migrated in place during the 1.3.0 upgrade. Renames the admin page, menu, breadcrumbs, and help links to match. * feat(auth): rework login providers as Cacti\Auth classes Introduces LoginProviderInterface with credential (LDAP/AD) and redirect (SAML2/OpenID) flavors, replacing the procedural domains_login_process()/domains_ldap_*() functions. Adds blank-field group membership gating shared by every provider type. * feat(auth): add SAML2 and OpenID Connect login providers Adds onelogin/php-saml and firebase/php-jwt, a new cacti_http() SSRF-hardened outbound request helper for every OpenID discovery/token/JWKS call, and login_sso.php to handle the redirect-based SSO flow. Login page now shows a button per enabled SAML2/OpenID provider, and per-provider auth cookie opt-out is enforced on both the password and SSO login paths. * chore(i18n): regenerate cacti.pot * feat(auth): add Local and Basic auth as Cacti\Auth providers LocalAuthLoginProvider (CredentialLoginProviderInterface) and BasicAuthLoginProvider (new PreAuthenticatedLoginProviderInterface, since Basic Auth has no password for Cacti to check) are thin adapters over the existing local_auth_login_process()/basic_auth_login_process() functions, which are left untouched to avoid disturbing their regression-tested lockout/timing/rehash behavior. * feat(auth): move local password verification into LocalAuthLoginProvider secpass_login_process() and local_auth_login_process() are retired; their lockout, constant-time verification, forced-password-change, and rehash-on-login logic now live end to end in the class (verifyCredential/enforcePasswordPolicy/rehashIfNeeded). secpass_check_pass() stays a shared lib/auth.php function since password change/reset/user_admin also call it outside any login flow. Local auth still has no login_providers row: isEnabled() is always true and it cannot be disabled or deleted. * feat(auth): move Login Provider CRUD into Cacti\Auth classes AbstractLoginProvider gains static deleteById()/enableById()/disableById()/makeDefaultById()/persist(); each concrete provider owns its own collectParameters(), dispatched via LoginProviderFactory. login_providers.php's form_save()/form_actions() call these instead of doing raw SQL, and the retired provider_remove()/provider_disable()/provider_enable()/provider_default()/provider_collect_parameters() procedural functions are gone. Local/Basic auth are unaffected: they never extend AbstractLoginProvider, so they structurally cannot expose delete/disable. * fix(auth): address Copilot review findings on SSO/SAML/OIDC security Fixes callback/ACS/SLS/metadata URLs that used the raw provider id instead of the login realm (broke every SSO login), adds InResponseTo correlation for SAML and sub/azp validation for OIDC, requires HTTPS for OIDC discovery/endpoints, pins cacti_http() connections against DNS rebinding via CURLOPT_RESOLVE, exempts login_sso.php from the authenticated-page redirect guard and gates it on auth_method, wraps provider dispatch in a exception boundary, adds a dedicated SAML SLO path instead of routing it through login completion, makes remember-me an explicit opt-in for SSO, fixes disabled/deleted providers not revoking existing auth cookies, restricts the default-realm action to LDAP/AD, and fixes the 1.3.0 migration silently enabling group enforcement for installs that had it off. Also updates the E2E seed script for the login_providers schema. * chore(auth): refresh CI security baselines and document no-op validation Regenerate the sink inventory baseline for the new/renamed lib/Auth/* and login_providers.php files, and add '// no-validation:' comments on the intentionally-unconstrained admin/free-text/secret fields (LDAP DNs/filters, SAML certs/keys, OIDC client credentials, provider display fields) flagged by the validator constraint ratchet. Refreshes the validator baseline to drop the deleted user_domains.php entries. * refactor(ldap): remove dead DB-lookup path from Ldap::__construct The domain_id>0 branch duplicated the JSON-parameter-to-property mapping already owned by LdapLoginProvider::buildLdap(), and nothing constructs Ldap with a real id anymore (all call sites pass 0). Drop the parameter and the branch so Ldap stays a pure connection/protocol class with no DB coupling. Also fixes a latent bug: group_require is a typed bool property, but LdapLoginProvider::authenticate() assigned it an int (1/0) from a strict_types=1 file, which throws a TypeError on every LDAP/AD login attempt. Assign a real bool instead. * fix(auth): fix LDAP login, upgrade ordering, and hardening guard failures LdapLoginProvider compared LdapError::GetErrorDetails()'s int error_num against the string '0' with strict equality, so it never matched and every LDAP/AD login was rejected regardless of valid credentials. Compare against int 0 instead. install/upgrades/1_3_0.php called login_providers_convert_1_3_0() (which reads and then drops user_domains/user_domains_ldap) before the pre-existing legacy ALTER TABLE statements that add the debug/network_timeout/bind_timeout columns those reads depend on, so upgrades failed once the tables were already gone. Moved the call to run after those ALTERs. OpenIdLoginProvider::complete() read state/code from raw \ instead of gnrv(), tripping the hardening pattern guard; switched to the request helper. * style(auth): apply php-cs-fixer formatting and normalize line endings to LF Runs php-cs-fixer across the new lib/Auth/* classes, login_providers.php, login_sso.php, and the other touched files, fixing indentation/alignment/docblock style to match project convention. Also normalizes several files that had picked up CRLF line endings back to the LF the project's .php-cs-fixer.php config (setLineEnding) and the rest of the tracked tree actually use. * fix(auth): address second Copilot review round (9 high/medium findings) - Make login_providers_convert_1_3_0() resumable: CREATE TABLE IF NOT EXISTS and INSERT ... ON DUPLICATE KEY UPDATE so an interrupted upgrade can be re-run instead of leaving a half-migrated, unrecoverable table. - SamlLoginProvider::complete() now fails closed when no saved AuthnRequest ID exists instead of passing null to processResponse(), which skipped InResponseTo validation and accepted unsolicited IdP-initiated assertions. - LoginResult gains an isCredentialFailure flag; only a genuine wrong-password LDAP bind (LdapError::Failure) counts toward the lockout counter now, not connection errors or group-membership denials. - Fixed button_label/debug being read from the JSON parameters column in OpenIdLoginProvider/SamlLoginProvider/LdapLoginProvider when they are actually top-level login_providers columns; AbstractLoginProvider now hydrates and exposes both. - Removed SamlLoginProvider's wantMessagesSigned, which silently required the outer SAML message (not just the assertion) to be signed, rejecting IdPs that only sign the assertion as the UI promises. - Added getTemplateUserId() to CredentialLoginProviderInterface and implemented it on LocalAuthLoginProvider so the contract (not just incidental AbstractLoginProvider inheritance) guarantees the method lib/auth.php calls. - cacti_http()'s CURLOPT_RESOLVE entries now bracket IPv6 literals correctly, and safe-IP validation strips parse_url()'s brackets before checking, fixing IPv6 target pinning. - login_providers.php's type dropdown now swaps the Full Name attribute default (cn/displayName) on an actual user-driven type change, not on page load, so a new Active Directory provider does not silently save the LDAP default. - Extracted auth_apply_group_login_opts() and call it from both auth_login.php and login_sso.php, so SSO logins honor a user's group-derived login_opts the same way password logins already do. * fix(auth): fix PHPStan level 8 errors and pest harness stub drift Test harness fix: the LoginProvidersLoginRealmValidationTest stub LoginResult class was missing the isCredentialFailure property added to the real class, causing 'Undefined property' warnings that broke the harness's JSON output. Added the property and set it on the simulated bad-password failure. PHPStan (48+ errors, all newly introduced by this branch's rewrite, never actually verified until now): narrowed db_fetch_row_prepared()/db_fetch_assoc()'s array|bool (including bare true, which survives a '?: []' short-circuit - fixed with explicit is_array() checks instead) at every call site across auth_login.php, login_sso.php, lib/auth.php, lib/Auth/LocalAuthLoginProvider.php, lib/Auth/LoginProviderFactory.php, and install/upgrades/1_3_0.php. Fixed OpenIdLoginProvider::discover()'s return shape to match its declared docblock instead of returning the raw decoded JSON. Fixed lib/functions.php's cacti_http() to narrow curl_exec()'s string|bool result with is_string() (not ===false, which still leaves 'true' as a residual type) and guard / as non-empty for curl_setopt_array()'s stubs. Fixed a real bug in SamlLoginProvider::processLogout(): processSLO(true) was passing 'true' as \ (the first parameter) instead of \ (the fifth), so it always redirected/exited internally with the default \=false, making Cacti's own logout.php never regain control. Now calls processSLO(keepLocalSession: true, stay: true) and is typed ': never' to match; login_sso.php's now-genuinely-unreachable break statements after processLogout()/initiate() are removed. Fixed login_providers.php passing a expression (not a variable) to inject_form_variables()'s by-reference parameter. Verified: PHPStan analyse clean (0 errors), full Pest Unit/Security suite passes (128 tests), hardening guard/sink inventory/validator ratchet all still clean. * fix(auth): fix ambiguous column and array+bool crash in user_log filter The user filter dropdown query selected a bare 'id' column after joining user_auth and login_providers, which is ambiguous now that login_providers also has an id column (unlike the old user_domains.domain_id) - MySQL rejected the query outright. Qualified as ua.id. Also narrow db_fetch_assoc()'s array|bool result before the \ + \ + \ merge, which was throwing a TypeError whenever the query failed instead of surfacing the real SQL error. * fix(auth): address third Copilot review round (5 findings + tests) - AbstractLoginProvider::persist() now disables the template account for every provider type; SAML2/OpenID also copy it via user_copy() in login_sso_complete() and were left directly usable as a local login. - cacti_http_resolve_safe_ips() now detects IPv4-mapped IPv6 literals (::ffff:0:0/96) via inet_pton() and validates the embedded IPv4 address, closing an SSRF bypass via e.g. ::ffff:169.254.169.254. - login_providers_convert_1_3_0() now tracks db_install_execute()'s status for every row and only drops the legacy user_domains/user_domains_ldap tables once every row (and the final row count) is confirmed migrated. - Ldap::Getcn() now looks up ldap_get_entries()'s always-lowercased attribute keys case-insensitively while still storing the result under the originally-requested key, fixing AD's displayName (and any other mixed-case configured attribute) always resolving to a blank full name. - SamlLoginProvider::processLogout() now redirects to the LogoutResponse URL processSLO() returns for a LogoutRequest (stay:true), instead of discarding it and always redirecting to Cacti's own logout page - federated logout was never actually acknowledged to the IdP. - Added tests/Unit/Security/Auth/OpenIdLoginProviderTest.php (13 tests) covering verifyIdToken() signature/issuer/audience/azp/nonce validation, discover()'s HTTPS enforcement, and complete()'s callback-state and UserInfo-subject-matching checks, against a locally-generated RSA keypair/JWKS. - Regenerated the sink inventory baseline for the new header_redirect call in SamlLoginProvider::processLogout(). Verified: PHPStan clean, full Pest Security/Auth suite passes (830 tests including the 13 new ones), php-cs-fixer/hardening-guard/sink-inventory/validator-ratchet all clean. |
||
|
|
2d516ca0a9 |
security: consolidate additional verified GHSA fixes for develop (batch 2) (#8065)
* security: contain data source paths within the RRA directory (GHSA-9qff-xccr-rrc5)
* security: escape executable path in exec_background on non-win32 (GHSA-cv7j-p6fh-j4q7)
* security: stop disabled accounts authenticating via remember-me cookie and revoke on disable (GHSA-pqrp-v752-mc4f)
* security: prefer base_url over client Host for the force_https redirect (GHSA-3466-fp7g-c9rf)
* changelog: add fragments for GHSA-9qff-xccr-rrc5, GHSA-cv7j-p6fh-j4q7, GHSA-pqrp-v752-mc4f, GHSA-3466-fp7g-c9rf
* security: address review findings on GHSA batch 2 (develop)
- lib/dsstats.php, poller_maintenance.php: route through
get_data_source_path() instead of expanding data_source_path directly,
so the RRA containment check applies to these consumers too (GHSA-9qff)
- lib/functions.php: data_source_path_within_rra() now also validates
the realpath of each existing ancestor segment, closing a symlink
pivot that the purely lexical check missed (GHSA-9qff)
- include/global.php: fail closed instead of falling back to SERVER_NAME
when base_url is unset/invalid, removing the residual open-redirect
path (GHSA-3466)
- lib/html_utility.php: cacti_force_https_host()/
cacti_build_https_redirect_url() now parse a scheme-less base_url
(matching lib/reports.php's own normalization) and preserve a
configured non-default port instead of silently dropping it (GHSA-3466)
- tests: move DataSourcePathContainmentTest.php into
tests/Unit/Security/PathTraversal/, and switch its fixtures from a
fictional CACTI_PATH_RRA path to a real temp directory so the new
realpath()-based checks (including a symlink-pivot regression case)
can actually run
* test: fix CACTI_PATH_RRA constant collision in DataSourcePathContainmentTest
CI showed 2 failures: RrdProxyProtocolTest.php runs earlier in the same
Pest process and already defines CACTI_PATH_RRA to a fictional,
non-existent '/var/lib/cacti/rra', so our own definition guard never
ran and realpath(CACTI_PATH_RRA) returned false for every assertion,
failing the two tests that expect containment to be true.
Now ensure the directory backing CACTI_PATH_RRA actually exists on
disk regardless of who defined the constant first, and gracefully
skip only the fixture-dependent assertions if this process can't
create it (e.g. no permission on a system path).
* test: refresh sink inventory baseline for exec_background rename
|
||
|
|
a1fab6816f |
ci: refresh security/SQL-interpolation baselines (#8059)
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> |
||
|
|
d3d99bb08a |
test(cdef): enforce resolver coverage (#7974)
* test(cdef): enforce resolver coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci(cdef): pin cache action correctly Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: isolate CDEF coverage bootstrap * fix(cdef): fail closed on invalid recursive definitions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): log corrupt resolver records Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): bound and isolate recursive resolution Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): preserve resolver failure state Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): isolate invalid graph expressions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): abort unsafe consumer operations Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): close consumer failure paths Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): make aggregate updates atomic Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): keep repair queries auditable Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): bind bulk deletes and escape item types Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(cdef): guard corrupt types and repair reporting Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(aggregate): propagate push-out failures Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(aggregate): document mutable message checks Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
af8449e487 |
Fix RECACHE ASSERT loop caused by SNMP uptime selection diverging from spine (develop) (#8058)
* fix: match spine's unconditional engine-time preference for SNMP uptime recache (develop) PR #7982 changed cacti_snmp_select_uptime() to prefer whichever of sysUpTime and snmpEngineTime is numerically larger. Spine's own reindex-assert re-check (poller.c) has no such magnitude comparison - it always prefers a numeric engine time, only falling back to sysUpTime when the engine OID isn't present. For any device where snmpEngineTime is legitimately smaller than sysUpTime (e.g. the SNMP agent/engine restarted more recently than the OS, a common, unremarkable scenario), lib/poller.php's update_reindex_cache() now picks sysUpTime as the recache baseline while spine always re-derives its live comparison value from the smaller engine time. Every poll then reports "RECACHE ASSERT FAILED" and queues a reindex; Cacti recomputes the baseline the same way, so it never converges - an infinite RECACHE ASSERT loop that looks like a spine bug even though spine is unchanged. Fix: add an opt-in $prefer_engine_time parameter to cacti_snmp_select_uptime() that keeps the existing wall-clock rejection (issue#7342) but skips the "prefer whichever is larger" comparison, matching spine's rule exactly. Use it for the two call sites that must stay consistent with spine's own live re-check: lib/poller.php's recache baseline, and both uptime reads in cmd.php (update_system_mibs()'s host.snmp_sysUpTimeInstance update, and ping_and_reindex_check()'s own PHP-poller equivalent of spine's reindex re-check). The display-only call sites (lib/api_device.php, lib/api_automation.php) keep the existing wrap-aware "prefer larger" logic, since they aren't compared against a persisted, spine-shared baseline. Ported from the 1.2.x fix (#8057). Added tests covering the new parameter and asserting both call sites opt into it. * test(snmp): cover prefer_engine_time in the coverage-gated suite phpunit-snmp.xml's "SNMP coverage" testsuite only runs SnmpCoverageTest.php, not SnmpUptimeSelectionTest.php where the new $prefer_engine_time assertions were added. The engine-time-preference branch in cacti_snmp_select_uptime() was never executed under the 100% line coverage gate, so the "lib/snmp.php 100% coverage" check failed at 99.8%. Mirror the same prefer_engine_time assertions into SnmpCoverageTest.php so the coverage-gated suite exercises the new branch. * fix: match spine's lack of wall-clock awareness for prefer_engine_time paths The wall-clock rejection guard ran before the prefer_engine_time branch, so an OpenBSD-style agent (engine time == Unix clock) still fell back to sysUpTime on the spine-facing paths even with prefer_engine_time=true. Spine's own re-check has no wall-clock awareness at all - it unconditionally prefers any numeric engine time - so that class of device kept the RECACHE ASSERT loop. Move the prefer_engine_time short-circuit before the wall-clock check so those paths replicate spine exactly; the wall-clock rejection still protects the display-only call sites. * test(snmp): fix stale wall-clock assumption in prefer_engine_time coverage test |
||
|
|
1e5329dd89 |
security: consolidate verified GHSA-rjvj win32 cmd.exe hardening for develop (#8056)
* fix(win32): harden the cmd.exe shell sinks on develop (GHSA-rjvj-r52f-8v5q) Forward-port of the 1.2.x win32 command-injection remediation. On Windows the SNMP/ping/data-query/trap commands run through cmd.exe, which ignores the \" escape and toggles quoting on every ", so cacti_escapeshellarg cannot stop & | ^ < > ( ). - add cacti_escapeshellarg_cmd(), stripping those operators on win32 before quoting (a Unix pass-through) - snmp_escape_string(): strip the operators in the win32 branch - route the SNMP binary hostname, the data-query device values (get_script_query_path), the data-input arg builders, the Windows ping hostname, and the SNMP trap notification arguments through the strip get_script_query_path is device-reachable (a rogue monitored device supplies an SNMP index/system field that executes on the Windows poller); the rest are post-auth. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(win32): strip %VAR% from the hostname on Windows (GHSA-rjvj-r52f-8v5q) cmd.exe expands %VAR% even inside quotes, so a device hostname of %USERNAME% would expand into the SNMP/ping command line. Add an opt-in $strip_env flag to cacti_escapeshellarg_cmd() and set it for the SNMP binary and ping hostnames, which never legitimately contain a percent. Community and credential values are left untouched because they can legitimately contain %. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(changelog): note win32 cmd.exe hardening for develop * security: require Package Management permission to trust new package signers package_accept_key() (reachable via the repository-based package import flow) inserted new keys into package_public_keys, the global trust store consulted by import_validate_signature() for every future import by every user, without checking for the Package Management (realm 29) permission that gates package_keys.php/package.php/package_repos.php for the same table. An Import Templates (realm 17) user could therefore add themselves as a trusted signer for the whole install. Also add the 'accept' action to the CSRF-protected bad_actions list so the acceptance step requires POST plus a valid token like every other state-changing action. GHSA-gxgm-8rhv-r7jv * docs(changelog): note package-signer trust authorization fix * security: close tab-based open redirect bypass in CSRF timeout handler sanitize_uri()'s guard ( |
||
|
|
1a37594ad6 |
Replace unmaintained phpdiff vendor lib with jfcherng/php-diff (develop) (#8050)
* chore: replace unmaintained phpdiff vendor lib with jfcherng/php-diff The manually-vendored include/vendor/phpdiff (chrisboulton/php-diff v1.1, 2009 - unmaintained since ~2013) backed the package-import file diff viewer (package_import.php's package_diff_file()), its only call site. Replace it with jfcherng/php-diff (^6.16, BSD-3-Clause), an actively maintained continuation of the same library, declared as a real Composer dependency in composer.json/composer.lock (installed at build/CI time, same as this branch's other composer.json dependencies - not committed to include/vendor, unlike the old hand-vendored phpdiff). package_import.php now uses Jfcherng\Diff\Differ + RendererFactory in place of the old Diff/Diff_Renderer_Html_Inline classes. Preserved the existing nosemgrep justification comment, updated to reference the new library's AbstractHtml::htmlSafe() escaping. Adopted the vendor's own diff-table.css as the Diff.css in all 10 themes, replacing the bespoke per-theme stylesheets that were tied to the old library's class names. * fix: address review feedback on jfcherng/php-diff migration - package_import.php: narrow $oldfile/$newfile to array before constructing Jfcherng\Diff\Differ so PHPStan can see the false branch is excluded (fixes level 6/8 analysis failure). - tests/tools/check_vendor_policy.php: drop the removed include/vendor/phpdiff/ path from the legacy allowlist. - tests/Unit/Security/Xss/PackageDiffRendererTest.php: rewrite against the new Jfcherng\Diff\Differ/RendererFactory API instead of the deleted vendored phpdiff classes. |
||
|
|
344ed3dcef |
fix(snmpagent): clarify missing SNMP notification receiver message on develop (#8040)
* fix(snmpagent): clarify missing SNMP notification receiver message * fix(snmpagent): point notice at the real SNMP Managers menu and isolate test config * fix(js): regenerate DOMPurify 3.4.15 asset and skip node_modules cleanup in CI * chore: add PR number to CHANGELOG entry |
||
|
|
2f14d78927 |
fix(js): regenerate DOMPurify 3.4.15 asset and keep node_modules during CI (#8043)
* fix(js): regenerate DOMPurify 3.4.15 asset and skip node_modules cleanup in CI * chore: add PR number to CHANGELOG entry |
||
|
|
d34fbaacc4 |
fix: Restore base javascript files for testing (#8037)
* fix: Restore base javascript files for testing * fix: screenfull 6.0.2 is missing screenfull.js * fix: Restoring screenful after discovering how the files are managed * fix: Remove node modules directory after installing modules * fix: Change in screenfull.js behavior affects cacti * fix: Restore jQuery 4 compatibility in the tablesorter pager plugin $.isArray()/$.trim() were removed in jQuery 4.0, which this PR pins in package.json. The restored jquery.tablesorter.pager.js still called E.isArray(...)/E.trim(...), so any Ajax pager response threw before rendering. Also fix a TypeError in the countChildRows branch, which indexed into `a` while it was still an undeclared var. Add patches/tablesorter+2.32.0.patch so the fix survives the next `npm run build:js` sync from node_modules, following the existing pace-js patch-package precedent. * fix: Rewrite screenfull's ESM export during sync-js instead of downgrading screenfull 6+ ships ESM-only (`export default screenfull;` in node_modules/screenfull/index.js), which throws "export declarations may only appear at top level of a module" when the copied file is loaded as a classic <script>. Add a post-copy transform step to build/sync-js.mjs that rewrites that line to `window.screenfull = screenfull;` for include/js/screenfull.js, so the 6.0.2 pin in package.json can be kept instead of reverting to 5.2.0. Throws if the expected export line isn't found, so a future screenfull release changing its export shape fails the build loudly rather than silently shipping a broken asset. Updates tests/js/sync_js.test.mjs to cover the transform and its failure mode. |
||
|
|
cb49f31ff9 |
Replace csrf-magic with maintained CSRF protection (#7744)
* build: add symfony/security-csrf Backs the CSRF token core that replaces the vendored csrf-magic fork. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: fix security-csrf package path assertion symfony/security-csrf ships CsrfTokenManager.php at the package root, not under src/. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor: register the composer autoloader before the CSRF include CSRF validation ran before the autoloader existed, which blocks any class-based guard. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): add CactiCsrfGuard over symfony/security-csrf Issues and validates session-scoped tokens; not yet wired into the request path. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): accept pre-upgrade csrf-magic tokens during the grace window Legacy tokens carry their own expiry, so the fallback closes itself without a config flag. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): port the output buffer rewriter to CactiCsrfGuard Keeps the hidden field name and JS globals that third-party plugins rely on. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): use json_encode for the JS script context in rewriteBuffer htmlspecialchars() protects the HTML attribute context but not the <script> string literal, which relied on Symfony's token generator never emitting a quote rather than being safe by construction. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(csrf): move the XHR shim to include/js as Cacti-owned code It has been maintained by the Cacti Group since 2020 and is not an upstream package. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(csrf): retire the vendored csrf-magic fork The procedural csrf_* surface stays as a deprecated shim so plugins keep working. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * feat(csrf): validate requests through CactiCsrfGuard csrf-magic self-invoked from its own file; the guard is called explicitly. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(install): stop gating the permission step on the csrf secret Retiring csrf-magic removed the directory its default path lives in, and setStep() clamps to the first errored step, so no fresh install could advance. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): key the legacy fallback on the raw secret bytes refresh_csrf.php writes PHP source with a trailing newline, and trimming that yields a different HMAC key than csrf-magic used. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): log when the guard degrades to disabled in a web context The installer is the only web path that can reach it, and failing open in silence is not something csrf-magic could do. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: changelog for the CSRF library replacement Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): mint the token before the session write callback closes it csrf_startup() minted lazily from the output buffer callback, which runs after session_write_close()'s shutdown-function registration, so a token minted there on a legacy-fallback POST was silently dropped. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): make refresh_csrf.php a no-op Its default secret path no longer ships and live tokens are session-bound, not HMACs over the secret, so rotation neither worked nor did anything meaningful; running it during the upgrade grace window used to delete the legacy secret and kill validation for pre-upgrade tokens outright. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): fall back to the old default secret path when reading it csrf-magic tried its own directory as a second candidate regardless of $path_csrf_secret; the new reader only tried the configured path, so a site pointing $path_csrf_secret at a file that was never created lost the legacy-token grace window entirely. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(csrf): point the JS header at its BSD license sidecar The header reduces the original BSD terms to prose and the actual text sits in an unreferenced csrf.js.LICENSE, so add a pointer to it. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(csrf): restore csrf_check()'s bool return and $fatal contract The shim ignored $fatal and always returned void, so audit/audit.php's `!csrf_check(false)` evaluated `!null` (always true) and denied every request instead of getting a real pass/fail result back. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: reference the pull request number in the changelog entry Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: resolve includes through Cacti path constants Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(csrf): enforce full coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Harden CSRF fallback handling Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(csrf): assert the component stays out of the index Committing it would leave a second copy to drift against the lock. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: align CSRF checks with PHP 8.2 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs: normalize the CSRF changelog entry * Fix CHANGELOG entry ordering Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(csrf): support constrained test environments * fix(csrf): narrow nullable guard state * test: place CSRF coverage in security domain Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * security(test): match disabled exec exactly Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: run CSRF coverage on PHP 8.3 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
eadbe5a9c6 |
Device Template Ordering Issues (#8035)
* fix: Regressin in poller_time table handling * fix: Host Template ordering was broken * fix: Remove Unit test as the logic was incorrect * fix: PSR on quotes |
||
|
|
157a4458ae |
fix(auth): reject malformed remember cookies (#7976)
* fix(auth): reject malformed remember cookies Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: load cookie contracts lazily * test(cookie): fail clearly when contract markers move Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(auth): align persistent credential lifecycle Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(auth): find cookie function boundaries robustly Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
055c5ef5fa |
security: reject LDAP logins that skip a domain bind (#8009)
* hardening: validate the login realm and escape the LDAP username per context domains_login_process() now rejects a realm get_auth_realms() does not list and fails closed on one that is listed but has no domain behind it, and the Ldap class escapes the username for the DN and the search filter separately rather than relying on a filter-only blocklist. - an unlisted realm sent with a non-empty password used to match no branch at all and returned an empty user with no error, which auth_login.php reads as an authenticated user and provisions from the template account Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * security: reject LDAP logins that skip a domain bind Posted realms outside user_domains no longer fall through to template or guest login. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(ldap): align escaping contract with username lookup Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: cover LDAP domain login through OpenLDAP The e2e stack now binds a real directory user and checks that a forged realm cannot skip that bind. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ldap): address Copilot review feedback Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: substitute LDAP filter placeholders in one pass LDAP_ESCAPE_FILTER leaves angle brackets intact, so a looped str_replace can rescan one value as another key. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Change realm retrieval function from gnrv to gfrv * tests: add gfrv() stub to harnesses that exercise gfrv()-calling code domains_login_process() and api_aggregate_convert_template() call gfrv() for realm/aggregate_template_id, but their test harnesses only stubbed gnrv(), so Pest failed with 'Call to undefined function gfrv()'. --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
182b21dc53 |
fix(csv): align export and graph sample windows (#7987)
* fix(csv): align export and graph sample windows Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci(csv): satisfy release policy checks * test(csv): align legacy capacity contract * test(csv): add standard license headers * fix(rrd): infer step from trailing fetch row Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci(csv): trigger on resolution query coverage Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): mirror fetch helper arguments Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): isolate percentile globals Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): annotate intentional eval Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): restore integration config Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): enable strict types Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(rrd): restore font config Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: align CSV coverage with supported PHP versions Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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
|
||
|
|
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> |
||
|
|
95060c49ba | Close test-suite discovery and coverage gaps (#7970) | ||
|
|
3f52135d36 | Harden installer cleanup and enforce coverage collection (#7993) | ||
|
|
1fdf9acf3b | Fix detached select menus on scroll (#8000) | ||
|
|
130f58243b | Fix SNMP uptime selection across polling paths (#7984) |