{"record":{"id":"717845d4cc4d11f8","repo":"arduino/Arduino","slug":"invalid-archive-it-must-contain-a-single-root-fol-717845","errorCode":null,"errorMessage":"Invalid archive: it must contain a single root folder while file {linkName} is outside {pathPrefix}","messagePattern":"Invalid archive: it must contain a single root folder while file (.+?) is outside (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"arduino-core/src/cc/arduino/utils/ArchiveExtractor.java","lineNumber":191,"sourceCode":"            stripPath--;\n          }\n          pathPrefix = name.substring(0, slash);\n        }\n\n        // Strip the common path prefix when requested\n        if (!name.startsWith(pathPrefix)) {\n          throw new IOException(\"Invalid archive: it must contain a single root folder while file \" + name + \" is outside \" + pathPrefix);\n        }\n        name = name.substring(pathPrefix.length());\n        if (name.isEmpty()) {\n          continue;\n        }\n        File outputFile = new File(destFolder, name);\n\n        File outputLinkedFile = null;\n        if (isLink) {\n          if (!linkName.startsWith(pathPrefix)) {\n            throw new IOException(\"Invalid archive: it must contain a single root folder while file \" + linkName + \" is outside \" + pathPrefix);\n          }\n          linkName = linkName.substring(pathPrefix.length());\n          outputLinkedFile = new File(destFolder, linkName);\n        }\n        if (isSymLink) {\n          // Symbolic links are referenced with relative paths\n          outputLinkedFile = new File(linkName);\n          if (outputLinkedFile.isAbsolute()) {\n            System.err.println(I18n.format(tr(\"Warning: file {0} links to an absolute path {1}\"), outputFile, outputLinkedFile));\n            System.err.println();\n          }\n        }\n\n        // Safety check\n        if (isDirectory) {\n          if (outputFile.isFile() && !overwrite) {\n            throw new IOException(\"Can't create folder \" + outputFile + \", a file with the same name exists!\");\n          }","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/arduino/Arduino/blob/a0df6e0e83b652c72bc78b0a1376c54d6ebc3bee/arduino-core/src/cc/arduino/utils/ArchiveExtractor.java#L173-L209","documentation":"For archive entries that are links (hard or symbolic), the link's target path (linkName) must also lie inside the stripped root prefix. If the link points outside the root folder — a classic path-traversal / symlink-attack shape — extract() throws this IOException naming the link target and the expected prefix.","triggerScenarios":"A tar entry is a link whose target path (linkName) does not start with the computed root pathPrefix, e.g. a symlink pointing to '../..' or to an absolute path outside the archive's root folder.","commonSituations":"Archives that bundle absolute or parent-relative symlinks (common when tarring from odd locations or packaging artifacts referencing the build machine); malicious archives attempting symlink escape; developer archives containing build symlinks to toolchains.","solutions":["Repackage the archive with only relative links that stay inside the root folder (or remove links entirely)","Inspect with tar -tvf to find suspicious absolute or '../' link targets and fix them before distributing","If you just need the files, extract with tar manually and resolve/remove the offending links"],"exampleFix":"// before (inside tar)\nln -s /etc/passwd lib/config -> link outside root\n// after\nln -s ../shared/config lib/config -> relative link within root folder","handlingStrategy":"validation","validationCode":"// reject archives whose link targets are absolute or escape the root\ntry (TarArchiveInputStream in = new TarArchiveInputStream(new FileInputStream(archiveFile))) {\n  TarArchiveEntry e;\n  while ((e = in.getNextTarEntry()) != null) {\n    if (e.isLink() && (e.getLinkName().startsWith(\"/\") || e.getLinkName().contains(\"..\"))) throw new SecurityException(\"Unsafe link target: \" + e.getLinkName());\n  }\n}","typeGuard":null,"tryCatchPattern":"try { extractor.extract(archive, dest, 1); } catch (IOException e) { if (e.getMessage().contains(\"is outside\")) { rejectOrSanitizeArchive(archive); } else { throw e; } }","preventionTips":["Use only relative links contained within the archive root","Remove build symlinks before packaging","Scan link entries with `tar -tvf` for absolute or '../' targets"],"tags":["archive","symlink","security","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"a0df6e0e83b652c72bc78b0a1376c54d6ebc3bee","analyzedAt":"2026-09-06T10:13:38.901Z","contentChangedAt":"2026-09-06T10:13:38.901Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}