75 lines
2.8 KiB
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 '';
|
|
}
|
|
}
|