Steuerkram/tests/102_KiBadgeXssTest.php

75 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
/**
* Test 102: Kein Stored XSS über Kontobezeichnungen im KI-Vorschlag-Badge (Issue #213)
*
* Das Badge wird im Browser aus Kontobezeichnungen (option.text, bereits
* dekodiert) und KI-Daten gebaut. Der Test stellt sicher, dass diese Werte
* nicht mehr per innerHTML eingefügt werden. Das Verhalten im Browser prüft
* tests/e2e/specs/beleg-ki-badge.spec.js.
*/
class KiBadgeXssTest extends TestCase
{
protected string $testName = 'Test 102: KI-Badge ohne innerHTML für Serverdaten';
public function run(): void
{
$root = __DIR__ . '/..';
$js = (string)file_get_contents($root . '/public/static/js/beleg-ki.js');
$body = $this->functionBody($js, '_showKiBadgeDone');
$this->assertNotEmpty($body, '_showKiBadgeDone() in beleg-ki.js gefunden');
$this->assert(!str_contains($body, 'innerHTML'), '_showKiBadgeDone() setzt kein innerHTML');
$this->assert(!str_contains($body, 'onclick'), '_showKiBadgeDone() erzeugt keinen Inline-Handler');
$this->assert(str_contains($body, 'textContent'), '_showKiBadgeDone() fügt Werte per textContent ein');
$this->assert(
!preg_match('/querySelector\([^)]*\+\s*v\./', $js),
'KI-Werte werden nicht in CSS-Selektoren eingesetzt'
);
$app = (string)file_get_contents($root . '/public/static/js/app.js');
$this->assert(
!str_contains($app, '${item.bezeichnung} <small'),
'setupAccountSearch() fügt die Kontobezeichnung nicht per innerHTML ein'
);
foreach (['src/Views/dashboard.php', 'src/Views/belege/list.php'] as $view) {
$content = (string)file_get_contents($root . '/' . $view);
$this->assert(!str_contains($content, '}">${msg}</span>'), "$view: Statusmeldung nicht per innerHTML");
}
$settings = (string)file_get_contents($root . '/src/Views/settings/form.php');
$this->assert(
!str_contains($settings, "'<div>' + l + '</div>'"),
'settings/form.php: Ollama-Fehlerdetails nicht per innerHTML'
);
}
/** Liefert den Rumpf einer JS-Funktion (bis zur passenden schließenden Klammer). */
private function functionBody(string $js, string $name): string
{
$pos = strpos($js, 'function ' . $name . '(');
if ($pos === false) {
return '';
}
$start = strpos($js, '{', $pos);
if ($start === false) {
return '';
}
$depth = 0;
$len = strlen($js);
for ($i = $start; $i < $len; $i++) {
if ($js[$i] === '{') {
$depth++;
} elseif ($js[$i] === '}') {
$depth--;
if ($depth === 0) {
return substr($js, $start, $i - $start + 1);
}
}
}
return '';
}
}