{"record":{"id":"17cd140aee13db88","repo":"symfony/http-kernel","slug":"a-restored-response-must-have-the-x-content-digest-header","errorCode":null,"errorMessage":"A restored response must have the X-Content-Digest header.","messagePattern":"A restored response must have the X-Content-Digest header\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"HttpCache/Store.php","lineNumber":186,"sourceCode":"    }\n\n    /**\n     * Writes a cache entry to the store for the given Request and Response.\n     *\n     * Existing entries are read and any that match the response are removed. This\n     * method calls write with the new list of cache entries.\n     *\n     * @throws \\RuntimeException\n     */\n    public function write(Request $request, Response $response): string\n    {\n        $key = $this->getCacheKey($request);\n        $storedEnv = $this->persistRequest($request);\n\n        if ($response->headers->has('X-Body-File')) {\n            // Assume the response came from disk, but at least perform some safeguard checks\n            if (!$response->headers->has('X-Content-Digest')) {\n                throw new \\RuntimeException('A restored response must have the X-Content-Digest header.');\n            }\n\n            $digest = $response->headers->get('X-Content-Digest');\n            if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) {\n                throw new \\RuntimeException('X-Body-File and X-Content-Digest do not match.');\n            }\n        // Everything seems ok, omit writing content to disk\n        } else {\n            // Responses that cannot provide their content, like BinaryFileResponse or\n            // StreamedResponse, have no entity to store, so no entry is written\n            if (false === $content = $response->getContent()) {\n                return $key;\n            }\n\n            $digest = $this->generateContentDigest($response);\n            $response->headers->set('X-Content-Digest', $digest);\n\n            if (!$this->save($digest, $content, false)) {","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/symfony/http-kernel/blob/aa3a39d7286a62cdfea98f0e69c651a3da6e36cf/HttpCache/Store.php#L168-L204","documentation":"HttpCache Store::write persists a response; if the response already carries an X-Body-File header (meaning its body was previously restored from disk), it must also carry an X-Content-Digest header identifying the stored body. This RuntimeException is a safeguard against a corrupted or hand-crafted restored response being re-stored without its content digest, which would break cache consistency between the metadata file and the body file on disk.","triggerScenarios":"Calling HttpCache's Store::write() (via the HttpCache kernel) with a Response whose headers contain X-Body-File but not X-Content-Digest — e.g. custom code that manually sets X-Body-File to trick the store into skipping a body rewrite, or a subclass of Store that builds such responses.","commonSituations":"Custom HttpCache Store subclasses that copy/clone cached responses and strip internal headers; debugging code that mutates internal X-* headers; restoring a response manually from cache files with an incomplete header set.","solutions":["Ensure any response carrying X-Body-File also carries the matching X-Content-Digest header before calling write()","Never manually set or strip X-Body-File / X-Content-Digest headers; let Store::restore() create them","If building a fresh Response to cache, remove the X-Body-File header so the store writes the body normally"],"exampleFix":"// before\n$response->headers->set('X-Body-File', $path);\n$store->write($request, $response);\n// after\n$response->headers->remove('X-Body-File'); // let the store recompute digest + body file\n$store->write($request, $response);","handlingStrategy":"validation","validationCode":"if ($response->headers->has('X-Body-File') && !$response->headers->has('X-Content-Digest')) {\n    throw new \\RuntimeException('Refusing to store: X-Body-File without X-Content-Digest');\n}\n$store->write($request, $response);","typeGuard":null,"tryCatchPattern":"try {\n    $store->write($request, $response);\n} catch (\\RuntimeException $e) {\n    $response->headers->remove('X-Body-File');\n    $response->headers->remove('X-Content-Digest');\n    $store->write($request, $response);\n}","preventionTips":["Never manually set X-Body-File or X-Content-Digest headers","Let Store::restore() create internal headers","Subclass Store rather than editing cached Response objects directly"],"tags":["http-cache","cache-consistency","symfony"],"backgroundTag":"internal-invariant-violation","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"}