diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 257bf817857842..5ea8b8ecf3866c 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -1,7 +1,16 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use crate::args::resolve_no_prompt; -use crate::util::fs::canonicalize_path; +use std::collections::HashSet; +use std::env; +use std::ffi::OsString; +use std::net::SocketAddr; +use std::num::NonZeroU32; +use std::num::NonZeroU8; +use std::num::NonZeroUsize; +use std::path::Path; +use std::path::PathBuf; +use std::str::FromStr; + use clap::builder::styling::AnsiColor; use clap::builder::FalseyValueParser; use clap::error::ErrorKind; @@ -23,22 +32,16 @@ use deno_core::normalize_path; use deno_core::resolve_url_or_path; use deno_core::url::Url; use deno_graph::GraphKind; +use deno_runtime::colors; use deno_runtime::deno_permissions::parse_sys_kind; use deno_runtime::deno_permissions::PermissionsOptions; use log::debug; use log::Level; use serde::Deserialize; use serde::Serialize; -use std::collections::HashSet; -use std::env; -use std::ffi::OsString; -use std::net::SocketAddr; -use std::num::NonZeroU32; -use std::num::NonZeroU8; -use std::num::NonZeroUsize; -use std::path::Path; -use std::path::PathBuf; -use std::str::FromStr; + +use crate::args::resolve_no_prompt; +use crate::util::fs::canonicalize_path; use super::flags_net; @@ -681,6 +684,54 @@ impl PermissionFlags { Ok(Some(new_paths)) } + fn resolve_allow_run( + allow_run: &[String], + ) -> Result, AnyError> { + let mut new_allow_run = Vec::with_capacity(allow_run.len()); + for command_name in allow_run { + if command_name.is_empty() { + bail!("Empty command name not allowed in --allow-run=...") + } + let command_path_result = which::which(command_name); + match command_path_result { + Ok(command_path) => new_allow_run.push(command_path), + Err(err) => { + log::info!( + "{} Failed to resolve '{}' for allow-run: {}", + colors::gray("Info"), + command_name, + err + ); + } + } + } + Ok(new_allow_run) + } + + let mut deny_write = + convert_option_str_to_path_buf(&self.deny_write, initial_cwd)?; + let allow_run = self + .allow_run + .as_ref() + .and_then(|raw_allow_run| match resolve_allow_run(raw_allow_run) { + Ok(resolved_allow_run) => { + if resolved_allow_run.is_empty() && !raw_allow_run.is_empty() { + None // convert to no permissions if now empty + } else { + Some(Ok(resolved_allow_run)) + } + } + Err(err) => Some(Err(err)), + }) + .transpose()?; + // add the allow_run list to deno_write + if let Some(allow_run_vec) = &allow_run { + if !allow_run_vec.is_empty() { + let deno_write = deny_write.get_or_insert_with(Vec::new); + deno_write.extend(allow_run_vec.iter().cloned()); + } + } + Ok(PermissionsOptions { allow_all: self.allow_all, allow_env: self.allow_env.clone(), @@ -694,7 +745,7 @@ impl PermissionFlags { initial_cwd, )?, deny_read: convert_option_str_to_path_buf(&self.deny_read, initial_cwd)?, - allow_run: self.allow_run.clone(), + allow_run, deny_run: self.deny_run.clone(), allow_sys: self.allow_sys.clone(), deny_sys: self.deny_sys.clone(), @@ -702,10 +753,7 @@ impl PermissionFlags { &self.allow_write, initial_cwd, )?, - deny_write: convert_option_str_to_path_buf( - &self.deny_write, - initial_cwd, - )?, + deny_write, prompt: !resolve_no_prompt(self), }) } diff --git a/cli/task_runner.rs b/cli/task_runner.rs index e8937590db8ace..ab7163bc93b720 100644 --- a/cli/task_runner.rs +++ b/cli/task_runner.rs @@ -213,8 +213,8 @@ impl ShellCommand for NodeGypCommand { ) -> LocalBoxFuture<'static, ExecuteResult> { // at the moment this shell command is just to give a warning if node-gyp is not found // in the future, we could try to run/install node-gyp for the user with deno - if which::which("node-gyp").is_err() { - log::warn!("{}: node-gyp was used in a script, but was not listed as a dependency. Either add it as a dependency or install it globally (e.g. `npm install -g node-gyp`)", crate::colors::yellow("warning")); + if context.state.resolve_command_path("node-gyp").is_err() { + log::warn!("{} node-gyp was used in a script, but was not listed as a dependency. Either add it as a dependency or install it globally (e.g. `npm install -g node-gyp`)", crate::colors::yellow("Warning")); } ExecutableCommand::new( "node-gyp".to_string(), diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index 11e4390513069b..eb53151ced2dee 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -21,6 +21,9 @@ use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; use std::process::ExitStatus; use std::rc::Rc; use tokio::process::Command; @@ -228,63 +231,15 @@ fn create_command( mut args: SpawnArgs, api_name: &str, ) -> Result { - fn get_requires_allow_all_env_var(args: &SpawnArgs) -> Option> { - fn requires_allow_all(key: &str) -> bool { - let key = key.trim(); - // we could be more targted here, but there are quite a lot of - // LD_* and DYLD_* env variables - key.starts_with("LD_") || key.starts_with("DYLD_") - } - - /// Checks if the user set this env var to an empty - /// string in order to clear it. - fn args_has_empty_env_value(args: &SpawnArgs, key_name: &str) -> bool { - args - .env - .iter() - .find(|(k, _)| k == key_name) - .map(|(_, v)| v.trim().is_empty()) - .unwrap_or(false) - } - - if let Some((key, _)) = args - .env - .iter() - .find(|(k, v)| requires_allow_all(k) && !v.trim().is_empty()) - { - return Some(key.into()); - } - - if !args.clear_env { - if let Some((key, _)) = std::env::vars().find(|(k, v)| { - requires_allow_all(k) - && !v.trim().is_empty() - && !args_has_empty_env_value(args, k) - }) { - return Some(key.into()); - } - } - - None - } - - { - let permissions = state.borrow_mut::(); - permissions.check_run(&args.cmd, api_name)?; - if permissions.check_run_all(api_name).is_err() { - // error the same on all platforms - if let Some(name) = get_requires_allow_all_env_var(&args) { - // we don't allow users to launch subprocesses with any LD_ or DYLD_* - // env vars set because this allows executing code (ex. LD_PRELOAD) - return Err(deno_core::error::custom_error( - "PermissionDenied", - format!("Requires --allow-all permissions to spawn subprocess with {} environment variable.", name) - )); - } - } - } - - let mut command = std::process::Command::new(args.cmd); + let (cmd, run_env) = compute_run_cmd_and_check_permissions( + &args.cmd, + args.cwd.as_deref(), + &args.env, + args.clear_env, + state, + api_name, + )?; + let mut command = std::process::Command::new(cmd); #[cfg(windows)] if args.windows_raw_arguments { @@ -298,14 +253,9 @@ fn create_command( #[cfg(not(windows))] command.args(args.args); - if let Some(cwd) = args.cwd { - command.current_dir(cwd); - } - - if args.clear_env { - command.env_clear(); - } - command.envs(args.env); + command.current_dir(run_env.cwd); + command.env_clear(); + command.envs(run_env.envs); #[cfg(unix)] if let Some(gid) = args.gid { @@ -554,6 +504,133 @@ fn close_raw_handle(handle: deno_io::RawBiPipeHandle) { } } +fn compute_run_cmd_and_check_permissions( + arg_cmd: &str, + arg_cwd: Option<&str>, + arg_envs: &[(String, String)], + arg_clear_env: bool, + state: &mut OpState, + api_name: &str, +) -> Result<(PathBuf, RunEnv), AnyError> { + let run_env = compute_run_env(arg_cwd, arg_envs, arg_clear_env) + .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; + let cmd = resolve_cmd(arg_cmd, &run_env) + .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; + check_run_permission(state, &cmd, &run_env, api_name)?; + Ok((cmd, run_env)) +} + +struct RunEnv { + envs: HashMap, + cwd: PathBuf, +} + +/// Computes the current environment, which will then be used to inform +/// permissions and finally spawning. This is very important to compute +/// ahead of time so that the environment used to verify permissions is +/// the same environment used to spawn the sub command. This protects against +/// someone doing timing attacks by changing the environment on a worker. +fn compute_run_env( + arg_cwd: Option<&str>, + arg_envs: &[(String, String)], + arg_clear_env: bool, +) -> Result { + #[allow(clippy::disallowed_methods)] + let cwd = std::env::current_dir().context("failed resolving cwd")?; + let cwd = arg_cwd + .map(|cwd_arg| resolve_path(cwd_arg, &cwd)) + .unwrap_or(cwd); + let envs = if arg_clear_env { + arg_envs.iter().cloned().collect() + } else { + let mut envs = std::env::vars().collect::>(); + for (key, value) in arg_envs { + envs.insert(key.clone(), value.clone()); + } + envs + }; + Ok(RunEnv { envs, cwd }) +} + +fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { + let is_path = cmd.contains('/'); + #[cfg(windows)] + let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute(); + if is_path { + Ok(resolve_path(cmd, &env.cwd)) + } else { + let path = env.envs.get("PATH").or_else(|| { + if cfg!(windows) { + env.envs.iter().find_map(|(k, v)| { + if k.to_uppercase() == "PATH" { + Some(v) + } else { + None + } + }) + } else { + None + } + }); + match which::which_in(cmd, path, &env.cwd) { + Ok(cmd) => Ok(cmd), + Err(which::Error::CannotFindBinaryPath) => { + Err(std::io::Error::from(std::io::ErrorKind::NotFound).into()) + } + Err(err) => Err(err.into()), + } + } +} + +fn resolve_path(path: &str, cwd: &Path) -> PathBuf { + deno_core::normalize_path(cwd.join(path)) +} + +fn check_run_permission( + state: &mut OpState, + cmd: &Path, + run_env: &RunEnv, + api_name: &str, +) -> Result<(), AnyError> { + let permissions = state.borrow_mut::(); + if !permissions.query_run_all(api_name) { + // error the same on all platforms + let env_var_names = get_requires_allow_all_env_vars(run_env); + if !env_var_names.is_empty() { + // we don't allow users to launch subprocesses with any LD_ or DYLD_* + // env vars set because this allows executing code (ex. LD_PRELOAD) + return Err(deno_core::error::custom_error( + "PermissionDenied", + format!( + "Requires --allow-all permissions to spawn subprocess with {} environment variable{}.", + env_var_names.join(", "), + if env_var_names.len() != 1 { "s" } else { "" } + ) + )); + } + permissions.check_run(cmd, api_name)?; + } + Ok(()) +} + +fn get_requires_allow_all_env_vars(env: &RunEnv) -> Vec<&str> { + fn requires_allow_all(key: &str) -> bool { + let key = key.trim(); + // we could be more targted here, but there are quite a lot of + // LD_* and DYLD_* env variables + key.starts_with("LD_") || key.starts_with("DYLD_") + } + + let mut found_envs = env + .envs + .iter() + .filter(|(k, v)| requires_allow_all(k) && !v.trim().is_empty()) + .map(|(k, _)| k.as_str()) + .collect::>(); + found_envs.sort(); + found_envs +} + #[op2] #[serde] fn op_spawn_child( @@ -634,6 +711,8 @@ fn op_spawn_kill( } mod deprecated { + use deno_core::anyhow; + use super::*; #[derive(Deserialize)] @@ -681,20 +760,24 @@ mod deprecated { #[serde] run_args: RunArgs, ) -> Result { let args = run_args.cmd; - state - .borrow_mut::() - .check_run(&args[0], "Deno.run()")?; - let env = run_args.env; - let cwd = run_args.cwd; - - let mut c = Command::new(args.first().unwrap()); - (1..args.len()).for_each(|i| { - let arg = args.get(i).unwrap(); + let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?; + let (cmd, run_env) = compute_run_cmd_and_check_permissions( + cmd, + run_args.cwd.as_deref(), + &run_args.env, + /* clear env */ false, + state, + "Deno.run()", + )?; + + let mut c = Command::new(cmd); + for arg in args.iter().skip(1) { c.arg(arg); - }); - cwd.map(|d| c.current_dir(d)); + } + c.current_dir(run_env.cwd); - for (key, value) in &env { + c.env_clear(); + for (key, value) in run_env.envs { c.env(key, value); } diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 7227bebf8c2349..2eacd8bcc41a5a 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -32,7 +32,6 @@ use std::path::PathBuf; use std::str::FromStr; use std::string::ToString; use std::sync::Arc; -use which::which; pub mod prompter; use prompter::permission_prompt; @@ -317,7 +316,7 @@ pub trait Descriptor: Eq + Clone + Hash { /// Parse this descriptor from a list of Self::Arg, which may have been converted from /// command-line strings. - fn parse(list: &Option>) -> Result, AnyError>; + fn parse(list: Option<&[Self::Arg]>) -> Result, AnyError>; /// Generic check function to check this descriptor against a `UnaryPermission`. fn check_in_permission( @@ -333,9 +332,6 @@ pub trait Descriptor: Eq + Clone + Hash { fn stronger_than(&self, other: &Self) -> bool { self == other } - fn aliases(&self) -> Vec { - vec![] - } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -423,43 +419,33 @@ impl UnaryPermission { desc: Option<&T>, allow_partial: AllowPartial, ) -> PermissionState { - let aliases = desc.map_or(vec![], T::aliases); - for desc in [desc] - .into_iter() - .chain(aliases.iter().map(Some).collect::>()) - { - let state = if self.is_flag_denied(desc) || self.is_prompt_denied(desc) { - PermissionState::Denied - } else if self.is_granted(desc) { - match allow_partial { - AllowPartial::TreatAsGranted => PermissionState::Granted, - AllowPartial::TreatAsDenied => { - if self.is_partial_flag_denied(desc) { - PermissionState::Denied - } else { - PermissionState::Granted - } + if self.is_flag_denied(desc) || self.is_prompt_denied(desc) { + PermissionState::Denied + } else if self.is_granted(desc) { + match allow_partial { + AllowPartial::TreatAsGranted => PermissionState::Granted, + AllowPartial::TreatAsDenied => { + if self.is_partial_flag_denied(desc) { + PermissionState::Denied + } else { + PermissionState::Granted } - AllowPartial::TreatAsPartialGranted => { - if self.is_partial_flag_denied(desc) { - PermissionState::GrantedPartial - } else { - PermissionState::Granted - } + } + AllowPartial::TreatAsPartialGranted => { + if self.is_partial_flag_denied(desc) { + PermissionState::GrantedPartial + } else { + PermissionState::Granted } } - } else if matches!(allow_partial, AllowPartial::TreatAsDenied) - && self.is_partial_flag_denied(desc) - { - PermissionState::Denied - } else { - PermissionState::Prompt - }; - if state != PermissionState::Prompt { - return state; } + } else if matches!(allow_partial, AllowPartial::TreatAsDenied) + && self.is_partial_flag_denied(desc) + { + PermissionState::Denied + } else { + PermissionState::Prompt } - PermissionState::Prompt } fn request_desc( @@ -512,9 +498,6 @@ impl UnaryPermission { match desc { Some(desc) => { self.granted_list.retain(|v| !v.stronger_than(desc)); - for alias in desc.aliases() { - self.granted_list.retain(|v| !v.stronger_than(&alias)); - } } None => { self.granted_global = false; @@ -582,11 +565,7 @@ impl UnaryPermission { ) { match desc { Some(desc) => { - let aliases = desc.aliases(); list.insert(desc); - for alias in aliases { - list.insert(alias); - } } None => *list_global = true, } @@ -612,7 +591,7 @@ impl UnaryPermission { ChildUnaryPermissionArg::GrantedList(granted_list) => { let granted: Vec = granted_list.into_iter().map(From::from).collect(); - perms.granted_list = T::parse(&Some(granted))?; + perms.granted_list = T::parse(Some(&granted))?; if !perms .granted_list .iter() @@ -649,7 +628,7 @@ impl Descriptor for ReadDescriptor { perm.check_desc(Some(self), true, api_name, || None) } - fn parse(args: &Option>) -> Result, AnyError> { + fn parse(args: Option<&[Self::Arg]>) -> Result, AnyError> { parse_path_list(args, ReadDescriptor) } @@ -681,7 +660,7 @@ impl Descriptor for WriteDescriptor { perm.check_desc(Some(self), true, api_name, || None) } - fn parse(args: &Option>) -> Result, AnyError> { + fn parse(args: Option<&[Self::Arg]>) -> Result, AnyError> { parse_path_list(args, WriteDescriptor) } @@ -754,7 +733,7 @@ impl Descriptor for NetDescriptor { perm.check_desc(Some(self), false, api_name, || None) } - fn parse(args: &Option>) -> Result, AnyError> { + fn parse(args: Option<&[Self::Arg]>) -> Result, AnyError> { parse_net_list(args) } @@ -864,7 +843,7 @@ impl Descriptor for EnvDescriptor { perm.check_desc(Some(self), false, api_name, || None) } - fn parse(list: &Option>) -> Result, AnyError> { + fn parse(list: Option<&[Self::Arg]>) -> Result, AnyError> { parse_env_list(list) } @@ -883,6 +862,11 @@ impl AsRef for EnvDescriptor { } } +pub enum RunDescriptorArg { + Name(String), + Path(PathBuf), +} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub enum RunDescriptor { /// Warning: You may want to construct with `RunDescriptor::from()` for case @@ -893,8 +877,26 @@ pub enum RunDescriptor { Path(PathBuf), } +impl From for RunDescriptorArg { + fn from(s: String) -> Self { + #[cfg(windows)] + let s = s.to_lowercase(); + let is_path = s.contains('/'); + #[cfg(windows)] + let is_path = is_path || s.contains('\\') || Path::new(&s).is_absolute(); + if is_path { + Self::Path(resolve_from_cwd(Path::new(&s)).unwrap()) + } else { + match which::which(&s) { + Ok(path) => Self::Path(path), + Err(_) => Self::Name(s), + } + } + } +} + impl Descriptor for RunDescriptor { - type Arg = String; + type Arg = RunDescriptorArg; fn check_in_permission( &self, @@ -905,7 +907,7 @@ impl Descriptor for RunDescriptor { perm.check_desc(Some(self), false, api_name, || None) } - fn parse(args: &Option>) -> Result, AnyError> { + fn parse(args: Option<&[Self::Arg]>) -> Result, AnyError> { parse_run_list(args) } @@ -916,16 +918,6 @@ impl Descriptor for RunDescriptor { fn name(&self) -> Cow { Cow::from(self.to_string()) } - - fn aliases(&self) -> Vec { - match self { - RunDescriptor::Name(name) => match which(name) { - Ok(path) => vec![RunDescriptor::Path(path)], - Err(_) => vec![], - }, - RunDescriptor::Path(_) => vec![], - } - } } impl From for RunDescriptor { @@ -938,7 +930,10 @@ impl From for RunDescriptor { if is_path { Self::Path(resolve_from_cwd(Path::new(&s)).unwrap()) } else { - Self::Name(s) + match which::which(&s) { + Ok(path) => Self::Path(path), + Err(_) => Self::Name(s), + } } } } @@ -947,11 +942,7 @@ impl From for RunDescriptor { fn from(p: PathBuf) -> Self { #[cfg(windows)] let p = PathBuf::from(p.to_string_lossy().to_string().to_lowercase()); - if p.is_absolute() { - Self::Path(p) - } else { - Self::Path(resolve_from_cwd(&p).unwrap()) - } + Self::Path(resolve_from_cwd(&p).unwrap()) } } @@ -988,7 +979,7 @@ impl Descriptor for SysDescriptor { perm.check_desc(Some(self), false, api_name, || None) } - fn parse(list: &Option>) -> Result, AnyError> { + fn parse(list: Option<&[Self::Arg]>) -> Result, AnyError> { parse_sys_list(list) } @@ -1025,7 +1016,7 @@ impl Descriptor for FfiDescriptor { perm.check_desc(Some(self), true, api_name, || None) } - fn parse(list: &Option>) -> Result, AnyError> { + fn parse(list: Option<&[Self::Arg]>) -> Result, AnyError> { parse_path_list(list, FfiDescriptor) } @@ -1330,15 +1321,16 @@ impl UnaryPermission { pub fn check( &mut self, - cmd: &str, + cmd: &Path, api_name: Option<&str>, ) -> Result<(), AnyError> { + debug_assert!(cmd.is_absolute()); skip_check_if_is_permission_fully_granted!(self); self.check_desc( - Some(&RunDescriptor::from(cmd.to_string())), + Some(&RunDescriptor::Path(cmd.to_path_buf())), false, api_name, - || Some(format!("\"{}\"", cmd)), + || Some(format!("\"{}\"", cmd.display())), ) } @@ -1346,6 +1338,21 @@ impl UnaryPermission { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, api_name, || None) } + + /// Queries without prompting + pub fn query_all(&mut self, api_name: Option<&str>) -> bool { + if self.is_allow_all() { + return true; + } + let (result, _prompted, _is_allow_all) = + self.query_desc(None, AllowPartial::TreatAsDenied).check2( + RunDescriptor::flag_name(), + api_name, + || None, + /* prompt */ false, + ); + result.is_ok() + } } impl UnaryPermission { @@ -1429,7 +1436,7 @@ pub struct PermissionsOptions { pub deny_ffi: Option>, pub allow_read: Option>, pub deny_read: Option>, - pub allow_run: Option>, + pub allow_run: Option>, pub deny_run: Option>, pub allow_sys: Option>, pub deny_sys: Option>, @@ -1440,8 +1447,8 @@ pub struct PermissionsOptions { impl Permissions { pub fn new_unary( - allow_list: &Option>, - deny_list: &Option>, + allow_list: Option<&[T::Arg]>, + deny_list: Option<&[T::Arg]>, prompt: bool, ) -> Result, AnyError> where @@ -1470,38 +1477,54 @@ impl Permissions { pub fn from_options(opts: &PermissionsOptions) -> Result { Ok(Self { read: Permissions::new_unary( - &opts.allow_read, - &opts.deny_read, + opts.allow_read.as_deref(), + opts.deny_read.as_deref(), opts.prompt, )?, write: Permissions::new_unary( - &opts.allow_write, - &opts.deny_write, + opts.allow_write.as_deref(), + opts.deny_write.as_deref(), opts.prompt, )?, net: Permissions::new_unary( - &opts.allow_net, - &opts.deny_net, + opts.allow_net.as_deref(), + opts.deny_net.as_deref(), opts.prompt, )?, env: Permissions::new_unary( - &opts.allow_env, - &opts.deny_env, + opts.allow_env.as_deref(), + opts.deny_env.as_deref(), opts.prompt, )?, sys: Permissions::new_unary( - &opts.allow_sys, - &opts.deny_sys, + opts.allow_sys.as_deref(), + opts.deny_sys.as_deref(), opts.prompt, )?, run: Permissions::new_unary( - &opts.allow_run, - &opts.deny_run, + opts + .allow_run + .as_ref() + .map(|d| { + d.iter() + .map(|s| RunDescriptorArg::Path(s.clone())) + .collect::>() + }) + .as_deref(), + opts + .deny_run + .as_ref() + .map(|d| { + d.iter() + .map(|s| RunDescriptorArg::from(s.clone())) + .collect::>() + }) + .as_deref(), opts.prompt, )?, ffi: Permissions::new_unary( - &opts.allow_ffi, - &opts.deny_ffi, + opts.allow_ffi.as_deref(), + opts.deny_ffi.as_deref(), opts.prompt, )?, all: Permissions::new_all(opts.allow_all), @@ -1534,13 +1557,13 @@ impl Permissions { fn none(prompt: bool) -> Self { Self { - read: Permissions::new_unary(&None, &None, prompt).unwrap(), - write: Permissions::new_unary(&None, &None, prompt).unwrap(), - net: Permissions::new_unary(&None, &None, prompt).unwrap(), - env: Permissions::new_unary(&None, &None, prompt).unwrap(), - sys: Permissions::new_unary(&None, &None, prompt).unwrap(), - run: Permissions::new_unary(&None, &None, prompt).unwrap(), - ffi: Permissions::new_unary(&None, &None, prompt).unwrap(), + read: Permissions::new_unary(None, None, prompt).unwrap(), + write: Permissions::new_unary(None, None, prompt).unwrap(), + net: Permissions::new_unary(None, None, prompt).unwrap(), + env: Permissions::new_unary(None, None, prompt).unwrap(), + sys: Permissions::new_unary(None, None, prompt).unwrap(), + run: Permissions::new_unary(None, None, prompt).unwrap(), + ffi: Permissions::new_unary(None, None, prompt).unwrap(), all: Permissions::new_all(false), } } @@ -1669,7 +1692,7 @@ impl PermissionsContainer { #[inline(always)] pub fn check_run( &mut self, - cmd: &str, + cmd: &Path, api_name: &str, ) -> Result<(), AnyError> { self.0.lock().run.check(cmd, Some(api_name)) @@ -1680,6 +1703,11 @@ impl PermissionsContainer { self.0.lock().run.check_all(Some(api_name)) } + #[inline(always)] + pub fn query_run_all(&mut self, api_name: &str) -> bool { + self.0.lock().run.query_all(Some(api_name)) + } + #[inline(always)] pub fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError> { self.0.lock().sys.check(kind, Some(api_name)) @@ -1871,12 +1899,12 @@ const fn unit_permission_from_flag_bools( } } -fn global_from_option(flag: &Option>) -> bool { +fn global_from_option(flag: Option<&[T]>) -> bool { matches!(flag, Some(v) if v.is_empty()) } fn parse_net_list( - list: &Option>, + list: Option<&[String]>, ) -> Result, AnyError> { if let Some(v) = list { v.iter() @@ -1888,7 +1916,7 @@ fn parse_net_list( } fn parse_env_list( - list: &Option>, + list: Option<&[String]>, ) -> Result, AnyError> { if let Some(v) = list { v.iter() @@ -1906,7 +1934,7 @@ fn parse_env_list( } fn parse_path_list( - list: &Option>, + list: Option<&[PathBuf]>, f: fn(PathBuf) -> T, ) -> Result, AnyError> { if let Some(v) = list { @@ -1925,7 +1953,7 @@ fn parse_path_list( } fn parse_sys_list( - list: &Option>, + list: Option<&[String]>, ) -> Result, AnyError> { if let Some(v) = list { v.iter() @@ -1943,22 +1971,19 @@ fn parse_sys_list( } fn parse_run_list( - list: &Option>, + list: Option<&[RunDescriptorArg]>, ) -> Result, AnyError> { - let mut result = HashSet::new(); - if let Some(v) = list { - for s in v { - if s.is_empty() { - return Err(AnyError::msg("Empty path is not allowed")); - } else { - let desc = RunDescriptor::from(s.to_string()); - let aliases = desc.aliases(); - result.insert(desc); - result.extend(aliases); - } - } - } - Ok(result) + let Some(v) = list else { + return Ok(HashSet::new()); + }; + Ok( + v.iter() + .map(|arg| match arg { + RunDescriptorArg::Name(s) => RunDescriptor::Name(s.clone()), + RunDescriptorArg::Path(l) => RunDescriptor::Path(l.clone()), + }) + .collect(), + ) } fn escalation_error() -> AnyError { @@ -2298,6 +2323,9 @@ mod tests { macro_rules! svec { ($($x:expr),*) => (vec![$($x.to_string()),*]); } + macro_rules! sarr { + ($($x:expr),*) => ([$($x.to_string()),*]); + } #[test] fn check_paths() { @@ -2678,94 +2706,88 @@ mod tests { set_prompter(Box::new(TestPrompter)); let perms1 = Permissions::allow_all(); let perms2 = Permissions { - read: Permissions::new_unary( - &Some(vec![PathBuf::from("/foo")]), - &None, - false, - ) - .unwrap(), + read: Permissions::new_unary(Some(&[PathBuf::from("/foo")]), None, false) + .unwrap(), write: Permissions::new_unary( - &Some(vec![PathBuf::from("/foo")]), - &None, + Some(&[PathBuf::from("/foo")]), + None, false, ) .unwrap(), - ffi: Permissions::new_unary( - &Some(vec![PathBuf::from("/foo")]), - &None, + ffi: Permissions::new_unary(Some(&[PathBuf::from("/foo")]), None, false) + .unwrap(), + net: Permissions::new_unary(Some(&sarr!["127.0.0.1:8000"]), None, false) + .unwrap(), + env: Permissions::new_unary(Some(&sarr!["HOME"]), None, false).unwrap(), + sys: Permissions::new_unary(Some(&sarr!["hostname"]), None, false) + .unwrap(), + run: Permissions::new_unary( + Some(&["deno".to_string().into()]), + None, false, ) .unwrap(), - net: Permissions::new_unary(&Some(svec!["127.0.0.1:8000"]), &None, false) - .unwrap(), - env: Permissions::new_unary(&Some(svec!["HOME"]), &None, false).unwrap(), - sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false) - .unwrap(), - run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(), all: Permissions::new_all(false), }; let perms3 = Permissions { - read: Permissions::new_unary( - &None, - &Some(vec![PathBuf::from("/foo")]), - false, - ) - .unwrap(), + read: Permissions::new_unary(None, Some(&[PathBuf::from("/foo")]), false) + .unwrap(), write: Permissions::new_unary( - &None, - &Some(vec![PathBuf::from("/foo")]), + None, + Some(&[PathBuf::from("/foo")]), false, ) .unwrap(), - ffi: Permissions::new_unary( - &None, - &Some(vec![PathBuf::from("/foo")]), + ffi: Permissions::new_unary(None, Some(&[PathBuf::from("/foo")]), false) + .unwrap(), + net: Permissions::new_unary(None, Some(&sarr!["127.0.0.1:8000"]), false) + .unwrap(), + env: Permissions::new_unary(None, Some(&sarr!["HOME"]), false).unwrap(), + sys: Permissions::new_unary(None, Some(&sarr!["hostname"]), false) + .unwrap(), + run: Permissions::new_unary( + None, + Some(&["deno".to_string().into()]), false, ) .unwrap(), - net: Permissions::new_unary(&None, &Some(svec!["127.0.0.1:8000"]), false) - .unwrap(), - env: Permissions::new_unary(&None, &Some(svec!["HOME"]), false).unwrap(), - sys: Permissions::new_unary(&None, &Some(svec!["hostname"]), false) - .unwrap(), - run: Permissions::new_unary(&None, &Some(svec!["deno"]), false).unwrap(), all: Permissions::new_all(false), }; let perms4 = Permissions { read: Permissions::new_unary( - &Some(vec![]), - &Some(vec![PathBuf::from("/foo")]), + Some(&[]), + Some(&[PathBuf::from("/foo")]), false, ) .unwrap(), write: Permissions::new_unary( - &Some(vec![]), - &Some(vec![PathBuf::from("/foo")]), + Some(&[]), + Some(&[PathBuf::from("/foo")]), false, ) .unwrap(), ffi: Permissions::new_unary( - &Some(vec![]), - &Some(vec![PathBuf::from("/foo")]), + Some(&[]), + Some(&[PathBuf::from("/foo")]), false, ) .unwrap(), net: Permissions::new_unary( - &Some(vec![]), - &Some(svec!["127.0.0.1:8000"]), + Some(&[]), + Some(&sarr!["127.0.0.1:8000"]), false, ) .unwrap(), - env: Permissions::new_unary(&Some(vec![]), &Some(svec!["HOME"]), false) + env: Permissions::new_unary(Some(&[]), Some(&sarr!["HOME"]), false) .unwrap(), - sys: Permissions::new_unary( - &Some(vec![]), - &Some(svec!["hostname"]), + sys: Permissions::new_unary(Some(&[]), Some(&sarr!["hostname"]), false) + .unwrap(), + run: Permissions::new_unary( + Some(&[]), + Some(&["deno".to_string().into()]), false, ) .unwrap(), - run: Permissions::new_unary(&Some(vec![]), &Some(svec!["deno"]), false) - .unwrap(), all: Permissions::new_all(false), }; #[rustfmt::skip] @@ -2894,33 +2916,38 @@ mod tests { set_prompter(Box::new(TestPrompter)); let mut perms = Permissions { read: Permissions::new_unary( - &Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]), - &None, + Some(&[PathBuf::from("/foo"), PathBuf::from("/foo/baz")]), + None, false, ) .unwrap(), write: Permissions::new_unary( - &Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]), - &None, + Some(&[PathBuf::from("/foo"), PathBuf::from("/foo/baz")]), + None, false, ) .unwrap(), ffi: Permissions::new_unary( - &Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]), - &None, + Some(&[PathBuf::from("/foo"), PathBuf::from("/foo/baz")]), + None, false, ) .unwrap(), net: Permissions::new_unary( - &Some(svec!["127.0.0.1", "127.0.0.1:8000"]), - &None, + Some(&sarr!["127.0.0.1", "127.0.0.1:8000"]), + None, false, ) .unwrap(), - env: Permissions::new_unary(&Some(svec!["HOME"]), &None, false).unwrap(), - sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false) + env: Permissions::new_unary(Some(&sarr!["HOME"]), None, false).unwrap(), + sys: Permissions::new_unary(Some(&sarr!["hostname"]), None, false) .unwrap(), - run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(), + run: Permissions::new_unary( + Some(&["deno".to_string().into()]), + None, + false, + ) + .unwrap(), all: Permissions::new_all(false), }; #[rustfmt::skip] @@ -3006,11 +3033,13 @@ mod tests { .check(&NetDescriptor("deno.land".parse().unwrap(), None), None) .is_err()); + #[allow(clippy::disallowed_methods)] + let cwd = std::env::current_dir().unwrap(); prompt_value.set(true); - assert!(perms.run.check("cat", None).is_ok()); + assert!(perms.run.check(&cwd.join("cat"), None).is_ok()); prompt_value.set(false); - assert!(perms.run.check("cat", None).is_ok()); - assert!(perms.run.check("ls", None).is_err()); + assert!(perms.run.check(&cwd.join("cat"), None).is_ok()); + assert!(perms.run.check(&cwd.join("ls"), None).is_err()); prompt_value.set(true); assert!(perms.env.check("HOME", None).is_ok()); @@ -3102,12 +3131,14 @@ mod tests { .is_ok()); prompt_value.set(false); - assert!(perms.run.check("cat", None).is_err()); + #[allow(clippy::disallowed_methods)] + let cwd = std::env::current_dir().unwrap(); + assert!(perms.run.check(&cwd.join("cat"), None).is_err()); prompt_value.set(true); - assert!(perms.run.check("cat", None).is_err()); - assert!(perms.run.check("ls", None).is_ok()); + assert!(perms.run.check(&cwd.join("cat"), None).is_err()); + assert!(perms.run.check(&cwd.join("ls"), None).is_ok()); prompt_value.set(false); - assert!(perms.run.check("ls", None).is_ok()); + assert!(perms.run.check(&cwd.join("ls"), None).is_ok()); prompt_value.set(false); assert!(perms.env.check("HOME", None).is_err()); @@ -3134,7 +3165,7 @@ mod tests { let mut perms = Permissions::allow_all(); perms.env = UnaryPermission { granted_global: false, - ..Permissions::new_unary(&Some(svec!["HOME"]), &None, false).unwrap() + ..Permissions::new_unary(Some(&sarr!["HOME"]), None, false).unwrap() }; prompt_value.set(true); @@ -3150,14 +3181,14 @@ mod tests { fn test_check_partial_denied() { let mut perms = Permissions { read: Permissions::new_unary( - &Some(vec![]), - &Some(vec![PathBuf::from("/foo/bar")]), + Some(&[]), + Some(&[PathBuf::from("/foo/bar")]), false, ) .unwrap(), write: Permissions::new_unary( - &Some(vec![]), - &Some(vec![PathBuf::from("/foo/bar")]), + Some(&[]), + Some(&[PathBuf::from("/foo/bar")]), false, ) .unwrap(), @@ -3175,8 +3206,8 @@ mod tests { fn test_net_fully_qualified_domain_name() { let mut perms = Permissions { net: Permissions::new_unary( - &Some(vec!["allowed.domain".to_string(), "1.1.1.1".to_string()]), - &Some(vec!["denied.domain".to_string(), "2.2.2.2".to_string()]), + Some(&["allowed.domain".to_string(), "1.1.1.1".to_string()]), + Some(&["denied.domain".to_string(), "2.2.2.2".to_string()]), false, ) .unwrap(), @@ -3341,8 +3372,8 @@ mod tests { fn test_create_child_permissions() { set_prompter(Box::new(TestPrompter)); let mut main_perms = Permissions { - env: Permissions::new_unary(&Some(vec![]), &None, false).unwrap(), - net: Permissions::new_unary(&Some(svec!["foo", "bar"]), &None, false) + env: Permissions::new_unary(Some(&[]), None, false).unwrap(), + net: Permissions::new_unary(Some(&sarr!["foo", "bar"]), None, false) .unwrap(), ..Permissions::none_without_prompt() }; @@ -3358,8 +3389,8 @@ mod tests { ) .unwrap(), Permissions { - env: Permissions::new_unary(&Some(vec![]), &None, false).unwrap(), - net: Permissions::new_unary(&Some(svec!["foo"]), &None, false).unwrap(), + env: Permissions::new_unary(Some(&[]), None, false).unwrap(), + net: Permissions::new_unary(Some(&sarr!["foo"]), None, false).unwrap(), ..Permissions::none_without_prompt() } ); @@ -3445,20 +3476,20 @@ mod tests { set_prompter(Box::new(TestPrompter)); assert!(Permissions::new_unary::( - &Some(vec![Default::default()]), - &None, + Some(&[Default::default()]), + None, false ) .is_err()); assert!(Permissions::new_unary::( - &Some(vec![Default::default()]), - &None, + Some(&[Default::default()]), + None, false ) .is_err()); assert!(Permissions::new_unary::( - &Some(vec![Default::default()]), - &None, + Some(&[Default::default()]), + None, false ) .is_err()); diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 841ef2d182d9be..47fcdb657bd541 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -3683,11 +3683,6 @@ itest!(followup_dyn_import_resolved { output: "run/followup_dyn_import_resolves/main.ts.out", }); -itest!(allow_run_allowlist_resolution { - args: "run --quiet -A allow_run_allowlist_resolution.ts", - output: "allow_run_allowlist_resolution.ts.out", -}); - itest!(unhandled_rejection { args: "run --check run/unhandled_rejection.ts", output: "run/unhandled_rejection.ts.out", @@ -4592,16 +4587,32 @@ fn permission_prompt_escapes_ansi_codes_and_control_chars() { )) }); - util::with_pty(&["repl"], |mut console| { - console.write_line_raw(r#"const boldANSI = "\u001b[1m";"#); - console.expect("undefined"); - console.write_line_raw(r#"const unboldANSI = "\u001b[22m";"#); - console.expect("undefined"); - console.write_line_raw( - r#"new Deno.Command(`${boldANSI}cat${unboldANSI}`).spawn();"#, - ); - console.expect("\u{250f} \u{26a0}\u{fe0f} Deno requests run access to \"\\u{1b}[1mcat\\u{1b}[22m\"."); - }); + // windows doesn't support backslashes in paths, so just try this on unix + if cfg!(unix) { + let context = TestContextBuilder::default().use_temp_cwd().build(); + context + .new_command() + .env("PATH", context.temp_dir().path()) + .env("DYLD_FALLBACK_LIBRARY_PATH", "") + .env("LD_LIBRARY_PATH", "") + .args_vec(["repl", "--allow-write=."]) + .with_pty(|mut console| { + console.write_line_raw(r#"const boldANSI = "\u001b[1m";"#); + console.expect("undefined"); + console.write_line_raw(r#"const unboldANSI = "\u001b[22m";"#); + console.expect("undefined"); + console.write_line_raw( + r#"Deno.writeTextFileSync(`${boldANSI}cat${unboldANSI}`, "");"#, + ); + console.expect("undefined"); + console.write_line_raw( + r#"new Deno.Command(`./${boldANSI}cat${unboldANSI}`).spawn();"#, + ); + console + .expect("\u{250f} \u{26a0}\u{fe0f} Deno requests run access to \""); + console.expect("\\u{1b}[1mcat\\u{1b}[22m\"."); // ensure escaped + }); + } } itest!(node_builtin_modules_ts { diff --git a/tests/specs/compile/permissions_denied/__test__.jsonc b/tests/specs/compile/permissions_denied/__test__.jsonc index 8f85901628a553..ec683ea62e3f59 100644 --- a/tests/specs/compile/permissions_denied/__test__.jsonc +++ b/tests/specs/compile/permissions_denied/__test__.jsonc @@ -1,5 +1,9 @@ { "tempDir": true, + "envs": { + "DYLD_FALLBACK_LIBRARY_PATH": "", + "LD_LIBRARY_PATH": "" + }, "steps": [{ "if": "unix", "args": "compile --output main main.ts", diff --git a/tests/specs/compile/permissions_denied/main.out b/tests/specs/compile/permissions_denied/main.out index e9ea45c812aede..47a4707cc7cdbf 100644 --- a/tests/specs/compile/permissions_denied/main.out +++ b/tests/specs/compile/permissions_denied/main.out @@ -1,2 +1,2 @@ -error: Uncaught (in promise) PermissionDenied: Requires run access to "deno", specify the required permissions during compilation using `deno compile --allow-run` +error: Uncaught (in promise) PermissionDenied: Requires run access to "[WILDLINE]deno[WILDLINE]", specify the required permissions during compilation using `deno compile --allow-run` [WILDCARD] \ No newline at end of file diff --git a/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out b/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out index 65ea53d586f60a..2f0ff11e287035 100644 --- a/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out +++ b/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out @@ -3,6 +3,6 @@ Download http://localhost:4260/@denotest/node-addon-implicit-node-gyp Download http://localhost:4260/@denotest/node-addon-implicit-node-gyp/1.0.0.tgz Initialize @denotest/node-addon-implicit-node-gyp@1.0.0 [UNORDERED_END] -warning: node-gyp was used in a script, but was not listed as a dependency. Either add it as a dependency or install it globally (e.g. `npm install -g node-gyp`) +Warning node-gyp was used in a script, but was not listed as a dependency. Either add it as a dependency or install it globally (e.g. `npm install -g node-gyp`) [WILDCARD] error: script 'install' in '@denotest/node-addon-implicit-node-gyp@1.0.0' failed with exit code 1 diff --git a/tests/specs/permission/path_not_permitted/__test__.jsonc b/tests/specs/permission/path_not_permitted/__test__.jsonc new file mode 100644 index 00000000000000..f10e8b389ea558 --- /dev/null +++ b/tests/specs/permission/path_not_permitted/__test__.jsonc @@ -0,0 +1,10 @@ +{ + "tempDir": true, + "envs": { + "LD_LIBRARY_PATH": "", + "LD_PRELOAD": "", + "DYLD_FALLBACK_LIBRARY_PATH": "" + }, + "args": "run -A main.ts", + "output": "main.out" +} diff --git a/tests/specs/permission/path_not_permitted/main.out b/tests/specs/permission/path_not_permitted/main.out new file mode 100644 index 00000000000000..3817c2ca51c58e --- /dev/null +++ b/tests/specs/permission/path_not_permitted/main.out @@ -0,0 +1,11 @@ +Running... +PermissionDenied: Requires run access to "[WILDLINE]deno[WILDLINE]", run again with the --allow-run flag + [WILDCARD] + at file:///[WILDLINE]/sub.ts:15:5 { + name: "PermissionDenied" +} +PermissionDenied: Requires run access to "[WILDLINE]deno[WILDLINE]", run again with the --allow-run flag + [WILDCARD] + at file:///[WILDLINE]/sub.ts:23:22 { + name: "PermissionDenied" +} diff --git a/tests/specs/permission/path_not_permitted/main.ts b/tests/specs/permission/path_not_permitted/main.ts new file mode 100644 index 00000000000000..9e8d627f2ae445 --- /dev/null +++ b/tests/specs/permission/path_not_permitted/main.ts @@ -0,0 +1,18 @@ +const binaryName = Deno.build.os === "windows" ? "deno.exe" : "deno"; +Deno.copyFileSync(Deno.execPath(), binaryName); + +console.log("Running..."); +new Deno.Command( + Deno.execPath(), + { + args: [ + "run", + "--allow-write", + "--allow-read", + `--allow-run=${binaryName}`, + "sub.ts", + ], + stderr: "inherit", + stdout: "inherit", + }, +).outputSync(); diff --git a/tests/specs/permission/path_not_permitted/sub.ts b/tests/specs/permission/path_not_permitted/sub.ts new file mode 100644 index 00000000000000..f2b6d6b37c769d --- /dev/null +++ b/tests/specs/permission/path_not_permitted/sub.ts @@ -0,0 +1,34 @@ +const binaryName = Deno.build.os === "windows" ? "deno.exe" : "deno"; +const pathSep = Deno.build.os === "windows" ? "\\" : "/"; + +Deno.mkdirSync("subdir"); +Deno.copyFileSync(binaryName, "subdir/" + binaryName); + +try { + const commandResult = new Deno.Command( + binaryName, + { + env: { "PATH": Deno.cwd() + pathSep + "subdir" }, + stdout: "inherit", + stderr: "inherit", + }, + ).outputSync(); + + console.log(commandResult.code); +} catch (err) { + console.log(err); +} + +try { + const child = Deno.run( + { + cmd: [binaryName], + env: { "PATH": Deno.cwd() + pathSep + "subdir" }, + stdout: "inherit", + stderr: "inherit", + }, + ); + console.log((await child.status()).code); +} catch (err) { + console.log(err); +} diff --git a/tests/specs/permission/write_allow_binary/__test__.jsonc b/tests/specs/permission/write_allow_binary/__test__.jsonc new file mode 100644 index 00000000000000..a47fed572dcd24 --- /dev/null +++ b/tests/specs/permission/write_allow_binary/__test__.jsonc @@ -0,0 +1,5 @@ +{ + "tempDir": true, + "args": "run -A main.ts", + "output": "main.out" +} diff --git a/tests/specs/permission/write_allow_binary/main.out b/tests/specs/permission/write_allow_binary/main.out new file mode 100644 index 00000000000000..e7c47f2883de40 --- /dev/null +++ b/tests/specs/permission/write_allow_binary/main.out @@ -0,0 +1,6 @@ +Running... +error: Uncaught (in promise) PermissionDenied: Requires write access to "binary[WILDLINE]", run again with the --allow-write flag +Deno.writeTextFileSync(binaryName, ""); + ^ + at [WILDCARD] + at file:///[WILDLINE]sub.ts:3:6 diff --git a/tests/specs/permission/write_allow_binary/main.ts b/tests/specs/permission/write_allow_binary/main.ts new file mode 100644 index 00000000000000..73deeab9a7bbf0 --- /dev/null +++ b/tests/specs/permission/write_allow_binary/main.ts @@ -0,0 +1,14 @@ +const binaryName = Deno.build.os === "windows" ? "binary.exe" : "binary"; +Deno.copyFileSync(Deno.execPath(), binaryName); + +console.log("Running..."); +const result = new Deno.Command( + Deno.execPath(), + { + args: ["run", "--allow-write", `--allow-run=./${binaryName}`, "sub.ts"], + stderr: "inherit", + stdout: "inherit", + }, +).outputSync(); + +console.assert(result.code == 1, "Expected failure"); diff --git a/tests/specs/permission/write_allow_binary/sub.ts b/tests/specs/permission/write_allow_binary/sub.ts new file mode 100644 index 00000000000000..e865597b15749d --- /dev/null +++ b/tests/specs/permission/write_allow_binary/sub.ts @@ -0,0 +1,3 @@ +const binaryName = Deno.build.os === "windows" ? "binary.exe" : "binary"; + +Deno.writeTextFileSync(binaryName, ""); diff --git a/tests/specs/run/allow_run_allowlist_resolution/__test__.jsonc b/tests/specs/run/allow_run_allowlist_resolution/__test__.jsonc new file mode 100644 index 00000000000000..173e13027fc347 --- /dev/null +++ b/tests/specs/run/allow_run_allowlist_resolution/__test__.jsonc @@ -0,0 +1,8 @@ +{ + "args": "run --quiet -A main.ts", + "output": "main.out", + "envs": { + "DYLD_FALLBACK_LIBRARY_PATH": "", + "LD_LIBRARY_PATH": "" + } +} diff --git a/tests/testdata/allow_run_allowlist_resolution.ts.out b/tests/specs/run/allow_run_allowlist_resolution/main.out similarity index 67% rename from tests/testdata/allow_run_allowlist_resolution.ts.out rename to tests/specs/run/allow_run_allowlist_resolution/main.out index 16ba6754a92111..f61f9b55030ec2 100644 --- a/tests/testdata/allow_run_allowlist_resolution.ts.out +++ b/tests/specs/run/allow_run_allowlist_resolution/main.out @@ -1,15 +1,15 @@ PermissionStatus { state: "granted", onchange: null } -PermissionStatus { state: "granted", onchange: null } -PermissionStatus { state: "granted", onchange: null } -PermissionStatus { state: "granted", onchange: null } - PermissionStatus { state: "granted", onchange: null } PermissionStatus { state: "prompt", onchange: null } PermissionStatus { state: "granted", onchange: null } +--- +Info Failed to resolve 'deno' for allow-run: cannot find binary path +PermissionStatus { state: "prompt", onchange: null } +PermissionStatus { state: "prompt", onchange: null } +PermissionStatus { state: "prompt", onchange: null } PermissionStatus { state: "prompt", onchange: null } - +--- PermissionStatus { state: "granted", onchange: null } PermissionStatus { state: "granted", onchange: null } PermissionStatus { state: "prompt", onchange: null } PermissionStatus { state: "granted", onchange: null } - diff --git a/tests/testdata/allow_run_allowlist_resolution.ts b/tests/specs/run/allow_run_allowlist_resolution/main.ts similarity index 71% rename from tests/testdata/allow_run_allowlist_resolution.ts rename to tests/specs/run/allow_run_allowlist_resolution/main.ts index c7369d928a097a..bf33d8cbe10a05 100644 --- a/tests/testdata/allow_run_allowlist_resolution.ts +++ b/tests/specs/run/allow_run_allowlist_resolution/main.ts @@ -1,26 +1,26 @@ // Testing the following (but with `deno` instead of `echo`): // | `deno run --allow-run=echo` | `which path == "/usr/bin/echo"` at startup | `which path != "/usr/bin/echo"` at startup | // |-------------------------------------|--------------------------------------------|--------------------------------------------| -// | **`Deno.Command("echo")`** | ✅ | ✅ | -// | **`Deno.Command("/usr/bin/echo")`** | ✅ | ❌ | +// | **`Deno.Command("echo")`** | ✅ | ✅ | +// | **`Deno.Command("/usr/bin/echo")`** | ✅ | ❌ | // | `deno run --allow-run=/usr/bin/echo | `which path == "/usr/bin/echo"` at runtime | `which path != "/usr/bin/echo"` at runtime | // |-------------------------------------|--------------------------------------------|--------------------------------------------| -// | **`Deno.Command("echo")`** | ✅ | ❌ | -// | **`Deno.Command("/usr/bin/echo")`** | ✅ | ✅ | +// | **`Deno.Command("echo")`** | ✅ | ❌ | +// | **`Deno.Command("/usr/bin/echo")`** | ✅ | ✅ | const execPath = Deno.execPath(); const execPathParent = execPath.replace(/[/\\][^/\\]+$/, ""); const testUrl = `data:application/typescript;base64,${ btoa(` - console.log(await Deno.permissions.query({ name: "run", command: "deno" })); - console.log(await Deno.permissions.query({ name: "run", command: "${ + console.error(await Deno.permissions.query({ name: "run", command: "deno" })); + console.error(await Deno.permissions.query({ name: "run", command: "${ execPath.replaceAll("\\", "\\\\") }" })); Deno.env.set("PATH", ""); - console.log(await Deno.permissions.query({ name: "run", command: "deno" })); - console.log(await Deno.permissions.query({ name: "run", command: "${ + console.error(await Deno.permissions.query({ name: "run", command: "deno" })); + console.error(await Deno.permissions.query({ name: "run", command: "${ execPath.replaceAll("\\", "\\\\") }" })); `) @@ -29,38 +29,39 @@ const testUrl = `data:application/typescript;base64,${ const process1 = await new Deno.Command(Deno.execPath(), { args: [ "run", - "--quiet", "--allow-env", "--allow-run=deno", testUrl, ], - stderr: "null", + stdout: "inherit", + stderr: "inherit", env: { "PATH": execPathParent }, }).output(); -console.log(new TextDecoder().decode(process1.stdout)); -const process2 = await new Deno.Command(Deno.execPath(), { +console.error("---"); + +await new Deno.Command(Deno.execPath(), { args: [ "run", - "--quiet", "--allow-env", "--allow-run=deno", testUrl, ], - stderr: "null", + stderr: "inherit", + stdout: "inherit", env: { "PATH": "" }, }).output(); -console.log(new TextDecoder().decode(process2.stdout)); -const process3 = await new Deno.Command(Deno.execPath(), { +console.error("---"); + +await new Deno.Command(Deno.execPath(), { args: [ "run", - "--quiet", "--allow-env", `--allow-run=${execPath}`, testUrl, ], - stderr: "null", + stderr: "inherit", + stdout: "inherit", env: { "PATH": execPathParent }, }).output(); -console.log(new TextDecoder().decode(process3.stdout)); diff --git a/tests/specs/run/ld_preload/__test__.jsonc b/tests/specs/run/ld_preload/__test__.jsonc index 767e423d063055..882f157e9e0f9c 100644 --- a/tests/specs/run/ld_preload/__test__.jsonc +++ b/tests/specs/run/ld_preload/__test__.jsonc @@ -7,13 +7,11 @@ "tests": { "env_arg": { "args": "run --allow-run=echo env_arg.ts", - "output": "env_arg.out", - "exitCode": 1 + "output": "env_arg.out" }, "set_with_allow_env": { "args": "run --allow-run=echo --allow-env set_with_allow_env.ts", - "output": "set_with_allow_env.out", - "exitCode": 1 + "output": "set_with_allow_env.out" } } } diff --git a/tests/specs/run/ld_preload/env_arg.out b/tests/specs/run/ld_preload/env_arg.out index fbf37014ae78b4..3df781a8e6a7d5 100644 --- a/tests/specs/run/ld_preload/env_arg.out +++ b/tests/specs/run/ld_preload/env_arg.out @@ -1,4 +1,8 @@ -error: Uncaught (in promise) PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable. -}).spawn(); - ^ - at [WILDCARD] +PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable. + [WILDCARD] + name: "PermissionDenied" +} +PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable. + [WILDCARD] + name: "PermissionDenied" +} diff --git a/tests/specs/run/ld_preload/env_arg.ts b/tests/specs/run/ld_preload/env_arg.ts index 0b236619e109dc..d7ca1073dfa088 100644 --- a/tests/specs/run/ld_preload/env_arg.ts +++ b/tests/specs/run/ld_preload/env_arg.ts @@ -1,5 +1,20 @@ -const output = new Deno.Command("echo", { - env: { - "LD_PRELOAD": "./libpreload.so", - }, -}).spawn(); +try { + new Deno.Command("echo", { + env: { + "LD_PRELOAD": "./libpreload.so", + }, + }).spawn(); +} catch (err) { + console.log(err); +} + +try { + Deno.run({ + cmd: ["echo"], + env: { + "LD_PRELOAD": "./libpreload.so", + }, + }); +} catch (err) { + console.log(err); +} diff --git a/tests/specs/run/ld_preload/set_with_allow_env.out b/tests/specs/run/ld_preload/set_with_allow_env.out index 2e92763ddac227..60dba7cff1e78d 100644 --- a/tests/specs/run/ld_preload/set_with_allow_env.out +++ b/tests/specs/run/ld_preload/set_with_allow_env.out @@ -1,4 +1,8 @@ -error: Uncaught (in promise) PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable. -const output = new Deno.Command("echo").spawn(); - ^ - at [WILDCARD] +PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable. + [WILDCARD] + name: "PermissionDenied" +} +PermissionDenied: Requires --allow-all permissions to spawn subprocess with DYLD_FALLBACK_LIBRARY_PATH, LD_PRELOAD environment variables. + [WILDCARD] + name: "PermissionDenied" +} diff --git a/tests/specs/run/ld_preload/set_with_allow_env.ts b/tests/specs/run/ld_preload/set_with_allow_env.ts index 9530f4478c5ede..79004aa1656ecf 100644 --- a/tests/specs/run/ld_preload/set_with_allow_env.ts +++ b/tests/specs/run/ld_preload/set_with_allow_env.ts @@ -1,3 +1,15 @@ Deno.env.set("LD_PRELOAD", "./libpreload.so"); -const output = new Deno.Command("echo").spawn(); +try { + new Deno.Command("echo").spawn(); +} catch (err) { + console.log(err); +} + +Deno.env.set("DYLD_FALLBACK_LIBRARY_PATH", "./libpreload.so"); + +try { + Deno.run({ cmd: ["echo"] }).spawnSync(); +} catch (err) { + console.log(err); +} diff --git a/tests/testdata/run/089_run_allow_list.ts.out b/tests/testdata/run/089_run_allow_list.ts.out index 68a4a2ac578a61..0fc1c80c2a948d 100644 --- a/tests/testdata/run/089_run_allow_list.ts.out +++ b/tests/testdata/run/089_run_allow_list.ts.out @@ -1,3 +1,3 @@ -[WILDCARD]PermissionDenied: Requires run access to "ls", run again with the --allow-run flag +[WILDCARD]PermissionDenied: Requires run access to "[WILDLINE]ls[WILDLINE]", run again with the --allow-run flag [WILDCARD] true diff --git a/tests/unit/process_test.ts b/tests/unit/process_test.ts index a35362d090feb8..383f17f384252b 100644 --- a/tests/unit/process_test.ts +++ b/tests/unit/process_test.ts @@ -611,6 +611,6 @@ Deno.test( p.close(); p.stdout.close(); assertStrictEquals(code, 1); - assertStringIncludes(stderr, "Failed getting cwd."); + assertStringIncludes(stderr, "failed resolving cwd:"); }, ); diff --git a/tools/lint.js b/tools/lint.js index d40b1b1fd92827..08b551e984dd33 100755 --- a/tools/lint.js +++ b/tools/lint.js @@ -221,7 +221,7 @@ async function ensureNoNewITests() { "pm_tests.rs": 0, "publish_tests.rs": 0, "repl_tests.rs": 0, - "run_tests.rs": 351, + "run_tests.rs": 350, "shared_library_tests.rs": 0, "task_tests.rs": 30, "test_tests.rs": 75,