* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the LICENSE file in the project root for the full license text.
*/
declare(strict_types=1);
namespace App\Tests\Http;
use App\Http\View;
use App\Tests\TestCase;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
/**
* R01-N21: Twig auto-escape is the only barrier between user-supplied
* strings (sprint name, task title, audit JSON, …) and stored XSS in
* the views that render them. One careless `|raw` or `{% autoescape
* false %}` opens the door.
*
* Two complementary checks:
*
* - **Behaviour pin**: render a known XSS payload through a synthetic
* Twig template using the same `View` env the controllers use, and
* assert the output is HTML-escaped. This catches a future change
* to `View`'s `autoescape` config (e.g. someone flips it to `'name'`
* so a template-name-derived strategy kicks in).
*
* - **Static guard**: scan every `views` `.twig` template for the
* escape-bypass forms — `|raw`, `|safe`, `{% autoescape false %}`,
* and any `{% autoescape ... %}` override — and fail if any are
* present. Tests like this beat code review when the codebase
* grows.
*/
final class TwigAutoescapeTest extends TestCase
{
public function testHtmlAutoescapeEscapesXssPayloadEndToEnd(): void
{
$view = new View(__DIR__ . '/../../views');
$twig = $view->twig();
$payload = '';
$tpl = $twig->createTemplate('{{ x }}');
$out = $tpl->render(['x' => $payload]);
// The literal `