Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: "CodeQL"

on:
push:
branches: [main, master, develop, regression-audit]
paths-ignore:
- "**/*.php"
- "**/*.md"
pull_request:
branches: [main, master, develop, regression-audit]
paths-ignore:
- "**/*.php"
- "**/*.md"
Comment on lines +4 to +13
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

paths-ignore excludes all **/*.php, so CodeQL won’t run on the vast majority of changes in this PHP plugin. If the intent is to security-scan the plugin, remove the PHP ignore (and consider ignoring only vendor/ or generated paths instead).

Copilot uses AI. Check for mistakes.
schedule:
- cron: "30 1 * * 1"
workflow_dispatch:

concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["javascript-typescript", "python", "ruby"]
steps:
Comment on lines +31 to +35
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The CodeQL language matrix doesn’t include php, so even when the workflow runs it won’t analyze the primary language in this repository. Add PHP to the matrix (or switch to a single-language config) so CodeQL produces useful results for this codebase.

Copilot uses AI. Check for mistakes.
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Initialize CodeQL
uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3
with:
category: "/language:${{ matrix.language }}"
18 changes: 18 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "cacti/plugin_syslog",
"description": "plugin_syslog plugin for Cacti",
"license": "GPL-2.0-or-later",
"require-dev": {
"pestphp/pest": "^1.23"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"autoload-dev": {
"files": [
"tests/bootstrap.php"
]
}
}
14 changes: 14 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Pest configuration file.
*/

require_once __DIR__ . '/bootstrap.php';
106 changes: 106 additions & 0 deletions tests/Security/Php74CompatibilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify plugin source files do not use PHP 8.0+ syntax.
* Cacti 1.2.x plugins must remain compatible with PHP 7.4.
*/

describe('PHP 7.4 compatibility in syslog', function () {
$files = array(
'database.php',
'functions.php',
'setup.php',
'syslog.php',
'syslog_alerts.php',
'syslog_process.php',
'syslog_removal.php',
'syslog_reports.php',
);

it('does not use str_contains (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

Comment on lines +28 to +40
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

These tests silently skip files when realpath() or file_get_contents() fails, which can lead to false-green results. Prefer failing with an explicit assertion/message when a target file is missing/unreadable so the compatibility gate remains meaningful over time.

Copilot uses AI. Check for mistakes.
expect(preg_match('/\bstr_contains\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_contains() which requires PHP 8.0"
);
}
});

it('does not use str_starts_with (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\bstr_starts_with\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_starts_with() which requires PHP 8.0"
);
}
});

it('does not use str_ends_with (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\bstr_ends_with\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_ends_with() which requires PHP 8.0"
);
}
});

it('does not use nullsafe operator (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\?->/', $contents))->toBe(0,
"{$relativeFile} uses nullsafe operator which requires PHP 8.0"
);
}
});
});
64 changes: 64 additions & 0 deletions tests/Security/PreparedStatementConsistencyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify migrated files use prepared DB helpers exclusively.
* Catches regressions where raw db_execute/db_fetch_* calls creep back in.
*/

describe('prepared statement consistency in syslog', function () {
it('uses prepared DB helpers in all plugin files', function () {
$targetFiles = array(
'database.php',
'functions.php',
'setup.php',
'syslog.php',
'syslog_alerts.php',
'syslog_process.php',
'syslog_removal.php',
'syslog_reports.php',
);
Comment on lines +16 to +26
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

This test will flag legitimate db_* calls that must exist in this repo, so it’s likely to fail immediately (e.g., database.php contains return db_execute(...) inside the syslog_db_* wrappers, and setup.php has db_execute(...) calls for plugin metadata cleanup). Consider excluding database.php (and/or known-safe sections) from scanning, or tightening the check to only prohibit raw db_* usage outside the syslog DB wrapper layer (e.g., enforce syslog_db_* usage in non-wrapper files).

Copilot uses AI. Check for mistakes.

$rawPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)\s*\(/';
$preparedPattern = '/\bdb_(?:execute|fetch_row|fetch_assoc|fetch_cell)_prepared\s*\(/';

foreach ($targetFiles as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
Comment on lines +35 to +41
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

Silently continue-ing when a target file is missing or unreadable makes this test pass without actually validating anything. Prefer failing the test with a clear message when realpath() or file_get_contents() fails so CI can catch moved/renamed files or unexpected read errors.

Suggested change
continue;
}
$contents = file_get_contents($path);
if ($contents === false) {
continue;
throw new RuntimeException("Unable to resolve target file for prepared statement consistency test: {$relativeFile}");
}
$contents = file_get_contents($path);
if ($contents === false) {
throw new RuntimeException("Unable to read target file for prepared statement consistency test: {$path}");

Copilot uses AI. Check for mistakes.
}

$lines = explode("\n", $contents);
$rawCallsOutsideComments = 0;

foreach ($lines as $line) {
$trimmed = ltrim($line);

if (strpos($trimmed, '//') === 0 || strpos($trimmed, '*') === 0 || strpos($trimmed, '#') === 0) {
continue;
}

if (preg_match($rawPattern, $line) && !preg_match($preparedPattern, $line)) {
$rawCallsOutsideComments++;
}
}

expect($rawCallsOutsideComments)->toBe(0,
"File {$relativeFile} contains raw (unprepared) DB calls"
);
}
});
});
36 changes: 36 additions & 0 deletions tests/Security/SetupStructureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify setup.php defines required plugin hooks and info function.
*/

describe('syslog setup.php structure', function () {
$source = file_get_contents(realpath(__DIR__ . '/../../setup.php'));

Comment on lines +15 to +16
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

If setup.php can’t be resolved/read (e.g., path changes), $source becomes false and subsequent string expectations will behave unpredictably and/or emit warnings. Consider asserting that realpath() and file_get_contents() succeeded (fail fast with a clear message) before running toContain / toMatch expectations.

Suggested change
$source = file_get_contents(realpath(__DIR__ . '/../../setup.php'));
$setupPath = realpath(__DIR__ . '/../../setup.php');
expect($setupPath)
->not->toBeFalse('Failed to resolve setup.php path from ' . __DIR__);
$source = file_get_contents($setupPath);
expect($source)
->not->toBeFalse('Failed to read setup.php at path: ' . $setupPath);

Copilot uses AI. Check for mistakes.
it('defines plugin_syslog_install function', function () use ($source) {
expect($source)->toContain('function plugin_syslog_install');
});

it('defines plugin_syslog_version function', function () use ($source) {
expect($source)->toContain('function plugin_syslog_version');
});

it('defines plugin_syslog_uninstall function', function () use ($source) {
expect($source)->toContain('function plugin_syslog_uninstall');
});

it('returns version array with name key', function () use ($source) {
expect($source)->toMatch('/[\'\""]name[\'\""]\s*=>/');
});

it('returns version array with version key', function () use ($source) {
expect($source)->toMatch('/[\'\""]version[\'\""]\s*=>/');
});
});
Loading
Loading