beemdevelopment/Aegis · critical · IOException

Attempted to write outside of the parent directory

Error message

Attempted to write outside of the parent directory

What it means

After parsing the pack, importPack builds the destination directory from the pack's UUID/version and verifies its canonical path stays inside Aegis's private icons base directory. If the canonical path escapes the base dir, the code refuses to write and throws this IOException, defending against ZIP-slip style attacks where crafted metadata targets paths like ../../databases.

Solutions

  1. Do not import icon packs from untrusted sources — this error indicates the pack is malicious or malformed
  2. Inspect pack.json for traversal characters (../, absolute paths) in the uuid/version-derived fields
  3. Obtain a clean pack.json with a canonical UUID and repackage the ZIP
  4. Report the malicious pack to its distributor
Defensive patterns

Strategy: validation

Validate before calling

File packDir = new File(iconsBaseDir, uuid + "/" + version);
String canonical = packDir.getCanonicalPath();
if (!canonical.startsWith(iconsBaseDir.getCanonicalPath() + File.separator)) {
    throw new IconPackException("Unsafe pack path");
}

Type guard

boolean isInsideParent(File child, File parent) throws IOException {
    return child.getCanonicalPath().startsWith(parent.getCanonicalPath() + File.separator);
}

Try / catch

try {
    iconPackManager.importPack(file);
} catch (IconPackException e) {
    Log.e(TAG, "Icon pack rejected (possible path traversal)", e);
    UiHelper.showDialog(context, R.string.malicious_icon_pack);
}

Prevention

When it happens

Trigger: Importing a crafted icon pack whose UUID/version (used to build the destination path) resolve to a canonical path outside the icons base directory — e.g. an identifier containing ../ traversal segments.

Common situations: Deliberately malicious icon packs designed to overwrite the Aegis vault or other app files (covered by testMaliciousIconPackCannotOverwriteVaultFile); extremely unlikely from legitimate packs, but possible if pack.json fields were tampered with.

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


AI-assisted analysis of beemdevelopment/Aegis@d6f4e5925a (2026-09-08). Data as JSON: /api/errors/e07e844a3a7eebf0. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/com/beemdevelopment/aegis/icons/IconPackManager.java:82

    public IconPack importPack(File inFile) throws IconPackException {
        try {
            // read and parse the icon pack definition file of the icon pack
            ZipFile zipFile = new ZipFile(inFile);
            FileHeader packHeader = zipFile.getFileHeader(_packDefFilename);
            if (packHeader == null) {
                throw new IOException("Unable to find pack.json in the root of the ZIP file");
            }
            IconPack pack;
            byte[] defBytes;
            try (ZipInputStream inStream = zipFile.getInputStream(packHeader)) {
                defBytes = IOUtils.readAll(inStream);
                pack = IconPack.fromBytes(defBytes);
            }

            // create a new directory to store the icon pack, based on the UUID and version
            File packDir = getIconPackDir(pack);
            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");
                }

View on GitHub (pinned to d6f4e5925a)