{"record":{"id":"146a190f2e589076","repo":"remotion-dev/remotion","slug":"invalid-public-file-path-path","errorCode":null,"errorMessage":"Invalid public file path: ${path}","messagePattern":"Invalid public file path: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/browser-studio/src/browser-studio-project-controller.ts","lineNumber":41,"sourceCode":"\tfileName: string;\n\tmutate: (project: VirtualProject) => VirtualProject;\n\tnodePathMutationFiles: ProjectNodePathMutationFiles | null;\n};\n\nconst MAX_HISTORY_ENTRIES = 100;\n\nconst normalizePublicFilePath = (path: string) => {\n\tconst withoutLeadingSlash = path.startsWith('/') ? path.slice(1) : path;\n\n\tif (\n\t\twithoutLeadingSlash.length === 0 ||\n\t\twithoutLeadingSlash.includes('\\\\') ||\n\t\twithoutLeadingSlash.includes('\\0') ||\n\t\twithoutLeadingSlash\n\t\t\t.split('/')\n\t\t\t.some((segment) => segment === '' || segment === '.' || segment === '..')\n\t) {\n\t\tthrow new Error(`Invalid public file path: ${path}`);\n\t}\n\n\treturn withoutLeadingSlash;\n};\n\nconst getCanonicalPublicFiles = (project: VirtualProject) => {\n\tconst canonicalFiles: Record<string, Uint8Array | string> = {};\n\n\tfor (const [path, contents] of Object.entries(project.publicFiles ?? {})) {\n\t\tconst canonicalPath = normalizePublicFilePath(path);\n\t\tif (Object.hasOwn(canonicalFiles, canonicalPath)) {\n\t\t\tthrow new Error(`Multiple public files resolve to ${canonicalPath}`);\n\t\t}\n\n\t\tcanonicalFiles[canonicalPath] = contents;\n\t}\n\n\treturn canonicalFiles;","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/browser-studio/src/browser-studio-project-controller.ts#L23-L59","documentation":"Browser Studio normalizes every public/static file path by stripping a single leading slash and then rejecting anything that is empty, contains a backslash, contains a NUL byte, or has any segment equal to '', '.' or '..'. This keeps public-file keys canonical and blocks path traversal / escapes. The throw happens during any operation that funnels a path through normalizePublicFilePath (canonicalization, add, rename, etc.).","triggerScenarios":"Calling a Browser Studio file operation with paths like '' (empty), '/' (becomes empty after strip), 'a//b' (empty segment), 'a/./b' or 'a/../b' (dot/dot-dot), 'a\\\\b' (Windows backslash), or a path containing a NUL character. Also when the UI forwards an untrimmed or programmatically-constructed string.","commonSituations":"Front-end passing a raw input without sanitizing; user typing a Windows-style path; code joining segments producing double slashes; accidental or malicious traversal attempts; trailing slashes producing an empty final segment.","solutions":["Sanitize the path client-side before calling the operation: strip a leading '/', replace backslashes with '/', and reject empty/dot/dot-dot segments.","Use forward-slash, single-segment-relative paths consistently (e.g. 'images/logo.png').","Add a UI validation layer that mirrors these rules and disables the submit button on invalid input.","If the path comes from user input, trim it and confirm it is non-empty before submission."],"exampleFix":"// before\nrenameStaticFile({oldRelativePath: '/assets/../etc/file', newRelativePath: 'out'});\n\n// after\nconst safe = (p: string) => p.replace(/^\\//, '').replace(/\\\\/g, '/');\nrenameStaticFile({oldRelativePath: safe('assets/file'), newRelativePath: 'out'});","handlingStrategy":"validation","validationCode":"const isValidPublicFilePath = (raw: string): boolean => {\n  const p = raw.startsWith('/') ? raw.slice(1) : raw;\n  if (p.length === 0 || p.includes('\\\\') || p.includes('\\0')) return false;\n  return p.split('/').every((seg) => seg !== '' && seg !== '.' && seg !== '..');\n};\n// guard every call site:\nif (!isValidPublicFilePath(userPath)) throw new Error(`Refusing invalid path: ${userPath}`);","typeGuard":"const isCanonicalPublicPath = (p: string): boolean => {\n  if (p.startsWith('/') || p.includes('\\\\') || p.includes('\\0')) return false;\n  return p.length > 0 && p.split('/').every((s) => s !== '' && s !== '.' && s !== '..');\n};","tryCatchPattern":null,"preventionTips":["Sanitize paths at the UI boundary before they reach Browser Studio operations.","Normalize once and store the canonical form; never re-introduce leading slashes or backslashes.","Treat any path containing '..', a NUL byte, or a backslash as invalid by default.","Add client-side validation mirroring normalizePublicFilePath so users get early feedback."],"tags":["validation","paths","public-files","browser-studio","security","path-traversal"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}