jdx/mise · error

brew-cask: invalid app source

Error message

brew-cask: invalid app source '{}'

What it means

AppArtifact source validation rejects sources that contain NUL bytes or backslashes, end with '/', or cannot be expressed as a relative artifact path (relative_artifact_path returns None). This runs before taking the basename so that traversal tricks (embedded separators, directory escapes) cannot hide inside a nested archive source.

Solutions

  1. Fix the cask's source to be a clean relative path inside the archive (no backslashes, NULs, or trailing slash).
  2. Replace backslash separators with '/' if the definition was written on Windows.
  3. Point the source at the actual file/bundle rather than a directory.
  4. Verify the cask against its upstream tap if you did not author it.

Example fix

// before
"source": ".\\Applications\\MyApp.app/"
// after
"source": "MyApp.app"
Defensive patterns

Strategy: validation

When it happens

Trigger: Resolving an app artifact whose `source` string contains '\0' or '\\', ends with '/', or is absolute/outside the staged archive (relative_artifact_path(Path::new(""), source) fails).

Common situations: Hand-edited cask JSON with Windows-style separators; a source accidentally written as a directory ('foo/') instead of a file; malicious or corrupted cask definitions attempting '../' traversal; conversion scripts emitting absolute paths.

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 jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/514111c078d74060. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/mod.rs:899

        remove_stale_versions(&caskroom_token, &cask.version)?;
        remove_cask_journals(&cask.token)?;
        file::remove_all(stage)?;
        Ok(cask.version)
    }
}

impl AppArtifact {
    fn target_name(&self) -> Result<&str> {
        if let Some(target) = &self.target {
            return Ok(target);
        }
        // A nested archive source still installs as its bundle basename. Check
        // the source before taking that basename so traversal cannot be hidden.
        if self.source.contains(['\0', '\\'])
            || self.source.ends_with('/')
            || relative_artifact_path(Path::new(""), Path::new(&self.source)).is_none()
        {
            bail!("brew-cask: invalid app source '{}'", self.source);
        }
        file_name_str(Path::new(&self.source), "app source")
    }
}

impl BinaryArtifact {
    fn target_name(&self) -> Result<String> {
        match &self.target {
            Some(target) => Ok(target.clone()),
            None => Ok(file_name_str(Path::new(&self.source), "binary source")?.to_string()),
        }
    }

    fn target_path(&self, appdir: &Path) -> Result<PathBuf> {
        binary_target_path(&self.target_name()?, appdir)
    }
}

View on GitHub (pinned to 533346cc37)