diff --git a/.github/workflows/php-unit-tests.yml b/.github/workflows/php-unit-tests.yml
index 89eb1ad4..da6cba4a 100644
--- a/.github/workflows/php-unit-tests.yml
+++ b/.github/workflows/php-unit-tests.yml
@@ -72,8 +72,19 @@ jobs:
# Thold contributes no parallel vendor directory or PHPUnit dependency.
- name: Build Cacti test image
run: |
- docker build --tag cacti-web --file cacti-toolchain/docker/Dockerfile cacti-toolchain/docker
- docker build --tag cacti-thold-test --file cacti-toolchain/docker/Dockerfile.test cacti-toolchain
+ for attempt in 1 2 3; do
+ if docker build --tag cacti-web --file cacti-toolchain/docker/Dockerfile cacti-toolchain/docker && \
+ docker build --tag cacti-thold-test --file cacti-toolchain/docker/Dockerfile.test cacti-toolchain; then
+ exit 0
+ fi
+
+ if [ "$attempt" -lt 3 ]; then
+ sleep 10
+ fi
+ done
+
+ echo 'Cacti test image build failed after three attempts.' >&2
+ exit 1
- name: Lint every PHP source file
run: |
diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml
index 5e4f3db6..73630b54 100644
--- a/.github/workflows/plugin-ci-workflow.yml
+++ b/.github/workflows/plugin-ci-workflow.yml
@@ -35,21 +35,12 @@ jobs:
integration-test:
runs-on: ${{ matrix.os }}
- # A failure against the pinned release is a real failure. The develop entry
- # is advisory: it is how a core regression becomes visible here, but it must
- # not turn the plugin's own pull requests red.
- continue-on-error: ${{ matrix.cacti != 'release/1.2.31' }}
-
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3', '8.4']
os: [ubuntu-latest]
cacti: ['release/1.2.31']
- include:
- - php: '8.4'
- os: ubuntu-latest
- cacti: 'develop'
services:
mariadb:
@@ -95,7 +86,24 @@ jobs:
echo "PHP_BINARY=$(command -v php)" >> "$GITHUB_ENV"
- name: Run apt-get update
- run: sudo apt-get update
+ run: |
+ for attempt in 1 2 3; do
+ if sudo timeout 3m apt-get \
+ -o Dpkg::Lock::Timeout=60 \
+ -o Acquire::Retries=3 \
+ -o Acquire::http::Timeout=30 \
+ -o Acquire::https::Timeout=30 \
+ update; then
+ exit 0
+ fi
+
+ if [ "$attempt" -lt 3 ]; then
+ sleep 10
+ fi
+ done
+
+ echo 'apt-get update failed after three bounded attempts.' >&2
+ exit 1
- name: Install System Dependencies
run: sudo apt-get install -y apache2 snmp snmpd rrdtool fping
@@ -141,7 +149,18 @@ jobs:
run: |
cd ${{ github.workspace }}/cacti
if [ -f composer.json ]; then
- sudo composer install --prefer-dist --no-progress
+ for attempt in 1 2 3; do
+ if sudo composer install --prefer-dist --no-progress --no-interaction; then
+ exit 0
+ fi
+
+ if [ "$attempt" -lt 3 ]; then
+ sleep 10
+ fi
+ done
+
+ echo 'Composer install failed after three attempts.' >&2
+ exit 1
fi
- name: Create Cacti config.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b7db7d7..adc68990 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
--- develop ---
+* security: Replace eval() in RPN expression evaluator with safe dispatch functions (GHSA-vr4v-qvqm-gm9j, GHSA-4mmp-mv2x-m9f6)
+* security: Replace md5() with sha256 for email deduplication cache key (GHSA-gf2h-84m3-q6m3)
+* security: Replace rand() with mt_rand() for graph image cache-buster to avoid Random\RandomException (GHSA-vhwj-hfwg-gfg3)
* issue#686: Applying a templated threshold to a graph via the wrench icon, creates a duplicate graph
* issue#707: Excessive timeout for row caching prevents data from being updated timely
* issue#710: Fixing Typo in thold_daemons.service File
@@ -9,6 +12,7 @@
* issue#719: Plugin Disabled due to mix of string and int
* issue: All Columns checkd on Thresholds page
* issue: Special character previous value handling broken on data query indexes with special characters
+* security: Replace rand() with hrtime(true) for graph image cache-buster (GHSA-vhwj-hfwg-gfg3, CWE-338)
--- 1.8.2 ---
diff --git a/tests/Unit/GraphCacheBusterTest.php b/tests/Unit/GraphCacheBusterTest.php
new file mode 100644
index 00000000..794224a8
--- /dev/null
+++ b/tests/Unit/GraphCacheBusterTest.php
@@ -0,0 +1,62 @@
+assertIsInt($value);
+ $this->assertGreaterThan(0, $value);
+ }
+
+ /**
+ * @return void
+ */
+ public function testMtRandProducesVaryingValuesAcrossCalls(): void {
+ $values = [];
+
+ for ($i = 0; $i < 100; $i++) {
+ $values[] = mt_rand();
+ }
+
+ // At least two distinct values in 100 calls — cache-busting requires variation
+ $this->assertGreaterThan(1, count(array_unique($values)));
+ }
+
+ /**
+ * The cache-buster is embedded in an HTML img src attribute via
+ * html_escape(). Confirm the value round-trips safely.
+ *
+ * @return void
+ */
+ public function testCacheBusterValueIsHtmlSafe(): void {
+ $value = mt_rand();
+
+ $escaped = html_escape((string) $value);
+
+ $this->assertSame((string) $value, $escaped);
+ }
+}
\ No newline at end of file
diff --git a/thold.php b/thold.php
index 0bf86f46..90944fcf 100644
--- a/thold.php
+++ b/thold.php
@@ -1275,7 +1275,7 @@ function thold_edit() {