Skip to content

DRAFT: fix(security): PR #313 follow-up - partition correctness, CSV/XSS hardening - #315

Open
somethingwithproof wants to merge 21 commits into
Cacti:developfrom
somethingwithproof:fix/pr313-followup-security-and-partitions
Open

DRAFT: fix(security): PR #313 follow-up - partition correctness, CSV/XSS hardening#315
somethingwithproof wants to merge 21 commits into
Cacti:developfrom
somethingwithproof:fix/pr313-followup-security-and-partitions

Conversation

@somethingwithproof

@somethingwithproof somethingwithproof commented Apr 10, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #313, now narrowed to the concrete correctness and hardening fixes that survived review.

  • keep partition maintenance on UTC integer boundary math and dMaxValue fallback semantics
  • fix callback dispatch so argument-bearing strings are rejected instead of normalized and executed
  • emit autocomplete init arguments through json_encode(..., JSON_HEX_*)
  • harden host autocomplete option creation by building DOM nodes instead of concatenating HTML
  • fix the report lower-bound bind typo in syslog_process_reports()
  • normalize syslog entrypoint plugin self-includes to __DIR__-based paths so web and CLI entrypoints bootstrap reliably
  • harden bulk action confirmation forms by escaping drp_action and encoding filter state in nav links
  • encode initSyslogMain() request/translatable strings with json_encode(..., JSON_HEX_*) so tab/init text cannot break script context
  • add regression coverage for partition locking, include-path normalization, and bulk-form/nav encoding
  • add Docker/Orb + headless Playwright end-to-end coverage with real syslog ingest through syslog_incoming

The earlier SQL-rule gating/settings work has been removed from this draft PR.

Test Plan

  • php -l clean on touched PHP files
  • node -c js/functions.js
  • php tests/regression/issue254_partition_table_locking_test.php
  • php tests/regression/include_path_normalization_test.php
  • php tests/regression/issue279_bulk_form_and_nav_encoding_test.php
  • tests/e2e/run-orb-docker-e2e.sh

End-to-End Coverage

The Docker/Orb Playwright run exercises 10 browser-backed flows against a disposable Cacti + syslog-plugin stack, with messages inserted into syslog_incoming and processed by the plugin CLI:

  1. login renders Cacti console
  2. syslog page loads with empty state
  3. alerts page loads
  4. removal page loads
  5. reports page loads
  6. first syslog message ingests into main tables
  7. normalization/aggregation data is produced
  8. ingested message becomes visible in the syslog UI
  9. second syslog message accumulates correctly
  10. second ingested message is visible in the UI

Closes #314

Copilot AI review requested due to automatic review settings April 10, 2026 18:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Follow-up hardening and correctness fixes for the Syslog plugin after PR #313, focusing on partition rotation correctness, SQL-rule opt-in gating, and UI/export injection defenses.

Changes:

  • Reworks partition boundary arithmetic to use UTC epoch integer math and hard-fail when partition expressions can’t be detected.
  • Adds syslog_allow_sql_rules (disabled by default) and gates SQL-type alert/removal/report rules behind it; adds table-name allowlists for interpolated identifiers.
  • Hardens CSV export and host autocomplete UI against injection vectors; restores/extends regression coverage.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
functions.php Partition boundary/rotation hardening; SQL-rule gating; CSV export sanitization helper; table allowlists.
setup.php Partition creation uses UTC epoch boundaries; adds Security Settings + syslog_allow_sql_rules option.
syslog.php Passes initSyslogAutocomplete args via json_encode() instead of raw-quoted strings.
js/functions.js Builds host <option> safely via jQuery APIs; replaces callback dispatcher with identifier-only invocation.
tests/regression/issue254_partition_table_locking_test.php Restores and expands regression guard assertions for partition and allowlist invariants.

Comment thread functions.php
Comment thread js/functions.js
somethingwithproof added a commit to somethingwithproof/plugin_syslog that referenced this pull request Apr 10, 2026
- syslog_partition_manage now gates syslog_partition_remove on the
  return value of syslog_partition_create. Previously a hard failure
  in create would still drop the oldest partition on every poller
  cycle, with no replacement, because remove ran unconditionally.

- syslog_alerts.php and syslog_reports.php refuse to save a type=sql
  rule when 'syslog_allow_sql_rules' is off and raise an admin-visible
  error message naming the setting to flip. The previous empty-result
  fallback displayed a generic "SQL was invalid" error that pointed
  editors at the wrong problem.

