Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions crates/mise-shim/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::env;
use std::path::Path;
use std::process::{Command, exit};
use std::process::{Command, ExitCode};

const MISE_SHIM_PATH_ENV: &str = "__MISE_SHIM_PATH";

Expand All @@ -24,29 +24,32 @@ fn paths_eq(a: &Path, b: &Path) -> bool {
}
}

fn main() {
let exe = env::current_exe().unwrap_or_else(|e| {
eprintln!("mise-shim: failed to determine executable path: {e}");
exit(1);
});
fn main() -> ExitCode {
match run() {
Ok(code) => ExitCode::from(code as u8),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Shim exit status is truncated

When a Windows tool launched through an executable mise shim returns a status outside 0..=255, casting the status to u8 changes it before forwarding it. A status of 256 therefore becomes 0, causing the failed shimmed command to be reported as successful.

Knowledge Base Used: Shims

Fix in Claude Code

Err(err) => {
eprintln!("{err}");
ExitCode::FAILURE
}
}
}

fn run() -> Result<i32, String> {
let exe = env::current_exe()
.map_err(|err| format!("mise-shim: failed to determine executable path: {err}"))?;
let tool = exe
.file_stem()
.unwrap_or_else(|| {
eprintln!("mise-shim: failed to determine tool name from executable path");
exit(1);
})
.ok_or_else(|| "mise-shim: failed to determine tool name from executable path".to_string())?
.to_os_string();

if env::var_os(MISE_SHIM_PATH_ENV)
.as_deref()
.is_some_and(|previous| paths_eq(Path::new(previous), &exe))
{
eprintln!(
return Err(format!(
"mise-shim: recursive shim invocation detected for {}: {}",
tool.to_string_lossy(),
exe.display()
);
exit(1);
));
}

let args = env::args_os().skip(1);
Expand All @@ -60,12 +63,11 @@ fn main() {
.status();

match status {
Ok(s) => exit(s.code().unwrap_or(1)),
Err(e) => {
eprintln!("mise-shim: failed to execute mise: {e}");
eprintln!("Ensure `mise` is installed and available on your PATH.");
eprintln!("See https://mise.en.dev for installation instructions.");
exit(1);
}
Ok(status) => Ok(status.code().unwrap_or(1)),
Err(err) => Err(format!(
"mise-shim: failed to execute mise: {err}\n\
Ensure `mise` is installed and available on your PATH.\n\
See https://mise.en.dev for installation instructions."
)),
}
}
6 changes: 3 additions & 3 deletions crates/vfox/src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ extern crate log;
#[cfg(feature = "cli")]
mod cli;

#[allow(clippy::disallowed_methods)]
#[cfg(feature = "cli")]
#[tokio::main]
async fn main() {
async fn main() -> std::process::ExitCode {
env_logger::init_from_env(env_logger::Env::default().filter_or("VFOX_LOG", "info"));
if let Err(err) = cli::run().await {
error!("{err}");
std::process::exit(1);
return std::process::ExitCode::FAILURE;
}
std::process::ExitCode::SUCCESS
}

#[cfg(not(feature = "cli"))]
Expand Down
12 changes: 6 additions & 6 deletions src/cli/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl Activate {
// This key is session-scoped and lost when the shell closes
if Settings::get().env_cache {
let key = CachedEnv::ensure_encryption_key();
prelude.push(ActivatePrelude::SetEnv(
prelude.push(ActivatePrelude::Set(
"__MISE_ENV_CACHE_KEY".to_string(),
key,
));
Expand All @@ -160,7 +160,7 @@ impl Activate {

fn prepend_path(&self, p: &Path) -> Option<ActivatePrelude> {
if is_dir_not_in_nix(p) && !is_dir_in_path(p) && !p.is_relative() {
Some(ActivatePrelude::PrependEnv(
Some(ActivatePrelude::Prepend(
PATH_KEY.to_string(),
p.to_string_lossy().to_string(),
))
Expand All @@ -172,18 +172,18 @@ impl Activate {
/// Used by activate_shims for the shims directory. Always prepends the path to
/// the front, even if already present (accepting a duplicate entry), so the
/// shims dir wins on re-source. For shells with native path dedup (fish), uses
/// MovePrependEnv to reorder without duplicating.
/// MovePrepend to reorder without duplicating.
fn shims_prepend_path(&self, shell: &dyn Shell, p: &Path) -> Option<ActivatePrelude> {
if !is_dir_not_in_nix(p) || p.is_relative() {
return None;
}
if shell.supports_move_path() {
Some(ActivatePrelude::MovePrependEnv(
Some(ActivatePrelude::MovePrepend(
PATH_KEY.to_string(),
p.to_string_lossy().to_string(),
))
} else {
Some(ActivatePrelude::PrependEnv(
Some(ActivatePrelude::Prepend(
PATH_KEY.to_string(),
p.to_string_lossy().to_string(),
))
Expand All @@ -209,7 +209,7 @@ fn remove_shims() -> std::io::Result<Option<ActivatePrelude>> {
// goes back into the user's live shell: a duplicate entry the user put there is
// theirs to keep, and only the shims dir may be dropped here.
let path = path_env.join_verbatim().to_string_lossy().to_string();
Ok(Some(ActivatePrelude::SetEnv(PATH_KEY.to_string(), path)))
Ok(Some(ActivatePrelude::Set(PATH_KEY.to_string(), path)))
} else {
Ok(None)
}
Expand Down
7 changes: 3 additions & 4 deletions src/cli/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl Completion {
match shell {
Shell::Bash => include_str!("../../completions/mise.bash"),
Shell::Fish => include_str!("../../completions/mise.fish"),
Shell::PowerShell => include_str!("../../completions/mise.ps1"),
Shell::Powershell => include_str!("../../completions/mise.ps1"),
Shell::Zsh => include_str!("../../completions/_mise"),
}
.to_string()
Expand All @@ -105,18 +105,17 @@ static AFTER_LONG_HELP: &str = color_print::cstr!(

#[derive(Debug, Clone, Copy, EnumString, strum::Display)]
#[strum(serialize_all = "snake_case")]
#[allow(clippy::enum_variant_names)] // PowerShell is a proper noun
enum Shell {
Bash,
Fish,
#[strum(serialize = "powershell")]
PowerShell,
Powershell,
Zsh,
}

impl ValueEnum for Shell {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Bash, Self::Fish, Self::PowerShell, Self::Zsh]
&[Self::Bash, Self::Fish, Self::Powershell, Self::Zsh]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(self.to_string()))
Expand Down
7 changes: 3 additions & 4 deletions src/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ pub fn kill_all() {
CmdLineRunner::kill_all();
}

/// Terminate only after command scopes and their destructors have unwound.
#[allow(clippy::disallowed_methods)]
pub fn terminate(code: i32) -> ! {
/// Convert a requested process status after command scopes have unwound.
pub fn status(code: i32) -> std::process::ExitCode {
debug!("exiting with code: {code}");
std::process::exit(code)
std::process::ExitCode::from(code as u8)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Exit status is truncated

When a Windows child returns a status outside 0..=255, casting the requested i32 status to u8 changes it before returning it to the OS, causing values such as 256 to be reported as success instead of preserving the child process failure.

Knowledge Base Used: CLI Dispatch Flow

Fix in Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows child exit codes truncated

Medium Severity

Replacing std::process::exit with ExitCode::from(code as u8) narrows propagated statuses to eight bits. On Windows, std::process::exit previously forwarded the full i32 from mise exec and shims; values above 255 (or wrapped negatives) can now report the wrong process status, including mapping some failures to 0.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 479c2ea. Configure here.

}

#[cfg(test)]
Expand Down
11 changes: 7 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use std::{
panic,
process::ExitCode,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
Expand Down Expand Up @@ -105,7 +106,7 @@ pub(crate) use crate::exit::request as request_exit;
pub(crate) use crate::result::Result;
use crate::ui::multi_progress_report::MultiProgressReport;

fn main() {
fn main() -> ExitCode {
let nprocs = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or_default();
Expand All @@ -126,7 +127,7 @@ fn main() {
Ok(runtime) => runtime,
Err(err) => {
eprintln!("Error: {err:?}");
exit::terminate(1);
return exit::status(1);
}
};
let result = runtime.block_on(main_());
Expand All @@ -150,8 +151,10 @@ fn main() {
} else {
drop(runtime);
}
if code != 0 {
exit::terminate(code);
if code == 0 {
ExitCode::SUCCESS
} else {
exit::status(code)
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/sandbox/seccomp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use seccompiler::{
};
use std::collections::BTreeMap;

fn syscall_number<T: Into<i64>>(number: T) -> i64 {
number.into()
}

/// Apply a seccomp-bpf filter that blocks network syscalls.
///
/// Blocks AF_INET and AF_INET6 sockets while allowing AF_UNIX (needed by many tools).
Expand Down Expand Up @@ -47,8 +51,10 @@ pub fn apply_seccomp_net_filter() -> Result<()> {

// Block socket() and socketpair() for inet families
// This is sufficient — if you can't create an inet socket, you can't do networking
#[allow(clippy::useless_conversion)]
for syscall in [libc::SYS_socket, libc::SYS_socketpair].map(i64::from) {
for syscall in [
syscall_number(libc::SYS_socket),
syscall_number(libc::SYS_socketpair),
] {
rules.insert(
syscall,
vec![socket_rule_inet.clone(), socket_rule_inet6.clone()],
Expand Down
17 changes: 8 additions & 9 deletions src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub trait Shell: Display {
}
/// Whether this shell natively deduplicates/reorders PATH entries.
/// When true, activate_shims skips the is_dir_in_path guard and uses
/// MovePrependEnv to ensure correct ordering on re-source.
/// MovePrepend to ensure correct ordering on re-source.
fn supports_move_path(&self) -> bool {
false
}
Expand All @@ -107,21 +107,20 @@ pub trait Shell: Display {
prelude
.iter()
.map(|p| match p {
ActivatePrelude::SetEnv(k, v) => self.set_env(k, v),
ActivatePrelude::PrependEnv(k, v) => self.prepend_env(k, v),
ActivatePrelude::MovePrependEnv(k, v) => self.move_prepend_env(k, v),
ActivatePrelude::Set(k, v) => self.set_env(k, v),
ActivatePrelude::Prepend(k, v) => self.prepend_env(k, v),
ActivatePrelude::MovePrepend(k, v) => self.move_prepend_env(k, v),
})
.join("")
}
}

#[allow(clippy::enum_variant_names)]
pub enum ActivatePrelude {
SetEnv(String, String),
PrependEnv(String, String),
/// Like PrependEnv but moves existing entries to the front (for fish --move).
Set(String, String),
Prepend(String, String),
/// Like Prepend but moves existing entries to the front (for fish --move).
/// Used only by activate_shims to reorder paths on re-source.
MovePrependEnv(String, String),
MovePrepend(String, String),
}

pub struct ActivateOptions {
Expand Down
4 changes: 2 additions & 2 deletions src/shell/nushell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ impl Nushell {
prelude
.iter()
.map(|p| match p {
ActivatePrelude::SetEnv(k, v) => format!("$env.{k} = r#'{v}'#\n"),
ActivatePrelude::PrependEnv(k, v) | ActivatePrelude::MovePrependEnv(k, v) => {
ActivatePrelude::Set(k, v) => format!("$env.{k} = r#'{v}'#\n"),
ActivatePrelude::Prepend(k, v) | ActivatePrelude::MovePrepend(k, v) => {
self.prepend_env(k, v)
}
})
Expand Down
Loading