mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -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:
parent
334c842392
commit
74fc66da11
27 changed files with 684 additions and 364 deletions
|
@ -1,7 +1,16 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use crate::args::resolve_no_prompt;
|
use std::collections::HashSet;
|
||||||
use crate::util::fs::canonicalize_path;
|
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::styling::AnsiColor;
|
||||||
use clap::builder::FalseyValueParser;
|
use clap::builder::FalseyValueParser;
|
||||||
use clap::error::ErrorKind;
|
use clap::error::ErrorKind;
|
||||||
|
@ -23,22 +32,16 @@ use deno_core::normalize_path;
|
||||||
use deno_core::resolve_url_or_path;
|
use deno_core::resolve_url_or_path;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
use deno_graph::GraphKind;
|
use deno_graph::GraphKind;
|
||||||
|
use deno_runtime::colors;
|
||||||
use deno_runtime::deno_permissions::parse_sys_kind;
|
use deno_runtime::deno_permissions::parse_sys_kind;
|
||||||
use deno_runtime::deno_permissions::PermissionsOptions;
|
use deno_runtime::deno_permissions::PermissionsOptions;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::env;
|
use crate::args::resolve_no_prompt;
|
||||||
use std::ffi::OsString;
|
use crate::util::fs::canonicalize_path;
|
||||||
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 super::flags_net;
|
use super::flags_net;
|
||||||
|
|
||||||
|
@ -681,6 +684,54 @@ impl PermissionFlags {
|
||||||
Ok(Some(new_paths))
|
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 {
|
Ok(PermissionsOptions {
|
||||||
allow_all: self.allow_all,
|
allow_all: self.allow_all,
|
||||||
allow_env: self.allow_env.clone(),
|
allow_env: self.allow_env.clone(),
|
||||||
|
@ -694,7 +745,7 @@ impl PermissionFlags {
|
||||||
initial_cwd,
|
initial_cwd,
|
||||||
)?,
|
)?,
|
||||||
deny_read: convert_option_str_to_path_buf(&self.deny_read, 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(),
|
deny_run: self.deny_run.clone(),
|
||||||
allow_sys: self.allow_sys.clone(),
|
allow_sys: self.allow_sys.clone(),
|
||||||
deny_sys: self.deny_sys.clone(),
|
deny_sys: self.deny_sys.clone(),
|
||||||
|
@ -702,10 +753,7 @@ impl PermissionFlags {
|
||||||
&self.allow_write,
|
&self.allow_write,
|
||||||
initial_cwd,
|
initial_cwd,
|
||||||
)?,
|
)?,
|
||||||
deny_write: convert_option_str_to_path_buf(
|
deny_write,
|
||||||
&self.deny_write,
|
|
||||||
initial_cwd,
|
|
||||||
)?,
|
|
||||||
prompt: !resolve_no_prompt(self),
|
prompt: !resolve_no_prompt(self),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,8 +213,8 @@ impl ShellCommand for NodeGypCommand {
|
||||||
) -> LocalBoxFuture<'static, ExecuteResult> {
|
) -> LocalBoxFuture<'static, ExecuteResult> {
|
||||||
// at the moment this shell command is just to give a warning if node-gyp is not found
|
// 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
|
// in the future, we could try to run/install node-gyp for the user with deno
|
||||||
if which::which("node-gyp").is_err() {
|
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"));
|
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(
|
ExecutableCommand::new(
|
||||||
"node-gyp".to_string(),
|
"node-gyp".to_string(),
|
||||||
|
|
|
@ -21,6 +21,9 @@ use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::ExitStatus;
|
use std::process::ExitStatus;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
@ -228,63 +231,15 @@ fn create_command(
|
||||||
mut args: SpawnArgs,
|
mut args: SpawnArgs,
|
||||||
api_name: &str,
|
api_name: &str,
|
||||||
) -> Result<CreateCommand, AnyError> {
|
) -> Result<CreateCommand, AnyError> {
|
||||||
fn get_requires_allow_all_env_var(args: &SpawnArgs) -> Option<Cow<str>> {
|
let (cmd, run_env) = compute_run_cmd_and_check_permissions(
|
||||||
fn requires_allow_all(key: &str) -> bool {
|
&args.cmd,
|
||||||
let key = key.trim();
|
args.cwd.as_deref(),
|
||||||
// we could be more targted here, but there are quite a lot of
|
&args.env,
|
||||||
// LD_* and DYLD_* env variables
|
args.clear_env,
|
||||||
key.starts_with("LD_") || key.starts_with("DYLD_")
|
state,
|
||||||
}
|
api_name,
|
||||||
|
)?;
|
||||||
/// Checks if the user set this env var to an empty
|
let mut command = std::process::Command::new(cmd);
|
||||||
/// 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);
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if args.windows_raw_arguments {
|
if args.windows_raw_arguments {
|
||||||
|
@ -298,14 +253,9 @@ fn create_command(
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
command.args(args.args);
|
command.args(args.args);
|
||||||
|
|
||||||
if let Some(cwd) = args.cwd {
|
command.current_dir(run_env.cwd);
|
||||||
command.current_dir(cwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.clear_env {
|
|
||||||
command.env_clear();
|
command.env_clear();
|
||||||
}
|
command.envs(run_env.envs);
|
||||||
command.envs(args.env);
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if let Some(gid) = args.gid {
|
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]
|
#[op2]
|
||||||
#[serde]
|
#[serde]
|
||||||
fn op_spawn_child(
|
fn op_spawn_child(
|
||||||
|
@ -634,6 +711,8 @@ fn op_spawn_kill(
|
||||||
}
|
}
|
||||||
|
|
||||||
mod deprecated {
|
mod deprecated {
|
||||||
|
use deno_core::anyhow;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -681,20 +760,24 @@ mod deprecated {
|
||||||
#[serde] run_args: RunArgs,
|
#[serde] run_args: RunArgs,
|
||||||
) -> Result<RunInfo, AnyError> {
|
) -> Result<RunInfo, AnyError> {
|
||||||
let args = run_args.cmd;
|
let args = run_args.cmd;
|
||||||
state
|
let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?;
|
||||||
.borrow_mut::<PermissionsContainer>()
|
let (cmd, run_env) = compute_run_cmd_and_check_permissions(
|
||||||
.check_run(&args[0], "Deno.run()")?;
|
cmd,
|
||||||
let env = run_args.env;
|
run_args.cwd.as_deref(),
|
||||||
let cwd = run_args.cwd;
|
&run_args.env,
|
||||||
|
/* clear env */ false,
|
||||||
|
state,
|
||||||
|
"Deno.run()",
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut c = Command::new(args.first().unwrap());
|
let mut c = Command::new(cmd);
|
||||||
(1..args.len()).for_each(|i| {
|
for arg in args.iter().skip(1) {
|
||||||
let arg = args.get(i).unwrap();
|
|
||||||
c.arg(arg);
|
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);
|
c.env(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use which::which;
|
|
||||||
|
|
||||||
pub mod prompter;
|
pub mod prompter;
|
||||||
use prompter::permission_prompt;
|
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
|
/// Parse this descriptor from a list of Self::Arg, which may have been converted from
|
||||||
/// command-line strings.
|
/// 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`.
|
/// Generic check function to check this descriptor against a `UnaryPermission`.
|
||||||
fn check_in_permission(
|
fn check_in_permission(
|
||||||
|
@ -333,9 +332,6 @@ pub trait Descriptor: Eq + Clone + Hash {
|
||||||
fn stronger_than(&self, other: &Self) -> bool {
|
fn stronger_than(&self, other: &Self) -> bool {
|
||||||
self == other
|
self == other
|
||||||
}
|
}
|
||||||
fn aliases(&self) -> Vec<Self> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -423,12 +419,7 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
|
||||||
desc: Option<&T>,
|
desc: Option<&T>,
|
||||||
allow_partial: AllowPartial,
|
allow_partial: AllowPartial,
|
||||||
) -> PermissionState {
|
) -> PermissionState {
|
||||||
let aliases = desc.map_or(vec![], T::aliases);
|
if self.is_flag_denied(desc) || self.is_prompt_denied(desc) {
|
||||||
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
|
PermissionState::Denied
|
||||||
} else if self.is_granted(desc) {
|
} else if self.is_granted(desc) {
|
||||||
match allow_partial {
|
match allow_partial {
|
||||||
|
@ -454,13 +445,8 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
|
||||||
PermissionState::Denied
|
PermissionState::Denied
|
||||||
} else {
|
} else {
|
||||||
PermissionState::Prompt
|
PermissionState::Prompt
|
||||||
};
|
|
||||||
if state != PermissionState::Prompt {
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PermissionState::Prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_desc(
|
fn request_desc(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -512,9 +498,6 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
|
||||||
match desc {
|
match desc {
|
||||||
Some(desc) => {
|
Some(desc) => {
|
||||||
self.granted_list.retain(|v| !v.stronger_than(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 => {
|
None => {
|
||||||
self.granted_global = false;
|
self.granted_global = false;
|
||||||
|
@ -582,11 +565,7 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
|
||||||
) {
|
) {
|
||||||
match desc {
|
match desc {
|
||||||
Some(desc) => {
|
Some(desc) => {
|
||||||
let aliases = desc.aliases();
|
|
||||||
list.insert(desc);
|
list.insert(desc);
|
||||||
for alias in aliases {
|
|
||||||
list.insert(alias);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => *list_global = true,
|
None => *list_global = true,
|
||||||
}
|
}
|
||||||
|
@ -612,7 +591,7 @@ impl<T: Descriptor + Hash> UnaryPermission<T> {
|
||||||
ChildUnaryPermissionArg::GrantedList(granted_list) => {
|
ChildUnaryPermissionArg::GrantedList(granted_list) => {
|
||||||
let granted: Vec<T::Arg> =
|
let granted: Vec<T::Arg> =
|
||||||
granted_list.into_iter().map(From::from).collect();
|
granted_list.into_iter().map(From::from).collect();
|
||||||
perms.granted_list = T::parse(&Some(granted))?;
|
perms.granted_list = T::parse(Some(&granted))?;
|
||||||
if !perms
|
if !perms
|
||||||
.granted_list
|
.granted_list
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -649,7 +628,7 @@ impl Descriptor for ReadDescriptor {
|
||||||
perm.check_desc(Some(self), true, api_name, || None)
|
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)
|
parse_path_list(args, ReadDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -681,7 +660,7 @@ impl Descriptor for WriteDescriptor {
|
||||||
perm.check_desc(Some(self), true, api_name, || None)
|
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)
|
parse_path_list(args, WriteDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,7 +733,7 @@ impl Descriptor for NetDescriptor {
|
||||||
perm.check_desc(Some(self), false, api_name, || None)
|
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)
|
parse_net_list(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,7 +843,7 @@ impl Descriptor for EnvDescriptor {
|
||||||
perm.check_desc(Some(self), false, api_name, || None)
|
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)
|
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)]
|
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
pub enum RunDescriptor {
|
pub enum RunDescriptor {
|
||||||
/// Warning: You may want to construct with `RunDescriptor::from()` for case
|
/// Warning: You may want to construct with `RunDescriptor::from()` for case
|
||||||
|
@ -893,8 +877,26 @@ pub enum RunDescriptor {
|
||||||
Path(PathBuf),
|
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 {
|
impl Descriptor for RunDescriptor {
|
||||||
type Arg = String;
|
type Arg = RunDescriptorArg;
|
||||||
|
|
||||||
fn check_in_permission(
|
fn check_in_permission(
|
||||||
&self,
|
&self,
|
||||||
|
@ -905,7 +907,7 @@ impl Descriptor for RunDescriptor {
|
||||||
perm.check_desc(Some(self), false, api_name, || None)
|
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)
|
parse_run_list(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -916,16 +918,6 @@ impl Descriptor for RunDescriptor {
|
||||||
fn name(&self) -> Cow<str> {
|
fn name(&self) -> Cow<str> {
|
||||||
Cow::from(self.to_string())
|
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 {
|
impl From<String> for RunDescriptor {
|
||||||
|
@ -938,7 +930,10 @@ impl From<String> for RunDescriptor {
|
||||||
if is_path {
|
if is_path {
|
||||||
Self::Path(resolve_from_cwd(Path::new(&s)).unwrap())
|
Self::Path(resolve_from_cwd(Path::new(&s)).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Self::Name(s)
|
match which::which(&s) {
|
||||||
|
Ok(path) => Self::Path(path),
|
||||||
|
Err(_) => Self::Name(s),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -947,12 +942,8 @@ impl From<PathBuf> for RunDescriptor {
|
||||||
fn from(p: PathBuf) -> Self {
|
fn from(p: PathBuf) -> Self {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let p = PathBuf::from(p.to_string_lossy().to_string().to_lowercase());
|
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())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for RunDescriptor {
|
impl std::fmt::Display for RunDescriptor {
|
||||||
|
@ -988,7 +979,7 @@ impl Descriptor for SysDescriptor {
|
||||||
perm.check_desc(Some(self), false, api_name, || None)
|
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)
|
parse_sys_list(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1025,7 +1016,7 @@ impl Descriptor for FfiDescriptor {
|
||||||
perm.check_desc(Some(self), true, api_name, || None)
|
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)
|
parse_path_list(list, FfiDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1330,15 +1321,16 @@ impl UnaryPermission<RunDescriptor> {
|
||||||
|
|
||||||
pub fn check(
|
pub fn check(
|
||||||
&mut self,
|
&mut self,
|
||||||
cmd: &str,
|
cmd: &Path,
|
||||||
api_name: Option<&str>,
|
api_name: Option<&str>,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
debug_assert!(cmd.is_absolute());
|
||||||
skip_check_if_is_permission_fully_granted!(self);
|
skip_check_if_is_permission_fully_granted!(self);
|
||||||
self.check_desc(
|
self.check_desc(
|
||||||
Some(&RunDescriptor::from(cmd.to_string())),
|
Some(&RunDescriptor::Path(cmd.to_path_buf())),
|
||||||
false,
|
false,
|
||||||
api_name,
|
api_name,
|
||||||
|| Some(format!("\"{}\"", cmd)),
|
|| Some(format!("\"{}\"", cmd.display())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1346,6 +1338,21 @@ impl UnaryPermission<RunDescriptor> {
|
||||||
skip_check_if_is_permission_fully_granted!(self);
|
skip_check_if_is_permission_fully_granted!(self);
|
||||||
self.check_desc(None, false, api_name, || None)
|
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> {
|
impl UnaryPermission<FfiDescriptor> {
|
||||||
|
@ -1429,7 +1436,7 @@ pub struct PermissionsOptions {
|
||||||
pub deny_ffi: Option<Vec<PathBuf>>,
|
pub deny_ffi: Option<Vec<PathBuf>>,
|
||||||
pub allow_read: Option<Vec<PathBuf>>,
|
pub allow_read: Option<Vec<PathBuf>>,
|
||||||
pub deny_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 deny_run: Option<Vec<String>>,
|
||||||
pub allow_sys: Option<Vec<String>>,
|
pub allow_sys: Option<Vec<String>>,
|
||||||
pub deny_sys: Option<Vec<String>>,
|
pub deny_sys: Option<Vec<String>>,
|
||||||
|
@ -1440,8 +1447,8 @@ pub struct PermissionsOptions {
|
||||||
|
|
||||||
impl Permissions {
|
impl Permissions {
|
||||||
pub fn new_unary<T>(
|
pub fn new_unary<T>(
|
||||||
allow_list: &Option<Vec<T::Arg>>,
|
allow_list: Option<&[T::Arg]>,
|
||||||
deny_list: &Option<Vec<T::Arg>>,
|
deny_list: Option<&[T::Arg]>,
|
||||||
prompt: bool,
|
prompt: bool,
|
||||||
) -> Result<UnaryPermission<T>, AnyError>
|
) -> Result<UnaryPermission<T>, AnyError>
|
||||||
where
|
where
|
||||||
|
@ -1470,38 +1477,54 @@ impl Permissions {
|
||||||
pub fn from_options(opts: &PermissionsOptions) -> Result<Self, AnyError> {
|
pub fn from_options(opts: &PermissionsOptions) -> Result<Self, AnyError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
read: Permissions::new_unary(
|
read: Permissions::new_unary(
|
||||||
&opts.allow_read,
|
opts.allow_read.as_deref(),
|
||||||
&opts.deny_read,
|
opts.deny_read.as_deref(),
|
||||||
opts.prompt,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
write: Permissions::new_unary(
|
write: Permissions::new_unary(
|
||||||
&opts.allow_write,
|
opts.allow_write.as_deref(),
|
||||||
&opts.deny_write,
|
opts.deny_write.as_deref(),
|
||||||
opts.prompt,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
net: Permissions::new_unary(
|
net: Permissions::new_unary(
|
||||||
&opts.allow_net,
|
opts.allow_net.as_deref(),
|
||||||
&opts.deny_net,
|
opts.deny_net.as_deref(),
|
||||||
opts.prompt,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
env: Permissions::new_unary(
|
env: Permissions::new_unary(
|
||||||
&opts.allow_env,
|
opts.allow_env.as_deref(),
|
||||||
&opts.deny_env,
|
opts.deny_env.as_deref(),
|
||||||
opts.prompt,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
sys: Permissions::new_unary(
|
sys: Permissions::new_unary(
|
||||||
&opts.allow_sys,
|
opts.allow_sys.as_deref(),
|
||||||
&opts.deny_sys,
|
opts.deny_sys.as_deref(),
|
||||||
opts.prompt,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
run: Permissions::new_unary(
|
run: Permissions::new_unary(
|
||||||
&opts.allow_run,
|
opts
|
||||||
&opts.deny_run,
|
.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,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
ffi: Permissions::new_unary(
|
ffi: Permissions::new_unary(
|
||||||
&opts.allow_ffi,
|
opts.allow_ffi.as_deref(),
|
||||||
&opts.deny_ffi,
|
opts.deny_ffi.as_deref(),
|
||||||
opts.prompt,
|
opts.prompt,
|
||||||
)?,
|
)?,
|
||||||
all: Permissions::new_all(opts.allow_all),
|
all: Permissions::new_all(opts.allow_all),
|
||||||
|
@ -1534,13 +1557,13 @@ impl Permissions {
|
||||||
|
|
||||||
fn none(prompt: bool) -> Self {
|
fn none(prompt: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
read: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
read: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
write: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
write: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
net: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
net: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
env: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
env: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
sys: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
sys: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
run: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
run: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
ffi: Permissions::new_unary(&None, &None, prompt).unwrap(),
|
ffi: Permissions::new_unary(None, None, prompt).unwrap(),
|
||||||
all: Permissions::new_all(false),
|
all: Permissions::new_all(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1669,7 +1692,7 @@ impl PermissionsContainer {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn check_run(
|
pub fn check_run(
|
||||||
&mut self,
|
&mut self,
|
||||||
cmd: &str,
|
cmd: &Path,
|
||||||
api_name: &str,
|
api_name: &str,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
self.0.lock().run.check(cmd, Some(api_name))
|
self.0.lock().run.check(cmd, Some(api_name))
|
||||||
|
@ -1680,6 +1703,11 @@ impl PermissionsContainer {
|
||||||
self.0.lock().run.check_all(Some(api_name))
|
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)]
|
#[inline(always)]
|
||||||
pub fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError> {
|
pub fn check_sys(&self, kind: &str, api_name: &str) -> Result<(), AnyError> {
|
||||||
self.0.lock().sys.check(kind, Some(api_name))
|
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())
|
matches!(flag, Some(v) if v.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_net_list(
|
fn parse_net_list(
|
||||||
list: &Option<Vec<String>>,
|
list: Option<&[String]>,
|
||||||
) -> Result<HashSet<NetDescriptor>, AnyError> {
|
) -> Result<HashSet<NetDescriptor>, AnyError> {
|
||||||
if let Some(v) = list {
|
if let Some(v) = list {
|
||||||
v.iter()
|
v.iter()
|
||||||
|
@ -1888,7 +1916,7 @@ fn parse_net_list(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_env_list(
|
fn parse_env_list(
|
||||||
list: &Option<Vec<String>>,
|
list: Option<&[String]>,
|
||||||
) -> Result<HashSet<EnvDescriptor>, AnyError> {
|
) -> Result<HashSet<EnvDescriptor>, AnyError> {
|
||||||
if let Some(v) = list {
|
if let Some(v) = list {
|
||||||
v.iter()
|
v.iter()
|
||||||
|
@ -1906,7 +1934,7 @@ fn parse_env_list(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path_list<T: Descriptor + Hash>(
|
fn parse_path_list<T: Descriptor + Hash>(
|
||||||
list: &Option<Vec<PathBuf>>,
|
list: Option<&[PathBuf]>,
|
||||||
f: fn(PathBuf) -> T,
|
f: fn(PathBuf) -> T,
|
||||||
) -> Result<HashSet<T>, AnyError> {
|
) -> Result<HashSet<T>, AnyError> {
|
||||||
if let Some(v) = list {
|
if let Some(v) = list {
|
||||||
|
@ -1925,7 +1953,7 @@ fn parse_path_list<T: Descriptor + Hash>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_sys_list(
|
fn parse_sys_list(
|
||||||
list: &Option<Vec<String>>,
|
list: Option<&[String]>,
|
||||||
) -> Result<HashSet<SysDescriptor>, AnyError> {
|
) -> Result<HashSet<SysDescriptor>, AnyError> {
|
||||||
if let Some(v) = list {
|
if let Some(v) = list {
|
||||||
v.iter()
|
v.iter()
|
||||||
|
@ -1943,22 +1971,19 @@ fn parse_sys_list(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_run_list(
|
fn parse_run_list(
|
||||||
list: &Option<Vec<String>>,
|
list: Option<&[RunDescriptorArg]>,
|
||||||
) -> Result<HashSet<RunDescriptor>, AnyError> {
|
) -> Result<HashSet<RunDescriptor>, AnyError> {
|
||||||
let mut result = HashSet::new();
|
let Some(v) = list else {
|
||||||
if let Some(v) = list {
|
return Ok(HashSet::new());
|
||||||
for s in v {
|
};
|
||||||
if s.is_empty() {
|
Ok(
|
||||||
return Err(AnyError::msg("Empty path is not allowed"));
|
v.iter()
|
||||||
} else {
|
.map(|arg| match arg {
|
||||||
let desc = RunDescriptor::from(s.to_string());
|
RunDescriptorArg::Name(s) => RunDescriptor::Name(s.clone()),
|
||||||
let aliases = desc.aliases();
|
RunDescriptorArg::Path(l) => RunDescriptor::Path(l.clone()),
|
||||||
result.insert(desc);
|
})
|
||||||
result.extend(aliases);
|
.collect(),
|
||||||
}
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escalation_error() -> AnyError {
|
fn escalation_error() -> AnyError {
|
||||||
|
@ -2298,6 +2323,9 @@ mod tests {
|
||||||
macro_rules! svec {
|
macro_rules! svec {
|
||||||
($($x:expr),*) => (vec![$($x.to_string()),*]);
|
($($x:expr),*) => (vec![$($x.to_string()),*]);
|
||||||
}
|
}
|
||||||
|
macro_rules! sarr {
|
||||||
|
($($x:expr),*) => ([$($x.to_string()),*]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_paths() {
|
fn check_paths() {
|
||||||
|
@ -2678,94 +2706,88 @@ mod tests {
|
||||||
set_prompter(Box::new(TestPrompter));
|
set_prompter(Box::new(TestPrompter));
|
||||||
let perms1 = Permissions::allow_all();
|
let perms1 = Permissions::allow_all();
|
||||||
let perms2 = Permissions {
|
let perms2 = Permissions {
|
||||||
read: Permissions::new_unary(
|
read: Permissions::new_unary(Some(&[PathBuf::from("/foo")]), None, false)
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
|
||||||
&None,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
write: Permissions::new_unary(
|
write: Permissions::new_unary(
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
Some(&[PathBuf::from("/foo")]),
|
||||||
&None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ffi: Permissions::new_unary(
|
ffi: Permissions::new_unary(Some(&[PathBuf::from("/foo")]), None, false)
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
.unwrap(),
|
||||||
&None,
|
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,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.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),
|
all: Permissions::new_all(false),
|
||||||
};
|
};
|
||||||
let perms3 = Permissions {
|
let perms3 = Permissions {
|
||||||
read: Permissions::new_unary(
|
read: Permissions::new_unary(None, Some(&[PathBuf::from("/foo")]), false)
|
||||||
&None,
|
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
write: Permissions::new_unary(
|
write: Permissions::new_unary(
|
||||||
&None,
|
None,
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
Some(&[PathBuf::from("/foo")]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ffi: Permissions::new_unary(
|
ffi: Permissions::new_unary(None, Some(&[PathBuf::from("/foo")]), false)
|
||||||
&None,
|
.unwrap(),
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
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,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.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),
|
all: Permissions::new_all(false),
|
||||||
};
|
};
|
||||||
let perms4 = Permissions {
|
let perms4 = Permissions {
|
||||||
read: Permissions::new_unary(
|
read: Permissions::new_unary(
|
||||||
&Some(vec![]),
|
Some(&[]),
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
Some(&[PathBuf::from("/foo")]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
write: Permissions::new_unary(
|
write: Permissions::new_unary(
|
||||||
&Some(vec![]),
|
Some(&[]),
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
Some(&[PathBuf::from("/foo")]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ffi: Permissions::new_unary(
|
ffi: Permissions::new_unary(
|
||||||
&Some(vec![]),
|
Some(&[]),
|
||||||
&Some(vec![PathBuf::from("/foo")]),
|
Some(&[PathBuf::from("/foo")]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
net: Permissions::new_unary(
|
net: Permissions::new_unary(
|
||||||
&Some(vec![]),
|
Some(&[]),
|
||||||
&Some(svec!["127.0.0.1:8000"]),
|
Some(&sarr!["127.0.0.1:8000"]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
env: Permissions::new_unary(&Some(vec![]), &Some(svec!["HOME"]), false)
|
env: Permissions::new_unary(Some(&[]), Some(&sarr!["HOME"]), false)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
sys: Permissions::new_unary(
|
sys: Permissions::new_unary(Some(&[]), Some(&sarr!["hostname"]), false)
|
||||||
&Some(vec![]),
|
.unwrap(),
|
||||||
&Some(svec!["hostname"]),
|
run: Permissions::new_unary(
|
||||||
|
Some(&[]),
|
||||||
|
Some(&["deno".to_string().into()]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
run: Permissions::new_unary(&Some(vec![]), &Some(svec!["deno"]), false)
|
|
||||||
.unwrap(),
|
|
||||||
all: Permissions::new_all(false),
|
all: Permissions::new_all(false),
|
||||||
};
|
};
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -2894,33 +2916,38 @@ mod tests {
|
||||||
set_prompter(Box::new(TestPrompter));
|
set_prompter(Box::new(TestPrompter));
|
||||||
let mut perms = Permissions {
|
let mut perms = Permissions {
|
||||||
read: Permissions::new_unary(
|
read: Permissions::new_unary(
|
||||||
&Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
|
Some(&[PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
|
||||||
&None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
write: Permissions::new_unary(
|
write: Permissions::new_unary(
|
||||||
&Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
|
Some(&[PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
|
||||||
&None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ffi: Permissions::new_unary(
|
ffi: Permissions::new_unary(
|
||||||
&Some(vec![PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
|
Some(&[PathBuf::from("/foo"), PathBuf::from("/foo/baz")]),
|
||||||
&None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
net: Permissions::new_unary(
|
net: Permissions::new_unary(
|
||||||
&Some(svec!["127.0.0.1", "127.0.0.1:8000"]),
|
Some(&sarr!["127.0.0.1", "127.0.0.1:8000"]),
|
||||||
&None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
env: Permissions::new_unary(&Some(svec!["HOME"]), &None, false).unwrap(),
|
env: Permissions::new_unary(Some(&sarr!["HOME"]), None, false).unwrap(),
|
||||||
sys: Permissions::new_unary(&Some(svec!["hostname"]), &None, false)
|
sys: Permissions::new_unary(Some(&sarr!["hostname"]), None, false)
|
||||||
|
.unwrap(),
|
||||||
|
run: Permissions::new_unary(
|
||||||
|
Some(&["deno".to_string().into()]),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
run: Permissions::new_unary(&Some(svec!["deno"]), &None, false).unwrap(),
|
|
||||||
all: Permissions::new_all(false),
|
all: Permissions::new_all(false),
|
||||||
};
|
};
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -3006,11 +3033,13 @@ mod tests {
|
||||||
.check(&NetDescriptor("deno.land".parse().unwrap(), None), None)
|
.check(&NetDescriptor("deno.land".parse().unwrap(), None), None)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
let cwd = std::env::current_dir().unwrap();
|
||||||
prompt_value.set(true);
|
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);
|
prompt_value.set(false);
|
||||||
assert!(perms.run.check("cat", None).is_ok());
|
assert!(perms.run.check(&cwd.join("cat"), None).is_ok());
|
||||||
assert!(perms.run.check("ls", None).is_err());
|
assert!(perms.run.check(&cwd.join("ls"), None).is_err());
|
||||||
|
|
||||||
prompt_value.set(true);
|
prompt_value.set(true);
|
||||||
assert!(perms.env.check("HOME", None).is_ok());
|
assert!(perms.env.check("HOME", None).is_ok());
|
||||||
|
@ -3102,12 +3131,14 @@ mod tests {
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
prompt_value.set(false);
|
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);
|
prompt_value.set(true);
|
||||||
assert!(perms.run.check("cat", None).is_err());
|
assert!(perms.run.check(&cwd.join("cat"), None).is_err());
|
||||||
assert!(perms.run.check("ls", None).is_ok());
|
assert!(perms.run.check(&cwd.join("ls"), None).is_ok());
|
||||||
prompt_value.set(false);
|
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);
|
prompt_value.set(false);
|
||||||
assert!(perms.env.check("HOME", None).is_err());
|
assert!(perms.env.check("HOME", None).is_err());
|
||||||
|
@ -3134,7 +3165,7 @@ mod tests {
|
||||||
let mut perms = Permissions::allow_all();
|
let mut perms = Permissions::allow_all();
|
||||||
perms.env = UnaryPermission {
|
perms.env = UnaryPermission {
|
||||||
granted_global: false,
|
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);
|
prompt_value.set(true);
|
||||||
|
@ -3150,14 +3181,14 @@ mod tests {
|
||||||
fn test_check_partial_denied() {
|
fn test_check_partial_denied() {
|
||||||
let mut perms = Permissions {
|
let mut perms = Permissions {
|
||||||
read: Permissions::new_unary(
|
read: Permissions::new_unary(
|
||||||
&Some(vec![]),
|
Some(&[]),
|
||||||
&Some(vec![PathBuf::from("/foo/bar")]),
|
Some(&[PathBuf::from("/foo/bar")]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
write: Permissions::new_unary(
|
write: Permissions::new_unary(
|
||||||
&Some(vec![]),
|
Some(&[]),
|
||||||
&Some(vec![PathBuf::from("/foo/bar")]),
|
Some(&[PathBuf::from("/foo/bar")]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -3175,8 +3206,8 @@ mod tests {
|
||||||
fn test_net_fully_qualified_domain_name() {
|
fn test_net_fully_qualified_domain_name() {
|
||||||
let mut perms = Permissions {
|
let mut perms = Permissions {
|
||||||
net: Permissions::new_unary(
|
net: Permissions::new_unary(
|
||||||
&Some(vec!["allowed.domain".to_string(), "1.1.1.1".to_string()]),
|
Some(&["allowed.domain".to_string(), "1.1.1.1".to_string()]),
|
||||||
&Some(vec!["denied.domain".to_string(), "2.2.2.2".to_string()]),
|
Some(&["denied.domain".to_string(), "2.2.2.2".to_string()]),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -3341,8 +3372,8 @@ mod tests {
|
||||||
fn test_create_child_permissions() {
|
fn test_create_child_permissions() {
|
||||||
set_prompter(Box::new(TestPrompter));
|
set_prompter(Box::new(TestPrompter));
|
||||||
let mut main_perms = Permissions {
|
let mut main_perms = Permissions {
|
||||||
env: Permissions::new_unary(&Some(vec![]), &None, false).unwrap(),
|
env: Permissions::new_unary(Some(&[]), None, false).unwrap(),
|
||||||
net: Permissions::new_unary(&Some(svec!["foo", "bar"]), &None, false)
|
net: Permissions::new_unary(Some(&sarr!["foo", "bar"]), None, false)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
..Permissions::none_without_prompt()
|
..Permissions::none_without_prompt()
|
||||||
};
|
};
|
||||||
|
@ -3358,8 +3389,8 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Permissions {
|
Permissions {
|
||||||
env: Permissions::new_unary(&Some(vec![]), &None, false).unwrap(),
|
env: Permissions::new_unary(Some(&[]), None, false).unwrap(),
|
||||||
net: Permissions::new_unary(&Some(svec!["foo"]), &None, false).unwrap(),
|
net: Permissions::new_unary(Some(&sarr!["foo"]), None, false).unwrap(),
|
||||||
..Permissions::none_without_prompt()
|
..Permissions::none_without_prompt()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -3445,20 +3476,20 @@ mod tests {
|
||||||
set_prompter(Box::new(TestPrompter));
|
set_prompter(Box::new(TestPrompter));
|
||||||
|
|
||||||
assert!(Permissions::new_unary::<ReadDescriptor>(
|
assert!(Permissions::new_unary::<ReadDescriptor>(
|
||||||
&Some(vec![Default::default()]),
|
Some(&[Default::default()]),
|
||||||
&None,
|
None,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(Permissions::new_unary::<EnvDescriptor>(
|
assert!(Permissions::new_unary::<EnvDescriptor>(
|
||||||
&Some(vec![Default::default()]),
|
Some(&[Default::default()]),
|
||||||
&None,
|
None,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(Permissions::new_unary::<NetDescriptor>(
|
assert!(Permissions::new_unary::<NetDescriptor>(
|
||||||
&Some(vec![Default::default()]),
|
Some(&[Default::default()]),
|
||||||
&None,
|
None,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
|
@ -3683,11 +3683,6 @@ itest!(followup_dyn_import_resolved {
|
||||||
output: "run/followup_dyn_import_resolves/main.ts.out",
|
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 {
|
itest!(unhandled_rejection {
|
||||||
args: "run --check run/unhandled_rejection.ts",
|
args: "run --check run/unhandled_rejection.ts",
|
||||||
output: "run/unhandled_rejection.ts.out",
|
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| {
|
// 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.write_line_raw(r#"const boldANSI = "\u001b[1m";"#);
|
||||||
console.expect("undefined");
|
console.expect("undefined");
|
||||||
console.write_line_raw(r#"const unboldANSI = "\u001b[22m";"#);
|
console.write_line_raw(r#"const unboldANSI = "\u001b[22m";"#);
|
||||||
console.expect("undefined");
|
console.expect("undefined");
|
||||||
console.write_line_raw(
|
console.write_line_raw(
|
||||||
r#"new Deno.Command(`${boldANSI}cat${unboldANSI}`).spawn();"#,
|
r#"Deno.writeTextFileSync(`${boldANSI}cat${unboldANSI}`, "");"#,
|
||||||
);
|
);
|
||||||
console.expect("\u{250f} \u{26a0}\u{fe0f} Deno requests run access to \"\\u{1b}[1mcat\\u{1b}[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 \"");
|
||||||
|
console.expect("\\u{1b}[1mcat\\u{1b}[22m\"."); // ensure escaped
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itest!(node_builtin_modules_ts {
|
itest!(node_builtin_modules_ts {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
{
|
{
|
||||||
"tempDir": true,
|
"tempDir": true,
|
||||||
|
"envs": {
|
||||||
|
"DYLD_FALLBACK_LIBRARY_PATH": "",
|
||||||
|
"LD_LIBRARY_PATH": ""
|
||||||
|
},
|
||||||
"steps": [{
|
"steps": [{
|
||||||
"if": "unix",
|
"if": "unix",
|
||||||
"args": "compile --output main main.ts",
|
"args": "compile --output main main.ts",
|
||||||
|
|
|
@ -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]
|
[WILDCARD]
|
|
@ -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
|
Download http://localhost:4260/@denotest/node-addon-implicit-node-gyp/1.0.0.tgz
|
||||||
Initialize @denotest/node-addon-implicit-node-gyp@1.0.0
|
Initialize @denotest/node-addon-implicit-node-gyp@1.0.0
|
||||||
[UNORDERED_END]
|
[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]
|
[WILDCARD]
|
||||||
error: script 'install' in '@denotest/node-addon-implicit-node-gyp@1.0.0' failed with exit code 1
|
error: script 'install' in '@denotest/node-addon-implicit-node-gyp@1.0.0' failed with exit code 1
|
||||||
|
|
10
tests/specs/permission/path_not_permitted/__test__.jsonc
Normal file
10
tests/specs/permission/path_not_permitted/__test__.jsonc
Normal 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"
|
||||||
|
}
|
11
tests/specs/permission/path_not_permitted/main.out
Normal file
11
tests/specs/permission/path_not_permitted/main.out
Normal 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"
|
||||||
|
}
|
18
tests/specs/permission/path_not_permitted/main.ts
Normal file
18
tests/specs/permission/path_not_permitted/main.ts
Normal 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();
|
34
tests/specs/permission/path_not_permitted/sub.ts
Normal file
34
tests/specs/permission/path_not_permitted/sub.ts
Normal 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);
|
||||||
|
}
|
5
tests/specs/permission/write_allow_binary/__test__.jsonc
Normal file
5
tests/specs/permission/write_allow_binary/__test__.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"tempDir": true,
|
||||||
|
"args": "run -A main.ts",
|
||||||
|
"output": "main.out"
|
||||||
|
}
|
6
tests/specs/permission/write_allow_binary/main.out
Normal file
6
tests/specs/permission/write_allow_binary/main.out
Normal 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
|
14
tests/specs/permission/write_allow_binary/main.ts
Normal file
14
tests/specs/permission/write_allow_binary/main.ts
Normal 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");
|
3
tests/specs/permission/write_allow_binary/sub.ts
Normal file
3
tests/specs/permission/write_allow_binary/sub.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const binaryName = Deno.build.os === "windows" ? "binary.exe" : "binary";
|
||||||
|
|
||||||
|
Deno.writeTextFileSync(binaryName, "");
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"args": "run --quiet -A main.ts",
|
||||||
|
"output": "main.out",
|
||||||
|
"envs": {
|
||||||
|
"DYLD_FALLBACK_LIBRARY_PATH": "",
|
||||||
|
"LD_LIBRARY_PATH": ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: "granted", onchange: null }
|
PermissionStatus { state: "granted", onchange: null }
|
||||||
PermissionStatus { state: "prompt", onchange: null }
|
PermissionStatus { state: "prompt", onchange: null }
|
||||||
PermissionStatus { state: "granted", 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 }
|
|
@ -14,13 +14,13 @@ const execPathParent = execPath.replace(/[/\\][^/\\]+$/, "");
|
||||||
|
|
||||||
const testUrl = `data:application/typescript;base64,${
|
const testUrl = `data:application/typescript;base64,${
|
||||||
btoa(`
|
btoa(`
|
||||||
console.log(await Deno.permissions.query({ name: "run", command: "deno" }));
|
console.error(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: "${
|
||||||
execPath.replaceAll("\\", "\\\\")
|
execPath.replaceAll("\\", "\\\\")
|
||||||
}" }));
|
}" }));
|
||||||
Deno.env.set("PATH", "");
|
Deno.env.set("PATH", "");
|
||||||
console.log(await Deno.permissions.query({ name: "run", command: "deno" }));
|
console.error(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: "${
|
||||||
execPath.replaceAll("\\", "\\\\")
|
execPath.replaceAll("\\", "\\\\")
|
||||||
}" }));
|
}" }));
|
||||||
`)
|
`)
|
||||||
|
@ -29,38 +29,39 @@ const testUrl = `data:application/typescript;base64,${
|
||||||
const process1 = await new Deno.Command(Deno.execPath(), {
|
const process1 = await new Deno.Command(Deno.execPath(), {
|
||||||
args: [
|
args: [
|
||||||
"run",
|
"run",
|
||||||
"--quiet",
|
|
||||||
"--allow-env",
|
"--allow-env",
|
||||||
"--allow-run=deno",
|
"--allow-run=deno",
|
||||||
testUrl,
|
testUrl,
|
||||||
],
|
],
|
||||||
stderr: "null",
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
env: { "PATH": execPathParent },
|
env: { "PATH": execPathParent },
|
||||||
}).output();
|
}).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: [
|
args: [
|
||||||
"run",
|
"run",
|
||||||
"--quiet",
|
|
||||||
"--allow-env",
|
"--allow-env",
|
||||||
"--allow-run=deno",
|
"--allow-run=deno",
|
||||||
testUrl,
|
testUrl,
|
||||||
],
|
],
|
||||||
stderr: "null",
|
stderr: "inherit",
|
||||||
|
stdout: "inherit",
|
||||||
env: { "PATH": "" },
|
env: { "PATH": "" },
|
||||||
}).output();
|
}).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: [
|
args: [
|
||||||
"run",
|
"run",
|
||||||
"--quiet",
|
|
||||||
"--allow-env",
|
"--allow-env",
|
||||||
`--allow-run=${execPath}`,
|
`--allow-run=${execPath}`,
|
||||||
testUrl,
|
testUrl,
|
||||||
],
|
],
|
||||||
stderr: "null",
|
stderr: "inherit",
|
||||||
|
stdout: "inherit",
|
||||||
env: { "PATH": execPathParent },
|
env: { "PATH": execPathParent },
|
||||||
}).output();
|
}).output();
|
||||||
console.log(new TextDecoder().decode(process3.stdout));
|
|
|
@ -7,13 +7,11 @@
|
||||||
"tests": {
|
"tests": {
|
||||||
"env_arg": {
|
"env_arg": {
|
||||||
"args": "run --allow-run=echo env_arg.ts",
|
"args": "run --allow-run=echo env_arg.ts",
|
||||||
"output": "env_arg.out",
|
"output": "env_arg.out"
|
||||||
"exitCode": 1
|
|
||||||
},
|
},
|
||||||
"set_with_allow_env": {
|
"set_with_allow_env": {
|
||||||
"args": "run --allow-run=echo --allow-env set_with_allow_env.ts",
|
"args": "run --allow-run=echo --allow-env set_with_allow_env.ts",
|
||||||
"output": "set_with_allow_env.out",
|
"output": "set_with_allow_env.out"
|
||||||
"exitCode": 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
error: Uncaught (in promise) PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
|
PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
|
||||||
}).spawn();
|
[WILDCARD]
|
||||||
^
|
name: "PermissionDenied"
|
||||||
at [WILDCARD]
|
}
|
||||||
|
PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
|
||||||
|
[WILDCARD]
|
||||||
|
name: "PermissionDenied"
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,20 @@
|
||||||
const output = new Deno.Command("echo", {
|
try {
|
||||||
|
new Deno.Command("echo", {
|
||||||
env: {
|
env: {
|
||||||
"LD_PRELOAD": "./libpreload.so",
|
"LD_PRELOAD": "./libpreload.so",
|
||||||
},
|
},
|
||||||
}).spawn();
|
}).spawn();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Deno.run({
|
||||||
|
cmd: ["echo"],
|
||||||
|
env: {
|
||||||
|
"LD_PRELOAD": "./libpreload.so",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
error: Uncaught (in promise) PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
|
PermissionDenied: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
|
||||||
const output = new Deno.Command("echo").spawn();
|
[WILDCARD]
|
||||||
^
|
name: "PermissionDenied"
|
||||||
at [WILDCARD]
|
}
|
||||||
|
PermissionDenied: Requires --allow-all permissions to spawn subprocess with DYLD_FALLBACK_LIBRARY_PATH, LD_PRELOAD environment variables.
|
||||||
|
[WILDCARD]
|
||||||
|
name: "PermissionDenied"
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
Deno.env.set("LD_PRELOAD", "./libpreload.so");
|
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);
|
||||||
|
}
|
||||||
|
|
2
tests/testdata/run/089_run_allow_list.ts.out
vendored
2
tests/testdata/run/089_run_allow_list.ts.out
vendored
|
@ -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]
|
[WILDCARD]
|
||||||
true
|
true
|
||||||
|
|
|
@ -611,6 +611,6 @@ Deno.test(
|
||||||
p.close();
|
p.close();
|
||||||
p.stdout.close();
|
p.stdout.close();
|
||||||
assertStrictEquals(code, 1);
|
assertStrictEquals(code, 1);
|
||||||
assertStringIncludes(stderr, "Failed getting cwd.");
|
assertStringIncludes(stderr, "failed resolving cwd:");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -221,7 +221,7 @@ async function ensureNoNewITests() {
|
||||||
"pm_tests.rs": 0,
|
"pm_tests.rs": 0,
|
||||||
"publish_tests.rs": 0,
|
"publish_tests.rs": 0,
|
||||||
"repl_tests.rs": 0,
|
"repl_tests.rs": 0,
|
||||||
"run_tests.rs": 351,
|
"run_tests.rs": 350,
|
||||||
"shared_library_tests.rs": 0,
|
"shared_library_tests.rs": 0,
|
||||||
"task_tests.rs": 30,
|
"task_tests.rs": 30,
|
||||||
"test_tests.rs": 75,
|
"test_tests.rs": 75,
|
||||||
|
|
Loading…
Reference in a new issue