- syslog.php now passes JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS |
  JSON_HEX_QUOT to json_encode() for the initSyslogAutocomplete
  arguments. Defense-in-depth so a future tainted value cannot close
  the <script> block.

- syslog_csv_safe strips leading spaces (not tabs or CRs) before
  checking the first character, so " =SUM(A1)" is prefixed while
  leading tab and CR remain detectable as triggers themselves.

- syslog_debug prints H:i:s instead of H:m:s (was printing month
  where minutes should be).

- Regression test tightens the syslog_manage_items allowlist check
  to match the exact allowlist literal and the fail-closed return
  shape. Added a guard that syslog_partition_manage wraps
  syslog_partition_remove in an if on syslog_partition_create's
  return value.

- New tests/regression/issue315_csv_safe_unit_test.php extracts
  syslog_csv_safe via source and exercises empty/null/int/benign
  inputs, every trigger character, leading-space variants, mid-string
  occurrences, and the already-escaped case.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/plugin_syslog that referenced this pull request Apr 10, 2026
Comment thread functions.php Outdated
Comment thread js/functions.js
Comment thread functions.php
Comment thread functions.php
Comment thread functions.php
Comment thread functions.php Outdated
Comment thread setup.php Outdated
Comment thread syslog.php
Comment thread functions.php
Comment thread functions.php Outdated
somethingwithproof added a commit to somethingwithproof/plugin_syslog that referenced this pull request Apr 10, 2026
@somethingwithproof
somethingwithproof marked this pull request as draft April 10, 2026 23:17
@somethingwithproof somethingwithproof changed the title fix(security): PR #313 follow-up — partition correctness, CSV/XSS hardening, SQL rule gating DRAFT: fix(security): PR #313 follow-up - partition correctness, CSV/XSS hardening Apr 10, 2026
Follow-up to Cacti#313. Addresses eight correctness and hardening findings:

- Partition boundary math is now computed as integer epochs in PHP
  (next UTC midnight) and injected as numeric literals. strtotime()
  and UNIX_TIMESTAMP('date-literal') both pulled the PHP/MySQL
  session TZ into UTC-intended math and caused drift at day
  boundaries on non-UTC servers.

- syslog_partition_create() now hard-fails with an error log instead
  of silently warning when SHOW CREATE TABLE does not expose either
  TO_DAYS or UNIX_TIMESTAMP. Silent no-ops caused rotations to stall
  unnoticed. Rewrote str_contains() to strpos() for portability.

- syslog_manage_items() validates $from_table/$to_table against a
  three-value allowlist before interpolation. Defense-in-depth for
  the one caller that currently passes safe literals.

- Alert, removal, and report rule handlers of type sql are gated on
  a new 'syslog_allow_sql_rules' setting (off by default). These
  handlers inline admin-defined SQL into the WHERE clause and
  cannot be parameterised; the previous removal handler also
  emitted invalid syntax ("WHERE message (expr)"). Added a Security
  Settings section to setup.php with a warning description.

- CSV export replaces lossy trim($x, ' =+-@') with a syslog_csv_safe()
  helper that prepends a single quote only when the first character
  is one of =+-@, TAB, CR. Preserves content verbatim while defusing
  spreadsheet formula injection per OWASP guidance.

- Host autocomplete dropdown builds <option> elements via jQuery's
  attr()/text() instead of HTML string concatenation wrapped in
  DOMPurify.sanitize(). DOMPurify does not escape attribute
  delimiters, so a host containing a double quote could still break
  out of the class/value attribute.

- Autocomplete onChange dispatcher enforces a bare-identifier
  whitelist and looks the callback up as a direct window[name]
  property. Rejects dotted paths, arguments, and anything that is
  not a function, with a console.warn. Server side, the three
  initSyslogAutocomplete arguments now go through json_encode()
  instead of single-quote interpolation.

- Ported tests/regression/issue254_partition_table_locking_test.php
  to the new syslog_partition_check/create two-argument signatures
  and extended it to assert the UTC-midnight boundary arithmetic,
  the hard-fail on unknown partition expression, and the
  syslog_manage_items table-name allowlist.

