Skip to content

test: shared hand-off + mutation infrastructure for feature PRs#7083

Closed
somethingwithproof wants to merge 6 commits into
Cacti:developfrom
somethingwithproof:feat/develop-handoff-test-infra
Closed

test: shared hand-off + mutation infrastructure for feature PRs#7083
somethingwithproof wants to merge 6 commits into
Cacti:developfrom
somethingwithproof:feat/develop-handoff-test-infra

Conversation

@somethingwithproof

@somethingwithproof somethingwithproof commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Shared hand-off testing + mutation infrastructure consolidated from 8 in-flight feature PRs.

What this adds

  • composer.json: infection/infection ^0.27 in require-dev and infection/extension-installer: true in config.allow-plugins (the plugin would otherwise be blocked by Composer 2.x).
  • infection.json5 at repo root: baseline mutation config — Pest, MSI thresholds 70 / covered 80, 30s timeout, log under build/infection/. Per-feature filters live in the feature PRs.
  • tests/HandOff/HandOffHelpers.php: shared helpers reused by every hand-off test:
    • cacti_handoff_stub_cacti_log() records cacti_log calls into a static buffer
    • cacti_handoff_get_log_buffer() / cacti_handoff_clear_log_buffer() for assertions and reset
    • cacti_handoff_temp_file(contents, extension) — auto-cleaned tempnam wrapper
    • cacti_handoff_make_zip(entries) — minimal valid ZIP builder for upload-validation tests
  • tests/HandOff/<Feature>HandOffTest.php (8 files): boundary-crossing test suites consolidated from each feature PR. Each suite begins with a file_exists() / function_exists() guard against the feature's source file; on develop without the feature merged, the suite registers a single skipped test and returns. Once a feature PR merges, its suite activates automatically.
  • phpunit.xml: registers a HandOff testsuite alongside Unit and Integration.
  • docs/security/handoff-tests.md: explains the boundary-crossing assertion pattern and how to run mutation per feature.

Source PRs that contributed each suite

Each feature PR has been rebased to drop its local copy. Skip guards keep this PR green on develop today; the suites activate as feature PRs land.

What this does NOT change

  • No CI gate on mutation. Mutation is operator-run locally before merging substantive changes; running it in CI on every PR would multiply the PHP matrix runtime by 5-10x with no caught-bug payoff at present coverage.
  • 1.2.x branches are explicitly excluded — infection/infection transitively pulls Symfony components, and the project's policy is no Symfony on 1.2.x. Hand-off tests on 1.2.x branches still work via the same helpers but skip the mutation config.

Rollback

Two commits. Revert if there's pushback on the infection dev-dep.

Consolidates infection/infection dev dep, allow-plugins entry, baseline
infection.json5, tests/HandOff/HandOffHelpers.php with reusable stubs
(cacti_log capture, temp-file fixtures, minimal-zip builder), HandOff
testsuite registration, and the documented pattern. Feature PRs that
add hand-off coverage rebase onto this and contribute only ONE
HandOffTest file plus an optional infection.json5 filter override.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Copilot AI review requested due to automatic review settings April 29, 2026 03:18

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

Adds shared “hand-off” test infrastructure and baseline mutation-testing configuration so multiple feature PRs can rebase and drop duplicated scaffolding.

Changes:

  • Add shared hand-off test helpers under tests/HandOff/ and track the directory in git.
  • Register a HandOff testsuite in phpunit.xml and document the hand-off test pattern.
  • Introduce a baseline infection.json5 and add infection/infection to require-dev (plus Composer allow-plugin entry).

Reviewed changes

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

Show a summary per file
File Description
tests/HandOff/HandOffHelpers.php Adds shared helpers for log stubbing, temp files, and ZIP fixture creation used by HandOff tests.
tests/HandOff/.gitkeep Ensures tests/HandOff/ is tracked before tests land.
phpunit.xml Adds HandOff testsuite pointing to ./tests/HandOff.
infection.json5 Adds baseline Infection mutation-testing config and thresholds.
docs/security/handoff-tests.md Documents the hand-off test pattern and how to run HandOff/mutation locally.
composer.json Adds Infection dev dependency and allows Infection’s Composer plugin.

