summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-09-04 14:51:24 +0200
committerGitHub <noreply@github.com>2024-09-04 14:51:24 +0200
commit74fc66da110ec20d12751e7a0922cea300314399 (patch)
treeb0b057b7539b506b8db39287cd799e7c9cbd526f
parent334c842392e2587b8ca1d7cc7cc7d9231fc15286 (diff)
fix: lock down allow-run permissions more (#25370)
`--allow-run` even with an allow list has essentially been `--allow-all`... this locks it down more. 1. Resolves allow list for `--allow-run=` on startup to an absolute path, then uses these paths when evaluating if a command can execute. Also, adds these paths to `--deny-write` 1. Resolves the environment (cwd and env vars) before evaluating permissions and before executing a command. Then uses this environment to evaluate the permissions and then evaluate the command.
-rw-r--r--cli/args/flags.rs82
-rw-r--r--cli/task_runner.rs4
-rw-r--r--runtime/ops/process.rs237
-rw-r--r--runtime/permissions/lib.rs431
-rw-r--r--tests/integration/run_tests.rs41
-rw-r--r--tests/specs/compile/permissions_denied/__test__.jsonc4
-rw-r--r--tests/specs/compile/permissions_denied/main.out2
-rw-r--r--tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out2
-rw-r--r--tests/specs/permission/path_not_permitted/__test__.jsonc10
-rw-r--r--tests/specs/permission/path_not_permitted/main.out11
-rw-r--r--tests/specs/permission/path_not_permitted/main.ts18
-rw-r--r--tests/specs/permission/path_not_permitted/sub.ts34
-rw-r--r--tests/specs/permission/write_allow_binary/__test__.jsonc5
-rw-r--r--tests/specs/permission/write_allow_binary/main.out6
-rw-r--r--tests/specs/permission/write_allow_binary/main.ts14
-rw-r--r--tests/specs/permission/write_allow_binary/sub.ts3
-rw-r--r--tests/specs/run/allow_run_allowlist_resolution/__test__.jsonc8
-rw-r--r--tests/specs/run/allow_run_allowlist_resolution/main.out (renamed from tests/testdata/allow_run_allowlist_resolution.ts.out)12
-rw-r--r--tests/specs/run/allow_run_allowlist_resolution/main.ts (renamed from tests/testdata/allow_run_allowlist_resolution.ts)39
-rw-r--r--tests/specs/run/ld_preload/__test__.jsonc6
-rw-r--r--tests/specs/run/ld_preload/env_arg.out12
-rw-r--r--tests/specs/run/ld_preload/env_arg.ts25
-rw-r--r--tests/specs/run/ld_preload/set_with_allow_env.out12
-rw-r--r--tests/specs/run/ld_preload/set_with_allow_env.ts14
-rw-r--r--tests/testdata/run/089_run_allow_list.ts.out2
-rw-r--r--tests/unit/process_test.ts2
-rwxr-xr-xtools/lint.js2
27 files changed, 679 insertions, 359 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 257bf8178..5ea8b8ecf 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<Vec<PathBuf>, 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 e8937590d..ab7163bc9 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 11e439051..eb53151ce 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<CreateCommand, AnyError> {
- fn get_requires_allow_all_env_var(args: &SpawnArgs) -> Option<Cow<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_")
- }
-
- /// 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::<PermissionsContainer>();
- 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<String, String>,
+ 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<RunEnv, AnyError> {
+ #[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::<HashMap<_, _>>();
+ 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<PathBuf, AnyError> {
+ 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::<PermissionsContainer>();
+ 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::<Vec<_>>();
+ 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<RunInfo, AnyError> {
let args = run_args.cmd;
- state
- .borrow_mut::<PermissionsContainer>()
- .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 7227bebf8..2eacd8bcc 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError>;
+ fn parse(list: Option<&[Self::Arg]>) -> Result<HashSet<Self>, 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<Self> {
- vec![]
- }
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -423,43 +419,33 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
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::<Vec<_>>())
- {
- 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<T: Descriptor + Hash> UnaryPermission<T> {
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<T: Descriptor + Hash> UnaryPermission<T> {
) {
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<T: Descriptor + Hash> UnaryPermission<T> {
ChildUnaryPermissionArg::GrantedList(granted_list) => {
let granted: Vec<T::Arg> =
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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(args: Option<&[Self::Arg]>) -> Result<HashSet<Self>, 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(args: Option<&[Self::Arg]>) -> Result<HashSet<Self>, 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(args: Option<&[Self::Arg]>) -> Result<HashSet<Self>, 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(list: Option<&[Self::Arg]>) -> Result<HashSet<Self>, AnyError> {
parse_env_list(list)
}
@@ -883,6 +862,11 @@ impl AsRef<str> 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<String> 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(args: Option<&[Self::Arg]>) -> Result<HashSet<Self>, AnyError> {
parse_run_list(args)
}
@@ -916,16 +918,6 @@ impl Descriptor for RunDescriptor {
fn name(&self) -> Cow<str> {
Cow::from(self.to_string())
}
-
- fn aliases(&self) -> Vec<Self> {
- match self {
- RunDescriptor::Name(name) => match which(name) {
- Ok(path) => vec![RunDescriptor::Path(path)],
- Err(_) => vec![],
- },
- RunDescriptor::Path(_) => vec![],
- }
- }
}
impl From<String> for RunDescriptor {
@@ -938,7 +930,10 @@ impl From<String> 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<PathBuf> 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(list: Option<&[Self::Arg]>) -> Result<HashSet<Self>, 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<Vec<Self::Arg>>) -> Result<HashSet<Self>, AnyError> {
+ fn parse(list: Option<&[Self::Arg]>) -> Result<HashSet<Self>, AnyError> {
parse_path_list(list, FfiDescriptor)
}
@@ -1330,15 +1321,16 @@ impl UnaryPermission<RunDescriptor> {
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<RunDescriptor> {
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<FfiDescriptor> {
@@ -1429,7 +1436,7 @@ pub struct PermissionsOptions {
pub deny_ffi: Option<Vec<PathBuf>>,
pub allow_read: Option<Vec<PathBuf>>,
pub deny_read: Option<Vec<PathBuf>>,
- pub allow_run: Option<Vec<String>>,
+ pub allow_run: Option<Vec<PathBuf>>,
pub deny_run: Option<Vec<String>>,
pub allow_sys: Option<Vec<String>>,
pub deny_sys: Option<Vec<String>>,
@@ -1440,8 +1447,8 @@ pub struct PermissionsOptions {
impl Permissions {
pub fn new_unary<T>(
- allow_list: &Option<Vec<T::Arg>>,
- deny_list: &Option<Vec<T::Arg>>,
+ allow_list: Option<&[T::Arg]>,
+ deny_list: Option<&[T::Arg]>,
prompt: bool,
) -> Result<UnaryPermission<T>, AnyError>
where
@@ -1470,38 +1477,54 @@ impl Permissions {
pub fn from_options(opts: &PermissionsOptions) -> Result<Self, AnyError> {
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::<Vec<_>>()
+ })
+ .as_deref(),
+ opts
+ .deny_run
+ .as_ref()
+ .map(|d| {
+ d.iter()
+ .map(|s| RunDescriptorArg::from(s.clone()))
+ .collect::<Vec<_>>()
+ })
+ .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))
@@ -1681,6 +1704,11 @@ impl PermissionsContainer {
}
#[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<T>(flag: &Option<Vec<T>>) -> bool {
+fn global_from_option<T>(flag: Option<&[T]>) -> bool {
matches!(flag, Some(v) if v.is_empty())
}
fn parse_net_list(
- list: &Option<Vec<String>>,
+ list: Option<&[String]>,
) -> Result<HashSet<NetDescriptor>, AnyError> {
if let Some(v) = list {
v.iter()
@@ -1888,7 +1916,7 @@ fn parse_net_list(
}
fn parse_env_list(
- list: &Option<Vec<String>>,
+ list: Option<&[String]>,
) -> Result<HashSet<EnvDescriptor>, AnyError> {
if let Some(v) = list {
v.iter()
@@ -1906,7 +1934,7 @@ fn parse_env_list(
}
fn parse_path_list<T: Descriptor + Hash>(
- list: &Option<Vec<PathBuf>>,
+ list: Option<&[PathBuf]>,
f: fn(PathBuf) -> T,
) -> Result<HashSet<T>, AnyError> {
if let Some(v) = list {
@@ -1925,7 +1953,7 @@ fn parse_path_list<T: Descriptor + Hash>(
}
fn parse_sys_list(
- list: &Option<Vec<String>>,
+ list: Option<&[String]>,
) -> Result<HashSet<SysDescriptor>, AnyError> {
if let Some(v) = list {
v.iter()
@@ -1943,22 +1971,19 @@ fn parse_sys_list(
}
fn parse_run_list(
- list: &Option<Vec<String>>,
+ list: Option<&[RunDescriptorArg]>,
) -> Result<HashSet<RunDescriptor>, 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::<ReadDescriptor>(
- &Some(vec![Default::default()]),
- &None,
+ Some(&[Default::default()]),
+ None,
false
)
.is_err());
assert!(Permissions::new_unary::<EnvDescriptor>(
- &Some(vec![Default::default()]),
- &None,
+ Some(&[Default::default()]),
+ None,
false
)
.is_err());
assert!(Permissions::new_unary::<NetDescriptor>(
- &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 841ef2d18..47fcdb657 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 8f8590162..ec683ea62 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 e9ea45c81..47a4707cc 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 65ea53d58..2f0ff11e2 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 000000000..f10e8b389
--- /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 000000000..3817c2ca5
--- /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 000000000..9e8d627f2
--- /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 000000000..f2b6d6b37
--- /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 000000000..a47fed572
--- /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 000000000..e7c47f288
--- /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 000000000..73deeab9a
--- /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 000000000..e865597b1
--- /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 000000000..173e13027
--- /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
index 16ba6754a..f61f9b550 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
index c7369d928..bf33d8cbe 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 767e423d0..882f157e9 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 fbf37014a..3df781a8e 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 0b236619e..d7ca1073d 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 2e92763dd..60dba7cff 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 9530f4478..79004aa16 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 68a4a2ac5..0fc1c80c2 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 a35362d09..383f17f38 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 d40b1b1fd..08b551e98 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,