Verification:
- php -l on every touched file
- node -c on js/functions.js
- all eight regression tests in tests/regression/ that pass on
  develop continue to pass; issue254 was restored and passes;
  three pre-existing failures (issue253, issue269 x2) are
  unchanged on develop.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Addresses code review feedback on the PR Cacti#313 follow-up. The boundary
epoch calculation ((int)($time / 86400) + 1) * 86400 assumes a
non-negative UTC timestamp; non-numeric or pre-epoch values would
silently underflow. Reject them at function entry with a logged error
and a false return, matching the other guard clauses in the function.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Addresses a fourth type=sql path missed in the initial follow-up.
syslog_remove_items() at functions.php:654 also inlined the admin
rule message into the WHERE clause. Apply the same
syslog_allow_sql_rules opt-in that already gates syslog_manage_items,
syslog_get_alert_sql, and syslog_get_report_sql. When the setting is
off, the rule is skipped with a log entry naming the rule.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
- syslog_partition_manage now gates syslog_partition_remove on the
  return value of syslog_partition_create. Previously a hard failure
  in create would still drop the oldest partition on every poller
  cycle, with no replacement, because remove ran unconditionally.

- syslog_alerts.php and syslog_reports.php refuse to save a type=sql
  rule when 'syslog_allow_sql_rules' is off and raise an admin-visible
  error message naming the setting to flip. The previous empty-result
  fallback displayed a generic "SQL was invalid" error that pointed
  editors at the wrong problem.

- syslog.php now passes JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS |
  JSON_HEX_QUOT to json_encode() for the initSyslogAutocomplete
  arguments. Defense-in-depth so a future tainted value cannot close
  the <script> block.

- syslog_csv_safe strips leading spaces (not tabs or CRs) before
  checking the first character, so " =SUM(A1)" is prefixed while
  leading tab and CR remain detectable as triggers themselves.

- syslog_debug prints H:i:s instead of H:m:s (was printing month
  where minutes should be).

- Regression test tightens the syslog_manage_items allowlist check
  to match the exact allowlist literal and the fail-closed return
  shape. Added a guard that syslog_partition_manage wraps
  syslog_partition_remove in an if on syslog_partition_create's
  return value.

- New tests/regression/issue315_csv_safe_unit_test.php extracts
  syslog_csv_safe via source and exercises empty/null/int/benign
  inputs, every trigger character, leading-space variants, mid-string
  occurrences, and the already-escaped case.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof
somethingwithproof force-pushed the fix/pr313-followup-security-and-partitions branch from 70de561 to 0f33598 Compare July 14, 2026 10:46
somethingwithproof added a commit to somethingwithproof/plugin_syslog that referenced this pull request Jul 29, 2026
syslog_csv_safe() treated a leading tab/CR as skippable whitespace
before checking for =+-@, instead of as a trigger itself, so
tab/CR-prefixed CSV injection payloads passed through unescaped.
Align with the fix already reviewed in PR Cacti#315.

Relax include_path_normalization_test and
issue254_partition_table_locking_test assertions to match the current
functions.php; the originals were written against PR Cacti#313's
follow-up state, which hasn't merged yet, so they always failed.
Guard TEMP_DIR in the e2e docker script so rm -rf can't run outside
/tmp.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/plugin_syslog that referenced this pull request Jul 29, 2026
syslog_csv_safe() was defined but never called from syslog_export(),
so the formula-injection guard provided no actual protection. Replace
the older trim($value, ' =+-@') calls, which destroyed leading/
trailing formula characters instead of neutralizing them, with
syslog_csv_safe() on every field written via fputcsv(), matching the
approach already reviewed in PR Cacti#315.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Three defects, all failing open or failing at setup:

- the plugin syntax check redirected find's own output rather than php's, so
  PHP errors never reached the grep testing for them; the step could not fail
- MYSQL_AUTH_USR carried a literal tilde, because parameter expansion happens
  after tilde expansion, so MySQL was handed a path it could not resolve
- the Cacti checkout took the default branch, which is 1.3 in development and
  whose CLI installer currently fatals with an undefined __()

plugin_syslog additionally installed libapache2-mod-php${{ matrix.php }},
which Ubuntu does not package, so apt exited 100 before Cacti was reached.

Verified with actionlint, which is clean on the result.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof
somethingwithproof marked this pull request as ready for review August 17, 2026 21:24
# Conflicts:
#	.github/workflows/plugin-ci-workflow.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up to PR #313: partition correctness, CSV/XSS hardening, SQL rule gating

3 participants