Steuerkram/tests/68_ProductionErrorHardeningTest.php

78 lines
3.1 KiB
PHP

<?php
declare(strict_types=1);
/**
* Test 68: Produktions-Härtung der PHP-Fehlerausgabe (Issue #142).
* Prüft, dass display_errors deaktiviert ist, die Ini im Image landet und
* public/index.php Fehler zentral abfängt statt Details an den Client zu senden.
*/
class ProductionErrorHardeningTest extends TestCase
{
protected string $testName = 'Test 68: display_errors aus & zentraler Error-Handler ohne Detail-Leak';
public function run(): void
{
$baseDir = dirname(__DIR__);
$iniPath = $baseDir . '/php/error-handling.ini';
$this->assert(is_file($iniPath), 'php/error-handling.ini existiert');
$ini = file_get_contents($iniPath) ?: '';
$this->assert(
(bool) preg_match('/^\s*display_errors\s*=\s*Off\s*$/mi', $ini),
'display_errors ist in error-handling.ini auf Off gesetzt'
);
$this->assert(
(bool) preg_match('/^\s*log_errors\s*=\s*On\s*$/mi', $ini),
'log_errors ist in error-handling.ini auf On gesetzt'
);
$dockerfile = file_get_contents($baseDir . '/Dockerfile') ?: '';
$this->assert(
str_contains($dockerfile, 'php/error-handling.ini') && str_contains($dockerfile, '/usr/local/etc/php/conf.d/'),
'Dockerfile kopiert error-handling.ini in den PHP conf.d-Ordner'
);
$index = file_get_contents($baseDir . '/public/index.php') ?: '';
$this->assert(
str_contains($index, 'set_exception_handler'),
'index.php registriert einen globalen Exception-Handler'
);
$this->assert(
str_contains($index, 'set_error_handler'),
'index.php registriert einen globalen Error-Handler'
);
$this->assert(
str_contains($index, 'register_shutdown_function'),
'index.php registriert einen Shutdown-Handler für Fatal Errors'
);
// Der Handler muss vor dem Autoload/Routing stehen, damit auch frühe Fehler abgefangen werden.
$handlerPos = strpos($index, 'set_exception_handler');
$autoloadPos = strpos($index, "require BASE_DIR . '/vendor/autoload.php'");
$this->assert(
$handlerPos !== false && $autoloadPos !== false && $handlerPos < $autoloadPos,
'Exception-Handler wird vor dem Autoload registriert'
);
// Sicherstellen, dass im an den Client gesendeten HTML keine Exception-Details landen.
$exceptionHandlerBlock = substr($index, $handlerPos, 400);
foreach (['getMessage()', 'getTraceAsString()', 'getFile()', '$e->'] as $forbidden) {
$this->assert(
!str_contains($this->extractEchoBlock($index), $forbidden),
"Client-Ausgabe enthält keinen Verweis auf {$forbidden}"
);
}
unset($exceptionHandlerBlock);
}
private function extractEchoBlock(string $index): string
{
$start = strpos($index, "echo '<!DOCTYPE html>");
$end = strpos($index, "};", $start !== false ? $start : 0);
if ($start === false || $end === false) {
return '';
}
return substr($index, $start, $end - $start);
}
}