* 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 `