beemdevelopment/Aegis · critical · IOException
Attempted to write outside of the icon pack directory
Error message
Attempted to write outside of the icon pack directory
What it means
For each icon declared in pack.json, importPack computes the destination file inside the pack directory and asserts its canonical path stays within that directory. If an icon's relative filename escapes the pack dir (e.g. "../../vault.json"), the import aborts with this IOException. Like the parent-directory check, this blocks ZIP-slip attacks on individual icon entries.
Solutions
- Do not import icon packs from untrusted sources — this indicates a malicious or corrupted pack.json
- Inspect the "filename"/relative path fields in pack.json for ../ or absolute path segments
- Fix the icon filenames to plain relative names and repackage the ZIP
- Re-download the pack from a trusted source
Example fix
// before (pack.json)
{ "filename": "../../../databases/aegis.db" }
// after (pack.json)
{ "filename": "icon.png" } Defensive patterns
Strategy: validation
Validate before calling
File destFile = new File(packDir, icon.getRelativeFilename());
if (!destFile.getCanonicalPath().startsWith(packDir.getCanonicalPath() + File.separator)) {
throw new IconPackException("Unsafe icon path: " + icon.getRelativeFilename());
} Type guard
boolean isSafeRelativeName(String name) {
return name != null && !name.startsWith("/") && !name.contains("..") && !name.contains("\\");
} Try / catch
try {
iconPackManager.importPack(file);
} catch (IconPackException e) {
Log.e(TAG, "Icon pack rejected (path traversal in icon filename)", e);
} Prevention
- Reject icon filenames containing .., leading /, or backslashes before import
- Only install icon packs from vetted sources
- Canonicalize paths (not string compares on raw names) for containment checks
- Review pack.json contents when importing third-party packs
When it happens
Trigger: Importing an icon pack whose pack.json declares an icon with a relative filename containing ../ traversal or an absolute path, making destFile resolve outside the pack directory.
Common situations: Malicious icon packs attempting to overwrite arbitrary app files (covered by testMaliciousIconPackCannotOverwriteVaultFile); hand-edited pack.json with bad filename fields; Windows-authored archives with backslash paths that canonicalize unexpectedly.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Attempted to write outside of the parent directory
- Bad UUID format
- Unable to find pack.json in the root of the ZIP file
- Unable to find relative to the root of the ZIP file
- signatures cannot be null or empty!
AI-assisted analysis of beemdevelopment/Aegis@d6f4e5925a (2026-09-08).
Data as JSON: /api/errors/6bd6d19348ff49e7.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/main/java/com/beemdevelopment/aegis/icons/IconPackManager.java:99
if (!packDir.getCanonicalPath().startsWith(_iconsBaseDir.getCanonicalPath() + File.separator)) {
throw new IOException("Attempted to write outside of the parent directory");
}
if (packDir.exists()) {
throw new IconPackExistsException(pack);
}
IconPack existingPack = getIconPackByUUID(pack.getUUID());
if (existingPack != null) {
throw new IconPackExistsException(existingPack);
}
if (!packDir.exists() && !packDir.mkdirs()) {
throw new IOException(String.format("Unable to create directories: %s", packDir.toString()));
}
// extract each of the defined icons to the icon pack directory
for (IconPack.Icon icon : pack.getIcons()) {
File destFile = new File(packDir, icon.getRelativeFilename());
if (!destFile.getCanonicalPath().startsWith(packDir.getCanonicalPath() + File.separator)) {
throw new IOException("Attempted to write outside of the icon pack directory");
}
FileHeader iconHeader = zipFile.getFileHeader(icon.getRelativeFilename());
if (iconHeader == null) {
throw new IOException(String.format("Unable to find %s relative to the root of the ZIP file", icon.getRelativeFilename()));
}
// create new directories for this file if needed
File parent = destFile.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs()) {
throw new IOException(String.format("Unable to create directories: %s", packDir.toString()));
}
try (ZipInputStream inStream = zipFile.getInputStream(iconHeader);
FileOutputStream outStream = new FileOutputStream(destFile)) {
IOUtils.copy(inStream, outStream);
}
// after successful copy of the icon, store the new filenameView on GitHub (pinned to d6f4e5925a)