{"record":{"id":"e61ed98c4b9e3e19","repo":"symfony/http-kernel","slug":"unable-to-process-an-ssi-tag-without-a-virtual-attribute","errorCode":null,"errorMessage":"Unable to process an SSI tag without a \"virtual\" attribute.","messagePattern":"Unable to process an SSI tag without a \"virtual\" attribute\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"HttpCache/Ssi.php","lineNumber":67,"sourceCode":"        if (!\\in_array($parts[0], $this->contentTypes, true)) {\n            return $response;\n        }\n\n        // we don't use a proper XML parser here as we can have SSI tags in a plain text response\n        $content = $response->getContent();\n        $boundary = self::generateBodyEvalBoundary();\n        $chunks = preg_split('#<!--\\#include\\s+(.*?)\\s*-->#', $content, -1, \\PREG_SPLIT_DELIM_CAPTURE);\n\n        $i = 1;\n        while (isset($chunks[$i])) {\n            $options = [];\n            preg_match_all('/(virtual)=\"([^\"]*?)\"/', $chunks[$i], $matches, \\PREG_SET_ORDER);\n            foreach ($matches as $set) {\n                $options[$set[1]] = $set[2];\n            }\n\n            if (!isset($options['virtual'])) {\n                throw new \\RuntimeException('Unable to process an SSI tag without a \"virtual\" attribute.');\n            }\n\n            $chunks[$i] = $boundary.$options['virtual'].\"\\n\\n\\n\";\n            $i += 2;\n        }\n        $content = $boundary.implode('', $chunks).$boundary;\n\n        $response->setContent($content);\n        $response->headers->set('X-Body-Eval', 'SSI');\n\n        // remove SSI/1.0 from the Surrogate-Control header\n        $this->removeFromControl($response);\n\n        return $response;\n    }\n}\n","sourceCodeStart":49,"sourceCodeEnd":84,"githubUrl":"https://github.com/symfony/http-kernel/blob/aa3a39d7286a62cdfea98f0e69c651a3da6e36cf/HttpCache/Ssi.php#L49-L84","documentation":"During SSI response processing (only for configured content types), the body is split on <!--#include ... --> comments and attributes parsed by regex instead of a real XML parser. When a matched include directive lacks the \"virtual\" attribute, no include target can be determined, so this validation guard fires. It indicates malformed SSI markup in the response — an SSI include directive written without virtual, or with unrecognized attribute syntax.","triggerScenarios":"HTML containing <!--#include ...--> without virtual=, processed on a text/html response with SSI enabled.","commonSituations":"Migrating ESI tags to SSI (src vs virtual mismatch), or hand-written SSI comments missing the attribute.","solutions":["Add virtual=\"...\" to the SSI include tag.","Verify the tag uses SSI syntax (virtual) not ESI syntax (src).","Remove the tag if the include is not needed."],"exampleFix":"// before\n<!--#include set=\"box\" -->\n// after\n<!--#include virtual=\"/_fragment/news\" -->","handlingStrategy":"validation","validationCode":"if (preg_match('/<!--#include((?![^>]*virtual=)[^>]*)-->/', $html)) { // fix SSI tags missing virtual }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember SSI uses virtual, ESI uses src","Lint templates when migrating between ESI and SSI","Test rendered pages with SSI enabled"],"tags":["php","symfony","ssi","http-cache","html"],"backgroundTag":"missing-required-argument","analyzedSha":"aa3a39d7286a62cdfea98f0e69c651a3da6e36cf","analyzedAt":"2026-09-13T18:03:36.509Z","contentChangedAt":"2026-09-13T18:03:36.509Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}