diff --git a/.github/workflows/php-syntax.yml b/.github/workflows/php-syntax.yml new file mode 100644 index 00000000..07fa0d8d --- /dev/null +++ b/.github/workflows/php-syntax.yml @@ -0,0 +1,52 @@ +name: PHP Syntax + +on: + pull_request: + push: + branches: + - develop + +permissions: + contents: read + +concurrency: + group: php-syntax-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: PHP ${{ matrix.php }} syntax + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: none + coverage: none + + - name: Show PHP version + run: php -v + + - name: Guard against corrupted refactor patterns + run: | + set -euo pipefail + if grep -R -n -E '\b(is_|in_|call_user_func_|port_list_to_|mactrack_display_|mactrack_device_action_)\[' --include='*.php' .; then + echo "Detected corrupted call-pattern rewrite(s)." >&2 + exit 1 + fi + + - name: Lint PHP files + run: | + set -euo pipefail + git ls-files '*.php' | while IFS= read -r f; do + php -l "$f" + done diff --git a/.gitignore b/.gitignore index eb716067..32791f06 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ # +-------------------------------------------------------------------------+ locales/po/*.mo +.omc/ diff --git a/images/index.php b/images/index.php index 828ecbeb..06a9f5c2 100644 --- a/images/index.php +++ b/images/index.php @@ -1,4 +1,6 @@ ' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> @@ -3665,7 +3666,7 @@ function mactrack_site_filter($page = 'mactrack_sites.php') { if (get_request_var('device_type_id') == $device_type['device_type_id']) { print ' selected'; - } print '>' . $device_type['description'] . ' (' . $device_type['sysDescr_match'] . ')'; + } print '>' . html_escape($device_type['description']) . ' (' . html_escape($device_type['sysDescr_match']) . ')'; } } ?> diff --git a/lib/mactrack_h3c_3com.php b/lib/mactrack_h3c_3com.php index f28d695a..5340fd53 100644 --- a/lib/mactrack_h3c_3com.php +++ b/lib/mactrack_h3c_3com.php @@ -1,4 +1,6 @@ 0)) { // $devices holds the whole row from host table // now fetch the related device from mac_track_devices, if any - $mt_device = db_fetch_row('SELECT * from mac_track_devices WHERE host_id=' . $device['id']); + $mt_device = db_fetch_row_prepared('SELECT * FROM mac_track_devices WHERE host_id = ?', [$device['id']]); if (is_array($mt_device) && $mt_device) { if (!isset($mt_device['snmp_engine_id'])) { diff --git a/mactrack_ajax.php b/mactrack_ajax.php index 0078e00b..25997296 100644 --- a/mactrack_ajax.php +++ b/mactrack_ajax.php @@ -1,4 +1,6 @@ - () + () @@ -1178,7 +1180,7 @@ function mactrack_device_filter() { if (get_request_var('site_id') == $site['site_id']) { print ' selected'; - } print '>' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> diff --git a/mactrack_import_ouidb.php b/mactrack_import_ouidb.php index 250b8761..bb0b91ab 100644 --- a/mactrack_import_ouidb.php +++ b/mactrack_import_ouidb.php @@ -1,4 +1,6 @@ ' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> @@ -501,7 +503,7 @@ function mactrack_ip_address_filter() { if (get_request_var('device_id') == $filter_device['device_id']) { print ' selected'; - } print '>' . $filter_device['device_name'] . '(' . $filter_device['hostname'] . ')' . ''; + } print '>' . html_escape($filter_device['device_name']) . '(' . html_escape($filter_device['hostname']) . ')' . ''; } } ?> diff --git a/mactrack_view_devices.php b/mactrack_view_devices.php index 548260a8..5f0a54c4 100644 --- a/mactrack_view_devices.php +++ b/mactrack_view_devices.php @@ -1,4 +1,6 @@ ' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> diff --git a/mactrack_view_dot1x.php b/mactrack_view_dot1x.php index ae9f5dbc..286b8243 100644 --- a/mactrack_view_dot1x.php +++ b/mactrack_view_dot1x.php @@ -1,4 +1,6 @@ ' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> diff --git a/mactrack_view_graphs.php b/mactrack_view_graphs.php index ee9fc7d6..f001689a 100644 --- a/mactrack_view_graphs.php +++ b/mactrack_view_graphs.php @@ -1,4 +1,6 @@ ' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> @@ -616,7 +618,7 @@ function mactrack_filter_table() { if (get_request_var('device_id') == $device_id) { print ' selected'; - } print '>' . $device_name . ''; + } print '>' . html_escape($device_name) . ''; } } ?> diff --git a/mactrack_view_ips.php b/mactrack_view_ips.php index 8cc711e8..c93935d1 100644 --- a/mactrack_view_ips.php +++ b/mactrack_view_ips.php @@ -1,4 +1,6 @@ ' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> diff --git a/mactrack_view_macs.php b/mactrack_view_macs.php index f19f3c09..9e70702a 100644 --- a/mactrack_view_macs.php +++ b/mactrack_view_macs.php @@ -1,4 +1,6 @@ false]); // nosemgrep: php.lang.security.unserialize-use.unserialize-use -- object injection blocked by allowed_classes:false; mac/ip validated below foreach ($selected_items as $mac=>$ip) { if (!filter_var($mac, FILTER_VALIDATE_MAC)) { @@ -147,7 +149,7 @@ function form_actions() { } if (!isset($mac_address_array[$mac])) { - $mac_address_list .= '
  • ' . mactrack_format_mac($mac) . '
  • '; + $mac_address_list .= '
  • ' . html_escape(mactrack_format_mac($mac)) . '
  • '; // nosemgrep: php.lang.security.tainted-user-input-in-php-script.tainted-user-input-in-php-script -- mac extracted from POST key, sanitize_search_string applied, html_escape applied at output $mac_address_array[$mac] = $ip; } } @@ -185,7 +187,8 @@ function form_actions() { $save_html = "'; } - print " + print // nosemgrep: php.lang.security.injection.printed-request.printed-request -- drp_action validated; selected_items values from POST keys sanitized via sanitize_search_string + " @@ -1123,7 +1126,7 @@ function mactrack_mac_filter() { if (get_request_var('site_id') == $site['site_id']) { print ' selected'; - } print '>' . $site['site_name'] . ''; + } print '>' . html_escape($site['site_name']) . ''; } } ?> @@ -1154,7 +1157,7 @@ function mactrack_mac_filter() { if (get_request_var('device_id') == $filter_device['device_id']) { print ' selected'; - } print '>' . $filter_device['device_name'] . '(' . $filter_device['hostname'] . ')' . ''; + } print '>' . html_escape($filter_device['device_name']) . '(' . html_escape($filter_device['hostname']) . ')' . ''; } } ?> diff --git a/mactrack_view_sites.php b/mactrack_view_sites.php index 655bbc20..74f71627 100644 --- a/mactrack_view_sites.php +++ b/mactrack_view_sites.php @@ -1,4 +1,6 @@ + + + + ./tests/Unit + + + ./tests/Handoff + + + ./tests/Integration + + + ./tests/Mutation + + + ./tests/Smoke + + + + + + diff --git a/setup.php b/setup.php index 81ffe04b..4a9723eb 100644 --- a/setup.php +++ b/setup.php @@ -1,4 +1,6 @@ toContain('function plugin_mactrack_install'); + }); + + it('defines plugin_mactrack_version function', function () use ($setup) { + expect($setup)->toContain('function plugin_mactrack_version'); + }); + + it('defines plugin_mactrack_uninstall function', function () use ($setup) { + expect($setup)->toContain('function plugin_mactrack_uninstall'); + }); + + it('registers hooks in install function', function () use ($setup) { + expect($setup)->toContain('api_plugin_register_hook'); + }); + + it('reads version from INFO ini file', function () use ($setup) { + expect($setup)->toContain('parse_ini_file'); + }); + + it('has INFO file with required fields', function () { + $info = parse_ini_file(realpath(__DIR__ . '/../../INFO')); + expect($info)->toHaveKey('name'); + expect($info)->toHaveKey('version'); + expect($info['name'])->toBe('mactrack'); + }); +}); + +describe('mactrack required entry points', function () { + $setupSource = file_get_contents(realpath(__DIR__ . '/../../setup.php')); + + it('defines plugin_mactrack_check_config', function () use ($setupSource) { + expect($setupSource)->toContain('function plugin_mactrack_check_config'); + }); + + it('defines plugin_mactrack_upgrade', function () use ($setupSource) { + expect($setupSource)->toContain('function plugin_mactrack_upgrade'); + }); +}); diff --git a/tests/Handoff/XssEscapingHandoffTest.php b/tests/Handoff/XssEscapingHandoffTest.php new file mode 100644 index 00000000..a66d1c41 --- /dev/null +++ b/tests/Handoff/XssEscapingHandoffTest.php @@ -0,0 +1,37 @@ +' . $site['site_name'] . ''; + $unescaped = preg_match("/print\s+'>'\\s*\\.\\s*\\\$site\['site_name'\]\\s*\\.\\s*'<\\/option>'/", $src); + expect($unescaped)->toBe(0, 'site_name must be wrapped in html_escape() before printing'); + }); + + it('mactrack_view_arp.php has no unescaped device_name in option tags', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../mactrack_view_arp.php')); + $unescaped = preg_match("/print\s+'>'\\s*\\.\\s*\\\$filter_device\['device_name'\]\\s*\\./", $src); + expect($unescaped)->toBe(0, 'device_name must be wrapped in html_escape() before printing'); + }); + + it('mactrack_view_macs.php has no unescaped site_name in option tags', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../mactrack_view_macs.php')); + $unescaped = preg_match("/print\s+'>'\\s*\\.\\s*\\\$site\['site_name'\]\\s*\\.\\s*'<\\/option>'/", $src); + expect($unescaped)->toBe(0); + }); + + it('mactrack_devices.php has no unescaped device_name direct print', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../mactrack_devices.php')); + // verify escaped version is present and bare print is absent + expect($src)->toContain('html_escape($device[\'device_name\'])'); + expect($src)->not->toContain('print $device[\'device_name\']'); + }); + + it('lib/mactrack_functions.php has no unescaped description in option tags', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../lib/mactrack_functions.php')); + $unescaped = preg_match("/print\s+'>'\\s*\\.\\s*\\\$device_type\['description'\]\\s*\\./", $src); + expect($unescaped)->toBe(0, 'description must be wrapped in html_escape()'); + }); +}); diff --git a/tests/Integration/PreparedStatementTest.php b/tests/Integration/PreparedStatementTest.php new file mode 100644 index 00000000..4cbe6bdb --- /dev/null +++ b/tests/Integration/PreparedStatementTest.php @@ -0,0 +1,43 @@ +toBeEmpty( + 'Found request-var SQL injection: ' . implode("\n", $violations) + ); + }); + + it('uses db_fetch_assoc_prepared in lib/mactrack_functions.php for device_id query', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../lib/mactrack_functions.php')); + expect($src)->toContain('db_fetch_assoc_prepared('); + expect($src)->toContain('WHERE device_id = ?'); + expect($src)->not->toContain("WHERE device_id='\""); + }); + + it('lib/mactrack_functions.php has no string-concatenated device_id in SELECT queries', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../lib/mactrack_functions.php')); + $unsafe = preg_match('/db_fetch_assoc\s*\(\s*["\']SELECT[^"\']*WHERE\s+device_id\s*=\s*["\']/', $src); + expect($unsafe)->toBe(0, 'device_id must be parameterized via prepared statement'); + }); +}); diff --git a/tests/Mutation/FixedBugRegressionTest.php b/tests/Mutation/FixedBugRegressionTest.php new file mode 100644 index 00000000..6e81007b --- /dev/null +++ b/tests/Mutation/FixedBugRegressionTest.php @@ -0,0 +1,97 @@ +toContain("['allowed_classes' => false]"); + }); + + it('the allowed_classes option appears on the same unserialize call as get_nfilter_request_var', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../mactrack_view_macs.php')); + $pattern = "/unserialize\s*\(\s*get_nfilter_request_var\s*\([^)]+\)\s*,\s*\['allowed_classes'\s*=>\s*false\]\s*\)/"; + expect((bool) preg_match($pattern, $src))->toBeTrue('allowed_classes must be on the same unserialize call'); + }); + + it('has no unserialize call without allowed_classes on user input', function () { + $src = file_get_contents(realpath(__DIR__ . '/../../mactrack_view_macs.php')); + $unsafe = preg_match_all( + "/unserialize\s*\(\s*get_nfilter_request_var\s*\([^)]+\)\s*\)(?!\s*;?\s*\/\/\s*nosemgrep)/", + $src + ); + expect($unsafe)->toBe(0); + }); +}); + +describe('passthru integer injection fix regression', function () { + $funcs = file_get_contents(realpath(__DIR__ . '/../../lib/mactrack_functions.php')); + + it('device rescan does not concatenate raw device_id string into command', function () use ($funcs) { + // Before fix: $extra_args = ' -id=' . $dbinfo['device_id'] concatenated as string + expect($funcs)->not->toContain("'-id=' . \$dbinfo['device_id']"); + }); + + it('site scan does not concatenate raw site_id string into command', function () use ($funcs) { + // Before fix: ' -sid=' . $dbinfo['site_id'] (uncast) + expect($funcs)->not->toContain("'-sid=' . \$dbinfo['site_id']"); + }); + + it('device rescan uses int cast for device_id', function () use ($funcs) { + expect($funcs)->toContain('(int)$dbinfo[\'device_id\']'); + }); + + it('site scan uses int cast for site_id', function () use ($funcs) { + expect($funcs)->toContain('(int)$dbinfo[\'site_id\']'); + }); +}); + +describe('SQL prepared statement fix regression', function () { + $funcs = file_get_contents(realpath(__DIR__ . '/../../lib/mactrack_functions.php')); + + it('mac_track_interfaces query uses prepared statement', function () use ($funcs) { + expect($funcs)->toContain('db_fetch_assoc_prepared('); + // the old raw-concat form must be gone + expect($funcs)->not->toContain('mac_track_interfaces WHERE device_id='); + }); + + it('mac_track_interfaces query uses ? placeholder', function () use ($funcs) { + expect($funcs)->toMatch('/db_fetch_assoc_prepared\s*\(\s*\'SELECT \* FROM mac_track_interfaces WHERE device_id = \?/'); + }); +}); + +describe('XSS fix regression', function () { + it('html_escape applied to site_name in all five view files', function () { + $viewFiles = [ + 'mactrack_view_arp.php', + 'mactrack_view_macs.php', + 'mactrack_view_ips.php', + 'mactrack_view_interfaces.php', + 'mactrack_view_dot1x.php', + ]; + $root = realpath(__DIR__ . '/../../'); + $missing = []; + foreach ($viewFiles as $f) { + $src = file_get_contents("$root/$f"); + if (!str_contains($src, "html_escape(\$site['site_name'])")) { + $missing[] = $f; + } + } + expect($missing)->toBeEmpty('missing html_escape for site_name: ' . implode(', ', $missing)); + }); + + it('html_escape applied to device_name and hostname in arp and macs views', function () { + $root = realpath(__DIR__ . '/../../'); + $missing = []; + foreach (['mactrack_view_arp.php', 'mactrack_view_macs.php'] as $f) { + $src = file_get_contents("$root/$f"); + if (!str_contains($src, "html_escape(\$filter_device['device_name'])")) { + $missing[] = "$f:device_name"; + } + if (!str_contains($src, "html_escape(\$filter_device['hostname'])")) { + $missing[] = "$f:hostname"; + } + } + expect($missing)->toBeEmpty('missing html_escape: ' . implode(', ', $missing)); + }); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 00000000..174d7fd7 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,3 @@ +&1', $output, $returnCode); // nosemgrep: php.lang.security.exec-use.exec-use -- phpBin is PHP_BINARY (constant); file is escapeshellarg'd glob result + if ($returnCode !== 0) { + $failures[] = basename($file) . ': ' . implode(' ', $output); + } + } + + expect($failures)->toBeEmpty('PHP syntax errors: ' . implode('; ', $failures)); + }); + + it('all lib PHP files parse without syntax errors', function () { + $root = realpath(__DIR__ . '/../../'); + $phpBin = PHP_BINARY; + $libFiles = glob($root . '/lib/*.php'); + $failures = []; + + foreach ($libFiles as $file) { + $output = []; + $returnCode = 0; + // bare escapeshellarg(): cacti_escapeshellarg() requires the full Cacti bootstrap (DB connection, config); $file is a glob-returned server-local path, not user input + exec("$phpBin -l " . escapeshellarg($file) . ' 2>&1', $output, $returnCode); // nosemgrep: php.lang.security.exec-use.exec-use -- phpBin is PHP_BINARY (constant); file is escapeshellarg'd glob result + if ($returnCode !== 0) { + $failures[] = basename($file) . ': ' . implode(' ', $output); + } + } + + expect($failures)->toBeEmpty('PHP syntax errors in lib/: ' . implode('; ', $failures)); + }); +}); + +describe('plugin required hooks and functions', function () { + $setup = file_get_contents(realpath(__DIR__ . '/../../setup.php')); + + it('setup.php defines all required Cacti plugin hook functions', function () use ($setup) { + $required = [ + 'plugin_mactrack_install', + 'plugin_mactrack_uninstall', + 'plugin_mactrack_version', + 'plugin_mactrack_check_config', + 'plugin_mactrack_upgrade', + ]; + + $missing = []; + foreach ($required as $fn) { + if (!str_contains($setup, "function $fn")) { + $missing[] = $fn; + } + } + + expect($missing)->toBeEmpty('Missing functions: ' . implode(', ', $missing)); + }); +}); + +describe('datasource file discovery', function () { + it('lib directory contains mactrack_functions.php', function () { + expect(file_exists(realpath(__DIR__ . '/../../lib/mactrack_functions.php')))->toBeTrue(); + }); + + it('lib directory contains at least one mactrack library file', function () { + $lib = glob(realpath(__DIR__ . '/../../lib/') . '/mactrack_*.php'); + expect(count($lib))->toBeGreaterThan(0); + }); +}); diff --git a/tests/Unit/PassthruHardeningTest.php b/tests/Unit/PassthruHardeningTest.php new file mode 100644 index 00000000..4fc5e80d --- /dev/null +++ b/tests/Unit/PassthruHardeningTest.php @@ -0,0 +1,41 @@ +toContain('(int)$dbinfo[\'device_id\']'); + }); + + it('casts site_id to int before passthru in site scan', function () use ($funcs) { + expect($funcs)->toContain('(int)$dbinfo[\'site_id\']'); + }); + + it('annotates passthru calls with nosemgrep explaining the safety rationale', function () use ($funcs) { + $count = substr_count($funcs, 'nosemgrep: php.lang.security.exec-use.exec-use'); + expect($count)->toBe(2, 'both passthru calls should have nosemgrep annotations'); + }); + + it('does not use extra_args variable in passthru command (injection surface removed)', function () use ($funcs) { + // $extra_args was replaced with inline int-cast concatenations + $commandLines = []; + preg_match_all('/\$command\s*=.*passthru.*\n/s', $funcs, $commandLines); + // The commands should include (int) cast, not bare $extra_args + $passthruLines = []; + preg_match_all('/passthru\(\$command\);.*/', $funcs, $passthruLines); + expect(count($passthruLines[0]))->toBe(2); + }); + + it('site scan always passes --web flag (AJAX-only entry point by design)', function () use ($funcs) { + // mactrack_site_scan() is only reached via AJAX; --web is unconditional, unlike device rescan + expect($funcs)->toContain("' --web -sid=' . (int)\$dbinfo['site_id']"); + }); + + it('script paths are bare filesystem paths with no embedded arguments', function () use ($funcs) { + // cacti_escapeshellarg() quotes the entire value as one token; embedded flags would break the command + expect($funcs)->toContain("\$script_path = \$config['base_path'] . '/plugins/mactrack/mactrack_scanner.php'"); + expect($funcs)->toContain("\$script_path = \$config['base_path'] . '/plugins/mactrack/poller_mactrack.php'"); + }); +}); diff --git a/tests/Unit/UnserializeHardeningTest.php b/tests/Unit/UnserializeHardeningTest.php new file mode 100644 index 00000000..6808b10b --- /dev/null +++ b/tests/Unit/UnserializeHardeningTest.php @@ -0,0 +1,43 @@ +\s*false\]\s*\)/", + $macs, + $safeMatches + ); + $bare = preg_match_all( + "/unserialize\s*\(\s*get_nfilter_request_var\s*\([^)]+\)\s*\)/", + $macs, + $bareMatches + ); + expect($safe)->toBeGreaterThanOrEqual(1, 'at least one safe unserialize with allowed_classes:false'); + expect($bare)->toBe(0, 'no bare unserialize of user input without allowed_classes'); + }); + + it('does not have any bare unserialize of get_nfilter_request_var across the codebase', function () { + $phpFiles = glob(realpath(__DIR__ . '/../../') . '/*.php'); + $phpFiles = array_merge($phpFiles, glob(realpath(__DIR__ . '/../../lib/') . '/*.php')); + + $violations = []; + + foreach ($phpFiles as $file) { + $source = file_get_contents($file); + if (preg_match('/unserialize\s*\(\s*get_nfilter_request_var/', $source) && + !preg_match("/unserialize\s*\([^,]+,\s*\['allowed_classes'\s*=>\s*false\]/", $source)) { + $violations[] = basename($file); + } + } + + expect($violations)->toBeEmpty('these files have unsafe unserialize: ' . implode(', ', $violations)); + }); + + it('uses sanitize_unserialize_selected_items for integer id arrays', function () use ($macs) { + expect($macs)->toContain('sanitize_unserialize_selected_items'); + }); +}); diff --git a/tests/Unit/XssEscapingTest.php b/tests/Unit/XssEscapingTest.php new file mode 100644 index 00000000..1c7264a2 --- /dev/null +++ b/tests/Unit/XssEscapingTest.php @@ -0,0 +1,76 @@ +toContain("html_escape(\$site['site_name'])"); + expect($arp)->not->toContain("'>' . \$site['site_name'] . ''"); + }); + + it('escapes device_name and hostname in mactrack_view_arp.php select option', function () use ($arp) { + expect($arp)->toContain("html_escape(\$filter_device['device_name'])"); + expect($arp)->toContain("html_escape(\$filter_device['hostname'])"); + expect($arp)->not->toContain("'>' . \$filter_device['device_name']"); + }); + + it('escapes site_name in mactrack_view_macs.php select option', function () use ($macs) { + expect($macs)->toContain("html_escape(\$site['site_name'])"); + expect($macs)->not->toContain("'>' . \$site['site_name'] . ''"); + }); + + it('escapes device_name and hostname in mactrack_view_macs.php select option', function () use ($macs) { + expect($macs)->toContain("html_escape(\$filter_device['device_name'])"); + expect($macs)->toContain("html_escape(\$filter_device['hostname'])"); + }); + + it('escapes mac address list entry in mactrack_view_macs.php', function () use ($macs) { + expect($macs)->toContain('html_escape(mactrack_format_mac($mac))'); + expect($macs)->not->toMatch("/'\\.\\s*mactrack_format_mac\\(\\\\\\$mac\\)\\s*\\.'<\\/li>'/"); + }); + + it('escapes site_name in mactrack_view_ips.php', function () use ($ips) { + expect($ips)->toContain("html_escape(\$site['site_name'])"); + }); + + it('escapes site_name in mactrack_view_interfaces.php', function () use ($iface) { + expect($iface)->toContain("html_escape(\$site['site_name'])"); + }); + + it('escapes device_name in mactrack_view_interfaces.php', function () use ($iface) { + expect($iface)->toContain('html_escape($device_name)'); + }); + + it('escapes site_name in mactrack_view_dot1x.php', function () use ($dot1x) { + expect($dot1x)->toContain("html_escape(\$site['site_name'])"); + }); + + it('escapes device_name in mactrack_devices.php', function () use ($devs) { + expect($devs)->toContain("html_escape(\$device['device_name'])"); + }); + + it('escapes hostname in mactrack_devices.php', function () use ($devs) { + expect($devs)->toContain("html_escape(\$device['hostname'])"); + }); + + it('escapes site_name in mactrack_devices.php select option', function () use ($devs) { + expect($devs)->toContain("html_escape(\$site['site_name'])"); + }); + + it('escapes site_name in lib/mactrack_functions.php', function () use ($funcs) { + expect($funcs)->toContain("html_escape(\$site['site_name'])"); + }); + + it('escapes device_type description and sysDescr_match in lib/mactrack_functions.php', function () use ($funcs) { + expect($funcs)->toContain("html_escape(\$device_type['description'])"); + expect($funcs)->toContain("html_escape(\$device_type['sysDescr_match'])"); + }); +}); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..a075e1e8 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,5 @@ +