NationalSecurityAgency/ghidra · error · IOException
Bad filename in archive: \"" + filename + "\"
Error message
Bad filename in archive: \"" + filename + "\"
What it means
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 '%','?','|'.
Source
Thrown at Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/archive/RestoreTask.java:146
throws IOException, CancelledException {
if (!shouldSkip(file)) {
super.processFile(file, destFSFile, monitor);
}
}
@Override
protected void processDirectory(GFile srcGFileDirectory, File destDirectory,
TaskMonitor monitor) throws IOException, CancelledException {
if (!shouldSkip(srcGFileDirectory)) {
super.processDirectory(srcGFileDirectory, destDirectory, monitor);
}
}
@Override
protected String mapSourceFilenameToDest(GFile srcFile) throws IOException {
String filename = srcFile.getName();
if (!FSUtilities.getSafeFilename(filename).equals(filename)) {
throw new IOException("Bad filename in archive: \"" + filename + "\"");
}
return filename;
}
private boolean shouldSkip(GFile file) {
String path = file.getPath().toLowerCase();
if (FILES_TO_SKIP.contains(path)) {
return true;
}
if (ArchivePlugin.OLD_FOLDER_PROPERTIES_FILE.equalsIgnoreCase(file.getName())) {
// ignore this file in any directory in the archive
return true;
}
String ext = "." + FilenameUtils.getExtension(file.getName());
if (GhidraURL.MARKER_FILE_EXTENSION.equalsIgnoreCase(ext)) {
// ignore .gpr marker files, any file name
return true;
}View on GitHub (pinned to d5f144c24d)
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.
Example fix
// The check is internal; fix the archive contents instead.
// Before (archive entry with an unsafe name):
// project/prj:backup <- ':' triggers the guard
// After (rename the entry, then re-zip):
// project/prj_backup
//
// Programmatic pre-scan to find offending entries before restoring:
try (GFileSystem fs = fsService.openFileSystemContainer(fsrl, monitor)) {
for (GFile f : fs.lookup("/").getListing()) {
String n = f.getName();
if (!FSUtilities.getSafeFilename(n).equals(n)) {
System.err.println("Unsafe entry: " + n);
}
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Scan the archive's entry names BEFORE invoking RestoreTask,
// flagging any name the sanitizer would change.
try (GFileSystem fs = fsService.openFileSystemContainer(archiveFSRL, monitor)) {
java.util.List<String> bad = new java.util.ArrayList<>();
fs.walkFileTree("/", (f, __) -> {
String n = f.getName();
if (n != null && !ghidra.formats.gfilesystem.FSUtilities
.getSafeFilename(n).equals(n)) {
bad.add(n);
}
});
if (!bad.isEmpty()) {
// refuse to restore; report bad names to the user
throw new IOException("Refusing archive with unsafe names: " + bad);
}
} Try / catch
// RestoreTask.run() already catches Throwable and surfaces a dialog.
// For programmatic restoration, wrap the extraction in try/catch and
// report the offending filename from the IOException message.
try {
restoreTask.run(monitor);
} catch (IOException e) {
if (e.getMessage().startsWith("Bad filename in archive:")) {
// log/report e.getMessage(); do not retry the same archive unchanged
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Couldn't create file " + projectFile.getAbsolutePath()
- Not a zip file: " + fs.getFSRL()
- Missing Ghidra Project Archive (.gar) marker file
- Could not recover cached scores: {e.getMessage()}
- Could not commit self-score: {ex.getMessage()}
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/330a6b2e2eff3a2a.
Report an issue: GitHub.