{"record":{"id":"330a6b2e2eff3a2a","repo":"NationalSecurityAgency/ghidra","slug":"bad-filename-in-archive-filename","errorCode":null,"errorMessage":"Bad filename in archive: \\\"\" + filename + \"\\\"","messagePattern":"Bad filename in archive: \\\\\"\" \\+ filename \\+ \"\\\\\"","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/RestoreTask.java","lineNumber":146,"sourceCode":"\t\t\tthrows IOException, CancelledException {\n\t\tif (!shouldSkip(file)) {\n\t\t\tsuper.processFile(file, destFSFile, monitor);\n\t\t}\n\t}\n\n\t@Override\n\tprotected void processDirectory(GFile srcGFileDirectory, File destDirectory,\n\t\t\tTaskMonitor monitor) throws IOException, CancelledException {\n\t\tif (!shouldSkip(srcGFileDirectory)) {\n\t\t\tsuper.processDirectory(srcGFileDirectory, destDirectory, monitor);\n\t\t}\n\t}\n\n\t@Override\n\tprotected String mapSourceFilenameToDest(GFile srcFile) throws IOException {\n\t\tString filename = srcFile.getName();\n\t\tif (!FSUtilities.getSafeFilename(filename).equals(filename)) {\n\t\t\tthrow new IOException(\"Bad filename in archive: \\\"\" + filename + \"\\\"\");\n\t\t}\n\t\treturn filename;\n\t}\n\n\tprivate boolean shouldSkip(GFile file) {\n\t\tString path = file.getPath().toLowerCase();\n\t\tif (FILES_TO_SKIP.contains(path)) {\n\t\t\treturn true;\n\t\t}\n\t\tif (ArchivePlugin.OLD_FOLDER_PROPERTIES_FILE.equalsIgnoreCase(file.getName())) {\n\t\t\t// ignore this file in any directory in the archive\n\t\t\treturn true;\n\t\t}\n\t\tString ext = \".\" + FilenameUtils.getExtension(file.getName());\n\t\tif (GhidraURL.MARKER_FILE_EXTENSION.equalsIgnoreCase(ext)) {\n\t\t\t// ignore .gpr marker files, any file name\n\t\t\treturn true;\n\t\t}","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/RestoreTask.java#L128-L164","documentation":"Thrown by RestoreTask while extracting a Ghidra Project Archive (.gar, a zip). mapSourceFilenameToDest() runs each archive entry name through FSUtilities.getSafeFilename(); if sanitization changes the name, the entry is rejected as unsafe and an IOException is thrown. This is a security guard against path traversal / zip-slip and against filesystem-illegal characters. getSafeFilename replaces / \\ : | with '_', maps empty/\".\"/\"..\" to fixed names, and percent-encodes control chars, non-ASCII, and '%','?','|'.","triggerScenarios":"Restoring a .gar whose zip entries contain any of '/' '\\\\' ':' '|', the chars '%','?','|', control characters (codepoint <32), non-ASCII (>126), or whose name is empty, \".\", or \"..\". Triggered during the startExtract()/processFile() traversal when mapSourceFilenameToDest() is called for each entry.","commonSituations":"Archive produced or hand-edited on another OS, a corrupted/tampered .gar, an archive that survived a failed transfer or unzip/re-zip round trip, or a maliciously crafted archive attempting zip-slip path traversal (e.g. an entry named \"../../etc/x\"). Rare for archives exported by a matching Ghidra version.","solutions":["Re-export the project from the original Ghidra installation (Archive > Save Project to Archive) so all entry names are clean.","Inspect the archive contents: run `unzip -l file.gar` (or `jar tf`) and look for any entry containing slashes, colons, or other unsafe characters; the bad filename is shown verbatim in the error message.","If only a few benign entries are flagged (e.g. cross-platform name differences), rebuild the zip without those characters and re-add the .gar marker file that verifyArchive() requires.","If a freshly Ghidra-exported archive fails, report it as a bug with the offending entry name rather than disabling the check."],"exampleFix":"// The check is internal; fix the archive contents instead.\n// Before (archive entry with an unsafe name):\n//   project/prj:backup   <- ':' triggers the guard\n// After (rename the entry, then re-zip):\n//   project/prj_backup\n//\n// Programmatic pre-scan to find offending entries before restoring:\ntry (GFileSystem fs = fsService.openFileSystemContainer(fsrl, monitor)) {\n    for (GFile f : fs.lookup(\"/\").getListing()) {\n        String n = f.getName();\n        if (!FSUtilities.getSafeFilename(n).equals(n)) {\n            System.err.println(\"Unsafe entry: \" + n);\n        }\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Scan the archive's entry names BEFORE invoking RestoreTask,\n// flagging any name the sanitizer would change.\ntry (GFileSystem fs = fsService.openFileSystemContainer(archiveFSRL, monitor)) {\n    java.util.List<String> bad = new java.util.ArrayList<>();\n    fs.walkFileTree(\"/\", (f, __) -> {\n        String n = f.getName();\n        if (n != null && !ghidra.formats.gfilesystem.FSUtilities\n                .getSafeFilename(n).equals(n)) {\n            bad.add(n);\n        }\n    });\n    if (!bad.isEmpty()) {\n        // refuse to restore; report bad names to the user\n        throw new IOException(\"Refusing archive with unsafe names: \" + bad);\n    }\n}","typeGuard":null,"tryCatchPattern":"// RestoreTask.run() already catches Throwable and surfaces a dialog.\n// For programmatic restoration, wrap the extraction in try/catch and\n// report the offending filename from the IOException message.\ntry {\n    restoreTask.run(monitor);\n} catch (IOException e) {\n    if (e.getMessage().startsWith(\"Bad filename in archive:\")) {\n        // log/report e.getMessage(); do not retry the same archive unchanged\n    } else {\n        throw e;\n    }\n}","preventionTips":["Only restore .gar archives produced by Ghidra's own 'Save Project to Archive' on the same/compatible version.","Never hand-edit, unzip-and-rezip, or transfer archives through tools that mangle entry names.","Treat this error as a possible security issue: a legitimate Ghidra archive should never contain path-traversal or illegal-filename entries."],"tags":["ghidra","archive","zip-slip","path-traversal","security","restore","io"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}