From e85de5f6c6588ad52f2b60e817d7aaa20017505c Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Mon, 12 Oct 2026 21:48:54 -0700 Subject: [PATCH 1/5] ci: pin fixtures and install locked shared test dependencies Signed-off-by: Thomas Vincent --- .github/workflows/plugin-ci-workflow.yml | 64 +++++++++++++----------- CHANGELOG.md | 1 + 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index a62c720..9498ce5 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -31,12 +31,20 @@ on: - main - develop +permissions: + contents: read + env: CACTI: 1.2.x - COMPOSER_ALLOW_SUPERUSER: 1 + CACTI_REF: d6d230f712c0af1a0391646f65364950ebf131af # 1.2.x fixture + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: integration-test: + timeout-minutes: 45 runs-on: ${{ matrix.os }} strategy: @@ -47,7 +55,7 @@ jobs: services: mariadb: - image: mariadb:11.8 + image: mariadb:11.8@sha256:6422478cb8e159f080fb1d8ccf65101e26fe51385787fde7d16c3b165a331f15 env: MYSQL_ROOT_PASSWORD: cactiroot MYSQL_DATABASE: cacti @@ -67,13 +75,15 @@ jobs: - name: Checkout Cacti uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + persist-credentials: false repository: Cacti/cacti - ref: ${{ env.CACTI }} + ref: ${{ env.CACTI_REF }} path: cacti - name: Checkout Maint Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + persist-credentials: false path: cacti/plugins/maint # patch-coverage.php diffs against the base branch below. fetch-depth: 0 @@ -82,6 +92,7 @@ jobs: uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0 with: php-version: ${{ matrix.php }} + tools: composer:2.10.3 extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring, snmp ini-values: "post_max_size=256M, max_execution_time=60, date.timezone=America/New_York" coverage: xdebug @@ -131,29 +142,24 @@ jobs: mysql "$MYSQL_AUTH_USR" cacti < ${{ github.workspace }}/cacti/cacti.sql mysql "$MYSQL_AUTH_USR" -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/usr/bin/php')" cacti - - name: Validate composer files - run: | - cd ${{ github.workspace }}/cacti - if [ -f composer.json ]; then - sudo composer validate --strict || true - fi - - - name: Install Composer Dependencies - run: | - cd ${{ github.workspace }}/cacti - if [ -f composer.json ]; then - sudo composer config --no-plugins allow-plugins.pestphp/pest-plugin true - sudo composer require --no-progress --no-interaction "pestphp/pest: ^3" "pestphp/pest-plugin-drift: ^3.0" - sudo rm -f composer.lock - sudo composer install --dev --no-progress - fi - - - name: Restore vendor ownership for Pest - run: | - cd ${{ github.workspace }}/cacti - if [ -f composer.json ]; then - sudo chown -R runner:runner include/vendor composer.lock - fi + # Core's release archives exclude .github and tests. Fetch these paths + # through Git at the immutable helper revision instead of an Action archive. + - name: Checkout shared CI tooling + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Cacti/cacti + ref: b1d8dd6a03a3c756a2b22dfe3a0c744b1f11116c + persist-credentials: false + path: ci-tools + sparse-checkout: | + .github/actions/setup-plugin-dependencies + tests/tools/plugin-harness + + - name: Install locked Cacti and plugin test dependencies + uses: ./ci-tools/.github/actions/setup-plugin-dependencies + with: + cacti-directory: cacti + install-core-dev: 'false' - name: Create Cacti config.php run: | @@ -194,7 +200,7 @@ jobs: - name: Run Pest Unit Tests run: | cd ${{ github.workspace }}/cacti - include/vendor/bin/pest --configuration=plugins/maint/phpunit.xml \ + "$CACTI_PEST_BIN" --configuration=plugins/maint/phpunit.xml \ --coverage-clover=plugins/maint/coverage/clover.xml # Whole-file coverage is meaningless here: most of the plugin only runs @@ -228,12 +234,12 @@ jobs: - name: Run Linter on base code if: ${{ env.CACTI != '1.2.x' }} - run: sudo composer run-script lint ${{ github.workspace }}/cacti/plugins/maint + run: composer run-script lint ${{ github.workspace }}/cacti/plugins/maint working-directory: ${{ github.workspace }}/cacti - name: Checking coding standards on base code if: ${{ env.CACTI != '1.2.x' }} - run: sudo composer run-script phpcsfixer ${{ github.workspace }}/cacti/plugins/maint + run: composer run-script phpcsfixer ${{ github.workspace }}/cacti/plugins/maint working-directory: ${{ github.workspace }}/cacti - name: Run PHPStan at Level 6 on base code outside of Composer due to technical issues diff --git a/CHANGELOG.md b/CHANGELOG.md index e789d7f..af0e454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # ChangeLog --- develop --- +* security: Pin CI fixtures and shared test dependencies, disable persisted checkout credentials, and limit workflow permissions and runtime * security: Replace the confirmation pages' inline `onClick='cactiReturnTo()'` Cancel buttons with the CSP-safe `cactiReturnTo` class so the pages no longer trip Cacti's Content-Security-Policy script-src-attr directive; see the README for the one-time `applySkin()` snippet needed when running Cacti below 1.2.31 (1.2.31+ binds the class automatically) * issue: Fix an uncaught `TypeError` from `html_nav_bar()` (the schedules, devices, webseer and servcheck lists passed the `page`/`rows`/`total_rows` request values as strings, which the typed core signature rejects under `declare(strict_types=1)`); the paging arguments are now cast to `int` From c81b36c77e2618a86b0f4aa1237e6107f62b0603 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Mon, 12 Oct 2026 22:02:41 -0700 Subject: [PATCH 2/5] test: load locked test dependencies in isolated child processes Signed-off-by: Thomas Vincent --- .github/workflows/plugin-ci-workflow.yml | 1 + tests/bootstrap-unit.php | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index 9498ce5..91e60e7 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -200,6 +200,7 @@ jobs: - name: Run Pest Unit Tests run: | cd ${{ github.workspace }}/cacti + export CACTI_TEST_AUTOLOAD="$(dirname "$CACTI_PEST_BIN")/../autoload.php" "$CACTI_PEST_BIN" --configuration=plugins/maint/phpunit.xml \ --coverage-clover=plugins/maint/coverage/clover.xml diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index 7d7bc17..6e0e3ce 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -68,6 +68,18 @@ } require_once $autoload; +// Isolated child tests need PHPUnit from the separate locked CI harness too. +// The ordinary core Composer toolchain already provides it through $autoload. +$test_autoload = getenv('CACTI_TEST_AUTOLOAD'); + +if ($test_autoload !== false && $test_autoload !== '') { + if (!is_readable($test_autoload)) { + throw new RuntimeException("Plugin test autoloader is not readable: $test_autoload"); + } + + require_once $test_autoload; +} + require_once __DIR__ . '/TestCase.php'; /* From 5a9ec0c885f1b6d93f8932117aa9a8b05c024acd Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Mon, 12 Oct 2026 22:03:02 -0700 Subject: [PATCH 3/5] ci: preserve test autoloader path assignment errors Signed-off-by: Thomas Vincent --- .github/workflows/plugin-ci-workflow.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index 91e60e7..5ed5e47 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -148,7 +148,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/cacti - ref: b1d8dd6a03a3c756a2b22dfe3a0c744b1f11116c + ref: f3ff777992875740e4665fedfe2eff8335f5b894 persist-credentials: false path: ci-tools sparse-checkout: | @@ -200,7 +200,8 @@ jobs: - name: Run Pest Unit Tests run: | cd ${{ github.workspace }}/cacti - export CACTI_TEST_AUTOLOAD="$(dirname "$CACTI_PEST_BIN")/../autoload.php" + CACTI_TEST_AUTOLOAD="$(dirname "$CACTI_PEST_BIN")/../autoload.php" + export CACTI_TEST_AUTOLOAD "$CACTI_PEST_BIN" --configuration=plugins/maint/phpunit.xml \ --coverage-clover=plugins/maint/coverage/clover.xml From 6a2c886edfa807791135f64ae9f4fbf6b45d60c5 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sat, 17 Oct 2026 22:26:05 -0700 Subject: [PATCH 4/5] docs(changelog): list CI hardening as a dev entry Signed-off-by: Thomas Vincent --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af0e454..3159111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # ChangeLog --- develop --- -* security: Pin CI fixtures and shared test dependencies, disable persisted checkout credentials, and limit workflow permissions and runtime * security: Replace the confirmation pages' inline `onClick='cactiReturnTo()'` Cancel buttons with the CSP-safe `cactiReturnTo` class so the pages no longer trip Cacti's Content-Security-Policy script-src-attr directive; see the README for the one-time `applySkin()` snippet needed when running Cacti below 1.2.31 (1.2.31+ binds the class automatically) * issue: Fix an uncaught `TypeError` from `html_nav_bar()` (the schedules, devices, webseer and servcheck lists passed the `page`/`rows`/`total_rows` request values as strings, which the typed core signature rejects under `declare(strict_types=1)`); the paging arguments are now cast to `int` * refactor: Move schema management into includes/database.php (the thold model) and relocate functions.php into includes/; setup.php delegates via require_once, and plugin_maint_check_upgrade() now refreshes the schema via db_update_table(), re-registers the is_device_in_maintenance hook against includes/functions.php, and updates the full plugin_config row on a version change. Switches every file inclusion from include/include_once to require/require_once * dev: Enforce patch coverage of changed lines in CI and remove the inert COMPOSER_ROOT_VERSION env from the Pest step +* dev: Pin CI fixtures and shared test dependencies, disable persisted checkout credentials, and limit workflow permissions and runtime * feature: Add a standard Cacti search filter to the Maintenance Schedules list that also matches the hostname/description/location/site name of associated Thold devices and the hostname/description of associated Servcheck tests * security: Add a version-safe CSP nonce (`plugin_maint_csp_nonce()`) to every inline `