Comment on lines +38 to +71
function &cacti_handoff_stub_cacti_log(): array {
static $buffer = array();

if (!function_exists('cacti_log')) {
eval('function cacti_log($string, $output = false, $environ = "CMDPHP", $level = 0) { '
. '\cacti_handoff_record_log_line($string, $environ, $level); '
. '}');
}

return $buffer;
}

/*
* Internal sink used by the eval'd cacti_log shim. Kept separate so
* the stored shape stays a single hashmap per call regardless of how
* cacti_log was invoked.
*/
function cacti_handoff_record_log_line(string $string, string $environ = 'CMDPHP', int $level = 0): void {
$buffer = &cacti_handoff_log_buffer_ref();
$buffer[] = array(
'string' => $string,
'environ' => $environ,
'level' => $level,
);
}

/*
* Single static buffer shared by the stub, the getter, and the
* clearer. A by-reference accessor avoids duplicating the static.
*/
function &cacti_handoff_log_buffer_ref(): array {
static $buffer = array();
return $buffer;
}

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

cacti_handoff_stub_cacti_log() returns a by-ref static $buffer, but the shim actually records log lines into the separate static buffer inside cacti_handoff_log_buffer_ref(). As a result, callers using the return value from cacti_handoff_stub_cacti_log() will never see recorded entries. Make the stub return cacti_handoff_log_buffer_ref() by reference (and remove the extra static), or have cacti_handoff_record_log_line() append to the same static used by the stub.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — stub now returns cacti_handoff_log_buffer_ref() by reference.

Comment thread tests/HandOff/HandOffHelpers.php Outdated
Comment on lines +42 to +51
eval('function cacti_log($string, $output = false, $environ = "CMDPHP", $level = 0) { '
. '\cacti_handoff_record_log_line($string, $environ, $level); '
. '}');
}

return $buffer;
}

