{"record":{"id":"dbacf640507fc375","repo":"SubtitleEdit/subtitleedit","slug":"archive-entry-is-rooted-outside-the-extraction-fol","errorCode":null,"errorMessage":"Archive entry is rooted outside the extraction folder: {reader.Entry.Key}","messagePattern":"Archive entry is rooted outside the extraction folder: (.+?)","errorType":"validation","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"src/ui/Logic/SevenZipExtractor/Unpacker.cs","lineNumber":410,"sourceCode":"            {\n                var entryFullName = reader.Entry.Key.Replace('\\\\', '/');\n                var normalizedSkipFolder = skipFolderLevel?.Replace('\\\\', '/').Trim('/') ?? string.Empty;\n                if (!string.IsNullOrEmpty(normalizedSkipFolder))\n                {\n                    if (entryFullName.Equals(normalizedSkipFolder, StringComparison.Ordinal))\n                    {\n                        entryFullName = string.Empty;\n                    }\n                    else if (entryFullName.StartsWith(normalizedSkipFolder + \"/\", StringComparison.Ordinal))\n                    {\n                        entryFullName = entryFullName[(normalizedSkipFolder.Length + 1)..];\n                    }\n                }\n\n                entryFullName = entryFullName.Replace('/', Path.DirectorySeparatorChar);\n                if (Path.IsPathRooted(entryFullName))\n                {\n                    throw new InvalidDataException($\"Archive entry is rooted outside the extraction folder: {reader.Entry.Key}\");\n                }\n\n                entryFullName = entryFullName.TrimStart(Path.DirectorySeparatorChar);\n                if (string.IsNullOrEmpty(entryFullName))\n                {\n                    if (reader.Entry.IsDirectory)\n                    {\n                        Directory.CreateDirectory(dir);\n                        continue;\n                    }\n\n                    throw new InvalidDataException(\"Archive contains an empty file entry name.\");\n                }\n\n                var fullFileName = Path.GetFullPath(Path.Combine(targetRoot, entryFullName));\n                var relativePath = Path.GetRelativePath(targetRoot, fullFileName);\n                if (relativePath.Equals(\"..\", StringComparison.Ordinal) ||\n                    relativePath.StartsWith(\"..\" + Path.DirectorySeparatorChar, StringComparison.Ordinal) ||","sourceCodeStart":392,"sourceCodeEnd":428,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Logic/SevenZipExtractor/Unpacker.cs#L392-L428","documentation":"InvalidDataException raised by a Zip-Slip / path-traversal defense: after normalizing separators, the archive entry path is Path.IsPathRooted (an absolute path like '/etc/...' or 'C:\\...'). The extractor refuses to write outside the target directory.","triggerScenarios":"An archive entry whose Key, after the skip-folder prefix is stripped and '/' is replaced with the OS separator, resolves to an absolute path. Typical of a maliciously crafted or malformed archive.","commonSituations":"Third-party or untrusted plugin/update archives, or archives produced on another OS that embed drive letters or leading slashes.","solutions":["Treat the archive as untrusted and reject it; do not bypass this guard.","If you control the archive, rebuild it so all entries use relative paths.","Verify the publisher/hash of the archive before extraction."],"exampleFix":"// before\nUnpacker.ExtractArchive(archivePath, dest);\n\n// after\nif (!IsTrustedPublisher(archivePath)) { logger.LogWarning(\"Rejecting archive with rooted entries\"); return; }\ntry { Unpacker.ExtractArchive(archivePath, dest); }\ncatch (InvalidDataException ex) when (ex.Message.Contains(\"rooted outside\")) { Quarantine(archivePath); }","handlingStrategy":"validation","validationCode":"foreach (var entry in ListEntries(archive))\n{\n    var name = entry.Key.Replace('/', Path.DirectorySeparatorChar);\n    if (Path.IsPathRooted(name)) { Quarantine(archive); throw new InvalidDataException($\"Refusing rooted entry {entry.Key}\"); }\n}","typeGuard":null,"tryCatchPattern":"try { Unpacker.ExtractArchive(archive, dest); }\ncatch (InvalidDataException ex) when (ex.Message.Contains(\"rooted outside\"))\n{\n    Quarantine(archive);\n    logger.LogWarning(\"Rejected rooted-entry archive {Archive}\", archive);\n}","preventionTips":["Only extract archives from trusted, verified publishers.","Do not bypass the path-traversal guard.","Scan entries for absolute paths before extraction.","Verify the archive hash against the publisher."],"tags":["security","zip-slip","path-traversal","archive","validation"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}