hardening: replace eval callback execution in autocomplete handling#268
hardening: replace eval callback execution in autocomplete handling#268somethingwithproof wants to merge 4 commits intoCacti:developfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens the Syslog plugin’s autocomplete handling by removing eval() usage when executing onChange callbacks in js/functions.js, reducing client-side code execution risk and improving security posture.
Changes:
- Replaced
eval(onChange)with a safer helper (runSyslogAutocompleteOnChange) that validates and invokes only named global functions. - Added a regression script to ensure
eval(no longer appears injs/functions.jsand that the safe helper remains present. - Updated CI to run plugin regression scripts and added a changelog entry for issue #260.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
js/functions.js |
Introduces safe callback execution helper and replaces eval() usage in autocomplete select handler. |
tests/regression/issue260_remove_eval_callback_test.php |
Adds a regression check to prevent reintroduction of eval() in autocomplete callback execution. |
.github/workflows/plugin-ci-workflow.yml |
Runs regression scripts during CI for the syslog plugin. |
CHANGELOG.md |
Documents the hardening change under develop for issue #260. |
| /** | ||
| * Initialize autocomplete for form dropdown fields | ||
| * @param {string} formName - The name of the form field | ||
| * @param {string} callback - The AJAX callback action | ||
| * @param {string} onChange - The onChange callback to execute when selection changes | ||
| */ | ||
| function runSyslogAutocompleteOnChange(onChange) { | ||
| if (typeof onChange !== 'string') { |
There was a problem hiding this comment.
The JSDoc block describes initSyslogAutocomplete(...), but it now sits directly above runSyslogAutocompleteOnChange(...), so the documentation no longer matches the function it is annotating. Please move this block to initSyslogAutocomplete (or split into two doc blocks so the helper is documented separately).
| if (strpos($javascript, 'function runSyslogAutocompleteOnChange(onChange)') === false) { | ||
| fwrite(STDERR, "Safe autocomplete callback helper is missing.\n"); | ||
| exit(1); | ||
| } | ||
|
|
||
| if (strpos($javascript, "if (!callbackName.match(/^[A-Za-z_$][A-Za-z0-9_$]*$/))") === false) { | ||
| fwrite(STDERR, "Autocomplete callback name validation is missing.\n"); | ||
| exit(1); | ||
| } | ||
|
|
||
| if (strpos($javascript, "if (typeof window[callbackName] === 'function')") === false || | ||
| strpos($javascript, 'window[callbackName]();') === false) { | ||
| fwrite(STDERR, "Expected function-reference callback execution is missing.\n"); | ||
| exit(1); | ||
| } | ||
|
|
||
| if (strpos($javascript, 'runSyslogAutocompleteOnChange(onChange);') === false) { |
There was a problem hiding this comment.
This regression test is very brittle because it asserts exact source-code substrings (including whitespace and exact function signature). Consider using preg_match() with more flexible patterns (e.g., look for runSyslogAutocompleteOnChange definition and the identifier-validation regex) so harmless refactors/formatting changes don’t break CI.
| if (strpos($javascript, 'function runSyslogAutocompleteOnChange(onChange)') === false) { | |
| fwrite(STDERR, "Safe autocomplete callback helper is missing.\n"); | |
| exit(1); | |
| } | |
| if (strpos($javascript, "if (!callbackName.match(/^[A-Za-z_$][A-Za-z0-9_$]*$/))") === false) { | |
| fwrite(STDERR, "Autocomplete callback name validation is missing.\n"); | |
| exit(1); | |
| } | |
| if (strpos($javascript, "if (typeof window[callbackName] === 'function')") === false || | |
| strpos($javascript, 'window[callbackName]();') === false) { | |
| fwrite(STDERR, "Expected function-reference callback execution is missing.\n"); | |
| exit(1); | |
| } | |
| if (strpos($javascript, 'runSyslogAutocompleteOnChange(onChange);') === false) { | |
| if (!preg_match('/function\s+runSyslogAutocompleteOnChange\s*\(\s*onChange\s*\)/', $javascript)) { | |
| fwrite(STDERR, "Safe autocomplete callback helper is missing.\n"); | |
| exit(1); | |
| } | |
| if (!preg_match('~callbackName\.match\s*\(\s*/\^[A-Za-z_$][A-Za-z0-9_$]*\$/~', $javascript)) { | |
| fwrite(STDERR, "Autocomplete callback name validation is missing.\n"); | |
| exit(1); | |
| } | |
| if (!preg_match('/if\s*\(\s*typeof\s+window\[callbackName\]\s*===\s*[\'"]function[\'"]\s*\)/', $javascript) || | |
| !preg_match('/window\[callbackName\]\s*\(\s*\)\s*;/', $javascript)) { | |
| fwrite(STDERR, "Expected function-reference callback execution is missing.\n"); | |
| exit(1); | |
| } | |
| if (!preg_match('/runSyslogAutocompleteOnChange\s*\(\s*onChange\s*\)\s*;/', $javascript)) { |
There was a problem hiding this comment.
Fixed -- test now uses preg_match() patterns.
| if (strpos($javascript, "if (typeof window[callbackName] === 'function')") === false || | ||
| strpos($javascript, 'window[callbackName]();') === false) { |
There was a problem hiding this comment.
This assertion depends on an exact statement string (window[callbackName]();) which can easily change with formatting or minor refactors (e.g., adding arguments). Prefer matching the behavior more flexibly (regex) or asserting the absence of eval( plus presence of window[callbackName] invocation without hardcoding the full line.
| if (strpos($javascript, "if (typeof window[callbackName] === 'function')") === false || | |
| strpos($javascript, 'window[callbackName]();') === false) { | |
| $hasCallbackTypeCheck = preg_match( | |
| '/if\s*\(\s*typeof\s+window\s*\[\s*callbackName\s*\]\s*===\s*[\'"]function[\'"]\s*\)/', | |
| $javascript | |
| ) === 1; | |
| $hasCallbackInvocation = preg_match( | |
| '/window\s*\[\s*callbackName\s*\]\s*\(/', | |
| $javascript | |
| ) === 1; | |
| if (!$hasCallbackTypeCheck || !$hasCallbackInvocation) { |
|
Fixed in ea7668d -- added JSDoc to |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Add JSDoc block to the runSyslogAutocompleteOnChange helper so both autocomplete functions are documented. Replace exact-substring strpos check in the regression test with preg_match to tolerate minor whitespace variations in the signature without weakening the security assertion. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…test strpos() exact matches are brittle to whitespace/formatting changes. preg_match() with flexible patterns assert the same semantics without coupling to exact source formatting. Signed-off-by: Thomas Vincent <thomas@somethingwithproof.com> Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
a050c63 to
fef4ca7
Compare
Summary
eval(onChange)with safe function-reference execution ininitSyslogAutocomplete()tests/regression/*.phpexistValidation
Fixes #260