/*
* Internal sink used by the eval'd cacti_log shim. Kept separate so

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

Defining cacti_log() via eval() is avoidable here and makes the helper harder to audit/debug. PHP supports conditional function declarations without eval (e.g., define function cacti_log(...) { ... } inside the if (!function_exists('cacti_log')) block). Also, the stub signature diverges from the real cacti_log() in lib/functions.php:1448 (types/defaults/return bool), which can cause surprising behavior when hand-off tests hit real call sites—consider matching the real signature and returning true.

Suggested change
eval('function cacti_log($string, $output = false, $environ = "CMDPHP", $level = 0) { '
. '\cacti_handoff_record_log_line($string, $environ, $level); '
. '}');
}
return $buffer;
}
/*
* Internal sink used by the eval'd cacti_log shim. Kept separate so
function cacti_log(string $string, bool $output = false, string $environ = 'CMDPHP', int $level = 0): bool {
cacti_handoff_record_log_line($string, $environ, $level);
return true;
}
}
return $buffer;
}
/*
* Internal sink used by the cacti_log shim. Kept separate so

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — eval removed; cacti_log is declared inline inside function_exists guard with the correct signature.

Comment thread tests/HandOff/HandOffHelpers.php Outdated
}

foreach ($entries as $name => $contents) {
$zip->addFromString((string) $name, (string) $contents);

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

ZipArchive::addFromString() can return false (e.g., invalid entry name). Right now failures are silently ignored, which can lead to tests operating on an incomplete ZIP. Check the return value and throw a RuntimeException when adding an entry fails.

Suggested change
$zip->addFromString((string) $name, (string) $contents);
if ($zip->addFromString((string) $name, (string) $contents) === false) {
$zip->close();
throw new RuntimeException('cacti_handoff_make_zip: failed to add ZIP entry "' . (string) $name . '"');
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — addFromString return value checked; failure closes the archive and throws RuntimeException.

Comment thread infection.json5 Outdated
// on the Infection CLI. The thresholds below (minMsi / minCoveredMsi) are
// the project floor; feature PRs may raise them but should not lower them.
{
$schema: "vendor/infection/infection/resources/schema.json",

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

$schema points at vendor/infection/..., but this repo’s Composer vendor-dir is ./include/vendor (composer.json config). Updating the schema path to include/vendor/infection/infection/resources/schema.json keeps editor/IDE schema validation working with the project’s vendor layout.

Suggested change
$schema: "vendor/infection/infection/resources/schema.json",
$schema: "include/vendor/infection/infection/resources/schema.json",

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Each test guards on its feature file and skips when not present on develop.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request Apr 29, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
- phpunit.xml: add Integration testsuite alongside existing Unit/HandOff
- tests/Integration/: DbDumpIntegrationTest, PingIntegrationTest (from develop), CactiProcessIntegrationTest, SqlScriptsIntegrationTest
- tests/HandOff/RegressionGuardTest: guards against backsliding on shell_exec, password-in-argv, RLIKE concat, and open-redirect patterns

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request May 8, 2026
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

# Conflicts:
#	phpunit.xml
@somethingwithproof

Copy link
Copy Markdown
Contributor Author

Consolidated into #7124. The HandOff infrastructure, Integration suite, and infection config have been folded onto feat/test-infra-foundation along with the Pest test foundation. Closing here; please review on #7124.

somethingwithproof added a commit to somethingwithproof/cacti that referenced this pull request May 8, 2026
Guards integration tests and regression guards from Cacti#7083 against
absence of CactiProcess and other feature-PR hardening, so the
consolidated foundation PR is green on develop. Suites activate
automatically once their feature PRs merge.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
TheWitness added a commit that referenced this pull request May 12, 2026
…7124)

* test: shared hand-off + mutation infrastructure for feature PRs

Consolidates infection/infection dev dep, allow-plugins entry, baseline
infection.json5, tests/HandOff/HandOffHelpers.php with reusable stubs
(cacti_log capture, temp-file fixtures, minimal-zip builder), HandOff
testsuite registration, and the documented pattern. Feature PRs that
add hand-off coverage rebase onto this and contribute only ONE
HandOffTest file plus an optional infection.json5 filter override.

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

* test: consolidate per-feature hand-off suites into shared infra

Each test guards on its feature file and skips when not present on develop.

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

* fix(test): replace eval stub, unify log buffer ref, guard ZipArchive add

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

* fix(ci): correct infection vendor path in infection.json5

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

* ci(phpstan): baseline upstream undefined-variable and isset errors

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

* test: add HandOff and Integration testsuites; add regression guards

- phpunit.xml: add Integration testsuite alongside existing Unit/HandOff
- tests/Integration/: DbDumpIntegrationTest, PingIntegrationTest (from develop), CactiProcessIntegrationTest, SqlScriptsIntegrationTest
- tests/HandOff/RegressionGuardTest: guards against backsliding on shell_exec, password-in-argv, RLIKE concat, and open-redirect patterns

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

* chore(test): pest test infrastructure and PSR-4 autoload

Add Pest 2 dev dependency, regenerate composer.lock under PHP 8.1, declare PSR-4 autoload for the Cacti namespace, and seed the test bootstrap with three unit tests plus an orb integration check. Pest stays on the v2 line because v3 transitively requires PHP 8.2 and breaks the 8.1 matrix entry.

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

* chore(phpstan): catch up baseline with 11 upstream-detected entries

Cacti Commit Audit fails on develop with 11 PHPStan errors that are
not yet in phpstan-baseline.neon. Append the matching entries so the
PR-A branch passes Run PHPStan at Level 6. Same set of entries
already exists on PR #7077; this is a transient catch-up that
upstream will absorb when those entries land on develop.

Files: aggregate_graphs.php, color_templates.php, graph_templates.php,
graphs.php, lib/html.php (3 entries).

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

* chore(test): drop composer.lock; let CI resolve per PHP version

The lock file pinned brianium/paratest v7.3.1 which only supports
PHP 8.1-8.3, so the 8.4 matrix entry rejected the lock with
"Your lock file does not contain a compatible set of packages".
A single lock cannot satisfy 8.1 (paratest 7.3.x) and 8.4 (paratest
7.4.x) at the same time because pestphp/pest 2.36.0 is the last
2.x release that supports 8.1 and the 8.1-compatible paratest tag
predates 8.4 support.

Match upstream develop's behaviour: no committed lock; let
`composer install` in CI resolve per matrix entry. Pest stays on
the v2 line in composer.json so 8.1 still gets a compatible Pest.

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

* fix(test): apply Copilot review feedback for test infra

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

* chore(test): drop psr-4 autoload and align deps with canonical PRs

Match the flat un-namespaced lib/CactiX.php convention used by #7088,
#7073, #7077, #7075. Drop symfony/process (added by #7073) and
symfony/validator (added by #7077) to avoid composer.json conflicts at
merge time.

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

* test: skip integration suites and regression guards pending feature PRs

Guards integration tests and regression guards from #7083 against
absence of CactiProcess and other feature-PR hardening, so the
consolidated foundation PR is green on develop. Suites activate
automatically once their feature PRs merge.

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

* fix(ci): drop infection/infection to unbreak PHP 8.4 matrix

infection/infection ^0.27 transitively pins thecodingmachine/safe
^2.1.2, resolving to v2.5.0. Its generated stubs use implicit-nullable
parameter declarations, which PHP 8.4 emits as deprecations into
cacti.log and trips the log-quiet assertion. infection has no config
or test wiring in this repo, so removing the dev dep clears the
transitive pin without losing functionality.

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
TheWitness added a commit that referenced this pull request May 12, 2026
…#6989)

* fix(import): harden package path traversal and absolute-path rejection

Reject absolute paths (Unix /... and Windows \... or C:\...) before any
boundary math runs. Extend realpath boundary checks to data_query xml_path
and package find_paths(). Exclude binary-extension files from path
resolution. cs-fixer style fixups included.

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

* test: align import path traversal coverage

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

* fix(style): align ping.php match arms; add PHPStan baseline for pre-existing Level 6 errors

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

* chore: ignore local Claude Code tooling state and add contributor notes

Add .claude/, .omc/, .worktrees/, and notepad.md to .gitignore so local
AI-assistant session state, oh-my-claudecode orchestration files, and
scratch worktrees never leak into a PR. Add CLAUDE.md at the repo root
documenting the house conventions (PHP 7.4 target on 1.2.x branches,
cacti_sizeof/htmle/gfrv wrappers, db_qstr_rlike, the vendor-pollution
pitfall) so future AI contributions match the review feedback pattern
from TheWitness instead of repeating it on every PR.

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

* chore: remove accidentally tracked .worktrees and .claude/worktrees gitlinks

31 submodule-style gitlinks pointing at local git worktrees had been
staged into the branch. They showed up in the PR diff stat as +1-line
entries and were what @TheWitness asked to be removed. The .gitignore
entry from the previous commit covers new ones; these needed an
explicit git rm --cached to leave the index.

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

* fix(import): tighten Copilot review feedback

Hoist allowed-base realpath out of per-file loop; early-continue when
$resolved_dir is false; whitespace-tolerant regex assertions in tests;
unique random temp base per test.

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

* test(import): hand-off coverage + infection mutation config

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

* ci(infection): allow infection/extension-installer composer plugin

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

* test(import): drop hand-off test, consolidated into #7083

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

* ci(phpstan): baseline upstream undefined-variable and isset errors

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

* ci: retrigger after transient packagist network failure

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

* chore: remove dev tooling artifacts from index

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

* ci: retrigger after transient PPA failure

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
TheWitness added a commit that referenced this pull request May 12, 2026
…7032)

* fix(snmp): strip trailing .0 padding in OID indexes

SNMP walks on some devices return OID indexes with one or more trailing
zero octets that are structural padding rather than meaningful data (e.g.
.1.3.6.1.2.1.2.2.1.2.1.0 where the final .0 is padding). When Cacti uses
those raw indexes as array keys they never match the parsed index values,
so rows silently disappear from data query results.

Detection heuristic: strip a trailing .0 segment only when (a) the OID
has more than two dot-separated components, (b) the candidate key without
the suffix already exists in the index map, and (c) the row count after
stripping is not reduced below 1. This avoids false-positive stripping on
legitimate single-component or two-component indexes.

Single-row OID indexes are preserved unchanged to prevent over-stripping
on devices where .0 is the real index value.

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

* chore: ignore local Claude Code tooling state and add contributor notes

Add .claude/, .omc/, .worktrees/, and notepad.md to .gitignore so local
AI-assistant session state, oh-my-claudecode orchestration files, and
scratch worktrees never leak into a PR. Add CLAUDE.md at the repo root
documenting the house conventions (PHP 7.4 target on 1.2.x branches,
cacti_sizeof/htmle/gfrv wrappers, db_qstr_rlike, the vendor-pollution
pitfall) so future AI contributions match the review feedback pattern
from TheWitness instead of repeating it on every PR.

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

* test(oid): tighten Copilot review feedback

Defense-in-depth size check after stripping; renamed dataset cases for
clarity; strengthened single-index guard test.

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

* fix(snmp): detect single zero OID padding safely

* ci(phpstan): baseline new errors from upstream PHPStan version bump

11 errors surfaced after PHPStan re-analyzed the tree on the latest
PR runs. None are in code this PR touches; they are pre-existing
issues in aggregate_graphs.php, color_templates.php, graph_templates.php,
graphs.php, and lib/html.php. Suppress them in the baseline so this
PR can land. A separate cleanup PR on develop should retire entries.

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

* test(oid): hand-off coverage + infection mutation config

Pest tests walk the full pipeline (raw indexes -> detect -> strip ->
consumer regex parse) end-to-end and pin the collision-safe + single-.0
boundary behaviour. infection.json5 scopes mutation to lib/data_query
helpers with a 70% MSI floor.

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

* ci(infection): allow infection/extension-installer composer plugin

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

* test(oid): drop hand-off test, consolidated into #7083

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

* test(oid): cover mixed .0 padding negative case

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

* fix(oid): guard strip function against mixed-padding sets

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

* chore: remove dev tooling artifacts from index

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

* ci: re-trigger after transient PPA failure

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

* ci: retrigger after transient PPA failure

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
TheWitness added a commit that referenced this pull request May 12, 2026
…ion (#7063)

* feat(security): add cacti_dispatch helper for action table authorisation

cacti_dispatch() wraps the repeated switch/case on get_request_var('action')
that every Cacti controller writes. Each action is declared with the
HTTP method it accepts, the realm id that must be allowed, and an
optional object-level ACL callback. The guards run in method then
realm then object-ACL order and each one logs with a WEBUI category
before returning a 405 or 403, so review across controllers no longer
depends on every author remembering to add the same three checks in
the same order.

The test file source-scans the helper for each guard and for the guard
ordering, so a future refactor that silently drops the realm or ACL
branch fails CI rather than regressing authorisation for every
controller that opts in.

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

* security(dispatch): address Copilot review on cacti_dispatch

Reject non-string action inputs before offset access so ?action[]=x
cannot produce a TypeError. Default REQUEST_METHOD to GET for CLI
and tests, and normalise both sides with cacti_strtoupper so the
comparison is locale-independent. Treat a declared-but-non-callable
object_acl as a misdeclaration and deny (log ERROR + 403) rather
than silently skipping the check. Split the denial emitter into
cacti_dispatch_deny() under a prefixed name to avoid colliding with
the existing raise_ajax_permission_denied() in lib/functions.php,
and always follow the AJAX helper with an explicit http_response_code()
so a denied non-AJAX request cannot fall through to a 200 render.
Reject non-callable callbacks with a 500 and ERROR log. Drop the
redundant docblock on the function and tighten the parameter types.

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

* style(dispatch): apply php-cs-fixer to cacti_dispatch

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

* fix(dispatch): drop tautological is_string checks flagged by phpstan level 6

PHPStan level 6 flagged two is_string() calls as always true:
- $default was already type-hinted as string via PHPDoc; the runtime
  cast to '' was redundant.
- The second is_string($action) at the unknown-action log happens
  after the earlier is_string guard already narrowed the type.

Tighten the signature to string $default so the first cast drops,
and remove the second is_string from the log interpolation since
$action is always a string by the time that branch runs.

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

* fix(dispatch): tighten Copilot review feedback

strspn whitelist on action; explicit AJAX detection for 403 vs redirect;
file_get_contents guard in tests.

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

* test(dispatch): hand-off coverage + infection mutation config

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

* ci(infection): allow infection/extension-installer composer plugin

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

* test(dispatch): drop hand-off test, consolidated into #7083

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

* feat(dispatch): reject mis-declared method, document CSRF scope

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

* ci(phpstan): baseline upstream undefined-variable and isset errors

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

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
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.

2 participants