{"record":{"id":"79fe58a7032bbd85","repo":"arduino/Arduino","slug":"invalid-archive-it-must-contain-a-single-root-fol-79fe58","errorCode":null,"errorMessage":"Invalid archive: it must contain a single root folder while file {name} 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":180,"sourceCode":"\n        // On the first archive entry, if requested, detect the common path\n        // prefix to be stripped from filenames\n        if (stripPath > 0 && pathPrefix.isEmpty()) {\n          int slash = 0;\n          while (stripPath > 0) {\n            slash = name.indexOf(\"/\", slash);\n            if (slash == -1) {\n              throw new IOException(\"Invalid archive: it must contain a single root folder\");\n            }\n            slash++;\n            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);","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/arduino/Arduino/blob/a0df6e0e83b652c72bc78b0a1376c54d6ebc3bee/arduino-core/src/cc/arduino/utils/ArchiveExtractor.java#L162-L198","documentation":"After the expected root prefix is computed, every subsequent archive entry must start with that same prefix. If an entry's name lies outside the root folder (different top-level directory or a file at archive root), extract() throws this IOException naming the offending file and the expected prefix. This enforces the 'single root folder' convention used when installing libraries.","triggerScenarios":"Archive contains entries from more than one top-level directory (e.g. both MyLib/ and Other/), or contains root-level files alongside the root folder, with stripPath=1 requested.","commonSituations":"Hand-assembled archives combining library files and extra files (README at root, license in another folder); archives built on different OSes with inconsistent leading './' entries; corrupted or maliciously crafted archives.","solutions":["Repackage so every entry lives under the same single root folder","Extract with a general-purpose tool (tar/7zip) and install the folder manually into libraries/","Check entries for inconsistent prefixes (leading './', absolute paths) and normalize them before archiving"],"exampleFix":"// bad archive: README at root plus MyLib/ folder\n// fix: move README inside the root folder\n// tar contents before: README, MyLib/src/MyLib.cpp\n// tar contents after: MyLib/README, MyLib/src/MyLib.cpp","handlingStrategy":"validation","validationCode":"// ensure all entries share one root folder\ntry (TarArchiveInputStream in = new TarArchiveInputStream(new FileInputStream(archiveFile))) {\n  TarArchiveEntry e;\n  while ((e = in.getNextTarEntry()) != null) {\n    if (e.getName().startsWith(\"../\") || new File(e.getName()).isAbsolute()) throw new IllegalArgumentException(\"Unsafe entry: \" + e.getName());\n  }\n}","typeGuard":null,"tryCatchPattern":"try { extractor.extract(archive, dest, 1); } catch (IOException e) { if (e.getMessage().contains(\"is outside\")) { repackArchiveWithSingleRoot(archive); } else { throw e; } }","preventionTips":["Keep every file inside one root folder when archiving","Normalize './' prefixes and avoid absolute paths in tar entries","Validate archives with `tar -tf` before distributing"],"tags":["archive","structure","security","arduino"],"backgroundTag":"unexpected-api-response-shape","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"}