mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-29 08:06:58 +09:00
* test(security): add SQL interpolation classifier and ratchet baseline Tokenizes each db_fetch/db_execute call and classifies its SQL argument as prepared, static, qstr-escaped, opaque, identifier or value. The value bucket is the migration target: a raw variable interpolated into value position. The baseline records the current per-file value count so CI can block new ones. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci(security): gate new SQL value interpolation in quality workflow Runs the interpolation audit in --check mode so a pull request that adds an unparameterised value interpolation fails until it uses db_*_prepared(). Pins setup-php to a commit SHA. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * refactor(security): split prebuilt SQL fragments from scalar interpolation Cacti names query fragments consistently (sql_where, sql_join, ...). A variable with such a name is a SQL snippet, not a bindable scalar, so it now lands in a separate fragment category instead of inflating the value count. Extracts scan_source() and a library-only include guard so the classifier can be unit tested. Baseline value count drops from 373 to 300. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(security): cover the SQL interpolation classifier Unit tests exercise each category (prepared, static, qstr, value, identifier, fragment, opaque) against snippets, including IF EXISTS identifiers and mixed scalar/fragment statements. An integration test runs the CLI against the tree and asserts the committed baseline stays in sync. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(tooling): gate chdir to CLI runs and align checkout version The audit is require'd by the unit tests in library mode; running chdir() at load time leaked a working-directory change into other tests. Gate it behind the library sentinel. Also aligns the ratchet job's checkout to the version the rest of the workflow uses. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(tooling): use PHP 8 string helpers and short array syntax in audit Switches the classifier to str_starts_with/str_ends_with/str_contains and short array and list syntax, matching the idiom the rest of the codebase uses. Output is unchanged. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix installer failure handling * style(lib): align installer assignment per cs-fixer Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <thewitness@cacti.net>
81 lines
2.9 KiB
PHP
81 lines
2.9 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
// End-to-end checks for the SQL interpolation ratchet: run the actual CLI
|
|
// against the real tree the way CI does, then confirm the committed baseline
|
|
// still matches that tree.
|
|
|
|
function audit_run(string $args): array {
|
|
$root = dirname(__DIR__, 2);
|
|
$cmd = escapeshellarg(PHP_BINARY) . ' '
|
|
. escapeshellarg($root . '/tests/tools/sql_interpolation_audit.php')
|
|
. ' ' . $args . ' 2>&1';
|
|
|
|
$output = array();
|
|
$code = 0;
|
|
exec('cd ' . escapeshellarg($root) . ' && ' . $cmd, $output, $code);
|
|
|
|
return array('code' => $code, 'out' => implode("\n", $output));
|
|
}
|
|
|
|
test('ratchet passes on the current tree', function () {
|
|
$result = audit_run('--check');
|
|
expect($result['code'])->toBe(0)
|
|
->and($result['out'])->toContain('ratchet passed');
|
|
});
|
|
|
|
test('json output is well-formed and totals every category', function () {
|
|
$result = audit_run('--json');
|
|
expect($result['code'])->toBe(0);
|
|
|
|
$data = json_decode($result['out'], true);
|
|
expect($data)->toBeArray()
|
|
->and($data)->toHaveKey('totals')
|
|
->and($data)->toHaveKey('files')
|
|
->and($data['totals'])->toHaveKeys(
|
|
array('prepared', 'static', 'qstr', 'fragment', 'opaque', 'identifier', 'value')
|
|
);
|
|
});
|
|
|
|
test('committed baseline matches the current tree', function () {
|
|
$root = dirname(__DIR__, 2);
|
|
$baseline = json_decode(file_get_contents($root . '/tests/tools/sql_interpolation_baseline.json'), true);
|
|
expect($baseline)->toBeArray();
|
|
|
|
$data = json_decode(audit_run('--json')['out'], true);
|
|
|
|
$current = array();
|
|
foreach ($data['files'] as $path => $counts) {
|
|
if (($counts['value'] ?? 0) > 0) {
|
|
$current[$path] = $counts['value'];
|
|
}
|
|
}
|
|
|
|
ksort($baseline);
|
|
ksort($current);
|
|
|
|
// Drift here means someone changed SQL call sites without refreshing the
|
|
// baseline: php tests/tools/sql_interpolation_audit.php --write-baseline
|
|
expect($current)->toBe($baseline);
|
|
});
|
|
|
|
test('every baseline path still exists', function () {
|
|
$root = dirname(__DIR__, 2);
|
|
$baseline = json_decode(file_get_contents($root . '/tests/tools/sql_interpolation_baseline.json'), true);
|
|
|
|
foreach (array_keys($baseline) as $path) {
|
|
expect(file_exists($root . '/' . $path))->toBeTrue("missing baseline path: $path");
|
|
}
|
|
});
|