1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

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.
This commit is contained in:
David Sherret 2024-09-04 14:51:24 +02:00 committed by GitHub
parent 334c842392
commit 74fc66da11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 684 additions and 364 deletions

View file

@ -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),
})
}

View file

@ -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(),

View file

@ -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 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(args.first().unwrap());
(1..args.len()).for_each(|i| {
let arg = args.get(i).unwrap();
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);
}

View file

@ -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
}
}
AllowPartial::TreatAsPartialGranted => {
if self.is_partial_flag_denied(desc) {
PermissionState::GrantedPartial
} 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
}
}
} 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))
@ -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<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());

View file

@ -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 {

View file

@ -1,5 +1,9 @@
{
"tempDir": true,
"envs": {
"DYLD_FALLBACK_LIBRARY_PATH": "",
"LD_LIBRARY_PATH": ""
},
"steps": [{
"if": "unix",
"args": "compile --output main main.ts",

View file

@ -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]

View file

@ -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

View file

@ -0,0 +1,10 @@
{
"tempDir": true,
"envs": {
"LD_LIBRARY_PATH": "",
"LD_PRELOAD": "",
"DYLD_FALLBACK_LIBRARY_PATH": ""
},
"args": "run -A main.ts",
"output": "main.out"
}

View file

@ -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"
}

View file

@ -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();

View file

@ -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);
}

View file

@ -0,0 +1,5 @@
{
"tempDir": true,
"args": "run -A main.ts",
"output": "main.out"
}

View file

@ -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

View file

@ -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");

View file

@ -0,0 +1,3 @@
const binaryName = Deno.build.os === "windows" ? "binary.exe" : "binary";
Deno.writeTextFileSync(binaryName, "");

View file

@ -0,0 +1,8 @@
{
"args": "run --quiet -A main.ts",
"output": "main.out",
"envs": {
"DYLD_FALLBACK_LIBRARY_PATH": "",
"LD_LIBRARY_PATH": ""
}
}

View file

@ -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 }
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 }
---
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 }

View file

@ -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));

View file

@ -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"
}
}
}

View file

@ -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"
}

View file

@ -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);
}

View file

@ -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"
}

View file

@ -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);
}

View file

@ -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

View file

@ -611,6 +611,6 @@ Deno.test(
p.close();
p.stdout.close();
assertStrictEquals(code, 1);
assertStringIncludes(stderr, "Failed getting cwd.");
assertStringIncludes(stderr, "failed resolving cwd:");
},
);

View file

@ -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,