{"record":{"id":"5ae9f0e60e5e1d97","repo":"octobercms/october","slug":"file-not-found-5ae9f0","errorCode":null,"errorMessage":"File not found","messagePattern":"File not found","errorType":"exception","errorClass":"ApplicationException","httpStatus":null,"severity":"error","filePath":"modules/cms/models/ThemeExport.php","lineNumber":152,"sourceCode":"            }\n\n            if (strlen($zipPath) && File::isFile($zipPath)) {\n                File::delete($zipPath);\n            }\n\n            throw $ex;\n        }\n\n        return $zipName;\n    }\n\n    /**\n     * download\n     */\n    public static function download($name, $outputName = null)\n    {\n        if (!preg_match('/^oc[0-9a-z]*$/i', $name)) {\n            throw new ApplicationException('File not found');\n        }\n\n        $zipPath = temp_path() . '/' . $name;\n        if (!file_exists($zipPath)) {\n            throw new ApplicationException('File not found');\n        }\n\n        $headers = Response::download($zipPath, $outputName)->headers->all();\n        $result = Response::make(File::get($zipPath), 200, $headers);\n\n        @File::delete($zipPath);\n\n        return $result;\n    }\n}\n","sourceCodeStart":134,"sourceCodeEnd":168,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/cms/models/ThemeExport.php#L134-L168","documentation":"ThemeExport::download($name) only accepts internal tokens matching ^oc[0-9a-z]*$ - the uniqid('oc') names export() generates. A mismatch means the caller passed something other than a fresh export token (a display filename, an edited/truncated value, or an injected path). The regex doubles as a path-traversal guard: dots, slashes, and separators are all rejected.","triggerScenarios":"Calling download() with a value that fails the regex: ThemeExport::download('my-theme.zip') (dots), a user-modified ?token= URL containing ../ or slashes, or code that passes the $outputName into $name instead of the token returned by export().","commonSituations":"Controllers persisting or round-tripping download tokens through user-editable state; URL truncation/HTML-escaping mangling the token; attempts to reuse the pretty filename as the token.","solutions":["Pass the exact string returned by ThemeExport::export() as $name; use the second parameter $outputName for the user-facing filename.","If the token came from a request, verify it was not truncated or altered (URL-encoding, escaping); regenerate the export rather than massaging the token.","Treat regex failure as a bad request - do not normalize the input to fit the pattern."],"exampleFix":"// before: display filename passed as the token - fails ^oc[0-9a-z]*$\nThemeExport::download('my-theme-export.zip');\n\n// after: internal token for $name, pretty name for $outputName\n$token = ThemeExport::export($theme, $data);   // e.g. 'oc64f1a2b3c4d5'\nreturn ThemeExport::download($token, 'my-theme-export.zip');","handlingStrategy":"validation","validationCode":"if (!preg_match('/^oc[0-9a-z]*$/i', $name)) {\n    // reject with 400 before calling ThemeExport::download()\n    abort(400, 'Invalid export token');\n}","typeGuard":"function isValidThemeExportToken(string $name): bool\n{\n    return (bool) preg_match('/^oc[0-9a-z]*$/i', $name);\n}","tryCatchPattern":"try {\n    return ThemeExport::download($token, 'theme.zip');\n} catch (ApplicationException $e) {\n    abort(404); // bad or tampered token - do not echo the path\n}","preventionTips":["Forward the export() return value untouched to download(); keep user-facing names in $outputName.","Never round-trip tokens through user-editable fields or URLs that get rewritten.","Regenerate the export on failure instead of massaging the token to fit the regex."],"tags":["validation","path-traversal","theme-export","download","input-validation"],"backgroundTag":"path-traversal-guard-rejected","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}