feat(permissions): add "--deny-*" flags (#19070)

This commit adds new "--deny-*" permission flags. These are complimentary to
"--allow-*" flags.

These flags can be used to restrict access to certain resources, even if they
were granted using "--allow-*" flags or the "--allow-all" ("-A") flag.

Eg. specifying "--allow-read --deny-read" will result in a permission error,
while "--allow-read --deny-read=/etc" will allow read access to all FS but the
"/etc" directory.

Runtime permissions APIs ("Deno.permissions") were adjusted as well, mainly
by adding, a new "PermissionStatus.partial" field. This field denotes that
while permission might be granted to requested resource, it's only partial (ie.
a "--deny-*" flag was specified that excludes some of the requested resources).
Eg. specifying "--allow-read=foo/ --deny-read=foo/bar" and then querying for
permissions like "Deno.permissions.query({ name: "read", path: "foo/" })"
will return "PermissionStatus { state: "granted", onchange: null, partial: true }",
denoting that some of the subpaths don't have read access.

Closes #18804.

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
Co-authored-by: Nayeem Rahman <nayeemrmn99@gmail.com>
This commit is contained in:
Asher Gomez 2023-08-03 21:19:19 +10:00 committed by GitHub
parent db287e216d
commit 6fb7e8d93b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1805 additions and 1456 deletions

View file

@ -24,7 +24,7 @@ use std::str::FromStr;
use crate::util::fs::canonicalize_path; use crate::util::fs::canonicalize_path;
use super::flags_allow_net; use super::flags_net;
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileFlags { pub struct FileFlags {
@ -364,13 +364,21 @@ pub struct Flags {
pub allow_all: bool, pub allow_all: bool,
pub allow_env: Option<Vec<String>>, pub allow_env: Option<Vec<String>>,
pub deny_env: Option<Vec<String>>,
pub allow_hrtime: bool, pub allow_hrtime: bool,
pub deny_hrtime: bool,
pub allow_net: Option<Vec<String>>, pub allow_net: Option<Vec<String>>,
pub deny_net: Option<Vec<String>>,
pub allow_ffi: Option<Vec<PathBuf>>, pub allow_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 allow_run: Option<Vec<String>>, pub allow_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 allow_write: Option<Vec<PathBuf>>, pub allow_write: Option<Vec<PathBuf>>,
pub deny_write: Option<Vec<PathBuf>>,
pub ca_stores: Option<Vec<String>>, pub ca_stores: Option<Vec<String>>,
pub ca_data: Option<CaData>, pub ca_data: Option<CaData>,
pub cache_blocklist: Vec<String>, pub cache_blocklist: Vec<String>,
@ -434,6 +442,17 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_read {
Some(read_denylist) if read_denylist.is_empty() => {
args.push("--deny-read".to_string());
}
Some(read_denylist) => {
let s = format!("--deny-read={}", join_paths(read_denylist, ","));
args.push(s);
}
_ => {}
}
match &self.allow_write { match &self.allow_write {
Some(write_allowlist) if write_allowlist.is_empty() => { Some(write_allowlist) if write_allowlist.is_empty() => {
args.push("--allow-write".to_string()); args.push("--allow-write".to_string());
@ -445,6 +464,17 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_write {
Some(write_denylist) if write_denylist.is_empty() => {
args.push("--deny-write".to_string());
}
Some(write_denylist) => {
let s = format!("--deny-write={}", join_paths(write_denylist, ","));
args.push(s);
}
_ => {}
}
match &self.allow_net { match &self.allow_net {
Some(net_allowlist) if net_allowlist.is_empty() => { Some(net_allowlist) if net_allowlist.is_empty() => {
args.push("--allow-net".to_string()); args.push("--allow-net".to_string());
@ -456,6 +486,17 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_net {
Some(net_denylist) if net_denylist.is_empty() => {
args.push("--deny-net".to_string());
}
Some(net_denylist) => {
let s = format!("--deny-net={}", net_denylist.join(","));
args.push(s);
}
_ => {}
}
match &self.unsafely_ignore_certificate_errors { match &self.unsafely_ignore_certificate_errors {
Some(ic_allowlist) if ic_allowlist.is_empty() => { Some(ic_allowlist) if ic_allowlist.is_empty() => {
args.push("--unsafely-ignore-certificate-errors".to_string()); args.push("--unsafely-ignore-certificate-errors".to_string());
@ -481,6 +522,17 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_env {
Some(env_denylist) if env_denylist.is_empty() => {
args.push("--deny-env".to_string());
}
Some(env_denylist) => {
let s = format!("--deny-env={}", env_denylist.join(","));
args.push(s);
}
_ => {}
}
match &self.allow_run { match &self.allow_run {
Some(run_allowlist) if run_allowlist.is_empty() => { Some(run_allowlist) if run_allowlist.is_empty() => {
args.push("--allow-run".to_string()); args.push("--allow-run".to_string());
@ -492,6 +544,17 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_run {
Some(run_denylist) if run_denylist.is_empty() => {
args.push("--deny-run".to_string());
}
Some(run_denylist) => {
let s = format!("--deny-run={}", run_denylist.join(","));
args.push(s);
}
_ => {}
}
match &self.allow_sys { match &self.allow_sys {
Some(sys_allowlist) if sys_allowlist.is_empty() => { Some(sys_allowlist) if sys_allowlist.is_empty() => {
args.push("--allow-sys".to_string()); args.push("--allow-sys".to_string());
@ -503,6 +566,17 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_sys {
Some(sys_denylist) if sys_denylist.is_empty() => {
args.push("--deny-sys".to_string());
}
Some(sys_denylist) => {
let s = format!("--deny-sys={}", sys_denylist.join(","));
args.push(s)
}
_ => {}
}
match &self.allow_ffi { match &self.allow_ffi {
Some(ffi_allowlist) if ffi_allowlist.is_empty() => { Some(ffi_allowlist) if ffi_allowlist.is_empty() => {
args.push("--allow-ffi".to_string()); args.push("--allow-ffi".to_string());
@ -514,10 +588,25 @@ impl Flags {
_ => {} _ => {}
} }
match &self.deny_ffi {
Some(ffi_denylist) if ffi_denylist.is_empty() => {
args.push("--deny-ffi".to_string());
}
Some(ffi_denylist) => {
let s = format!("--deny-ffi={}", join_paths(ffi_denylist, ","));
args.push(s);
}
_ => {}
}
if self.allow_hrtime { if self.allow_hrtime {
args.push("--allow-hrtime".to_string()); args.push("--allow-hrtime".to_string());
} }
if self.deny_hrtime {
args.push("--deny-hrtime".to_string());
}
args args
} }
@ -607,26 +696,42 @@ impl Flags {
pub fn has_permission(&self) -> bool { pub fn has_permission(&self) -> bool {
self.allow_all self.allow_all
|| self.allow_hrtime || self.allow_hrtime
|| self.deny_hrtime
|| self.allow_env.is_some() || self.allow_env.is_some()
|| self.deny_env.is_some()
|| self.allow_ffi.is_some() || self.allow_ffi.is_some()
|| self.deny_ffi.is_some()
|| self.allow_net.is_some() || self.allow_net.is_some()
|| self.deny_net.is_some()
|| self.allow_read.is_some() || self.allow_read.is_some()
|| self.deny_read.is_some()
|| self.allow_run.is_some() || self.allow_run.is_some()
|| self.deny_run.is_some()
|| self.allow_sys.is_some() || self.allow_sys.is_some()
|| self.deny_sys.is_some()
|| self.allow_write.is_some() || self.allow_write.is_some()
|| self.deny_write.is_some()
} }
pub fn has_permission_in_argv(&self) -> bool { pub fn has_permission_in_argv(&self) -> bool {
self.argv.iter().any(|arg| { self.argv.iter().any(|arg| {
arg == "--allow-all" arg == "--allow-all"
|| arg == "--allow-hrtime" || arg == "--allow-hrtime"
|| arg == "--deny-hrtime"
|| arg.starts_with("--allow-env") || arg.starts_with("--allow-env")
|| arg.starts_with("--deny-env")
|| arg.starts_with("--allow-ffi") || arg.starts_with("--allow-ffi")
|| arg.starts_with("--deny-ffi")
|| arg.starts_with("--allow-net") || arg.starts_with("--allow-net")
|| arg.starts_with("--deny-net")
|| arg.starts_with("--allow-read") || arg.starts_with("--allow-read")
|| arg.starts_with("--deny-read")
|| arg.starts_with("--allow-run") || arg.starts_with("--allow-run")
|| arg.starts_with("--deny-run")
|| arg.starts_with("--allow-sys") || arg.starts_with("--allow-sys")
|| arg.starts_with("--deny-sys")
|| arg.starts_with("--allow-write") || arg.starts_with("--allow-write")
|| arg.starts_with("--deny-write")
}) })
} }
} }
@ -2037,6 +2142,16 @@ static ALLOW_READ_HELP: &str = concat!(
" --allow-read=\"/etc,/var/log.txt\"" " --allow-read=\"/etc,/var/log.txt\""
); );
static DENY_READ_HELP: &str = concat!(
"Deny file system read access. Optionally specify denied paths.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-read\n",
" --deny-read=\"/etc,/var/log.txt\""
);
static ALLOW_WRITE_HELP: &str = concat!( static ALLOW_WRITE_HELP: &str = concat!(
"Allow file system write access. Optionally specify allowed paths.\n", "Allow file system write access. Optionally specify allowed paths.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2047,6 +2162,16 @@ static ALLOW_WRITE_HELP: &str = concat!(
" --allow-write=\"/etc,/var/log.txt\"" " --allow-write=\"/etc,/var/log.txt\""
); );
static DENY_WRITE_HELP: &str = concat!(
"Deny file system write access. Optionally specify denied paths.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-write\n",
" --deny-write=\"/etc,/var/log.txt\""
);
static ALLOW_NET_HELP: &str = concat!( static ALLOW_NET_HELP: &str = concat!(
"Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary.\n", "Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2057,6 +2182,16 @@ static ALLOW_NET_HELP: &str = concat!(
" --allow-net=\"localhost:8080,deno.land\"" " --allow-net=\"localhost:8080,deno.land\""
); );
static DENY_NET_HELP: &str = concat!(
"Deny network access. Optionally specify denied IP addresses and host names, with ports as necessary.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-net\n",
" --deny-net=\"localhost:8080,deno.land\""
);
static ALLOW_ENV_HELP: &str = concat!( static ALLOW_ENV_HELP: &str = concat!(
"Allow access to system environment information. Optionally specify accessible environment variables.\n", "Allow access to system environment information. Optionally specify accessible environment variables.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2067,6 +2202,16 @@ static ALLOW_ENV_HELP: &str = concat!(
" --allow-env=\"PORT,HOME,PATH\"" " --allow-env=\"PORT,HOME,PATH\""
); );
static DENY_ENV_HELP: &str = concat!(
"Deny access to system environment information. Optionally specify accessible environment variables.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-env\n",
" --deny-env=\"PORT,HOME,PATH\""
);
static ALLOW_SYS_HELP: &str = concat!( static ALLOW_SYS_HELP: &str = concat!(
"Allow access to OS information. Optionally allow specific APIs by function name.\n", "Allow access to OS information. Optionally allow specific APIs by function name.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2077,6 +2222,16 @@ static ALLOW_SYS_HELP: &str = concat!(
" --allow-sys=\"systemMemoryInfo,osRelease\"" " --allow-sys=\"systemMemoryInfo,osRelease\""
); );
static DENY_SYS_HELP: &str = concat!(
"Deny access to OS information. Optionally deny specific APIs by function name.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-sys\n",
" --deny-sys=\"systemMemoryInfo,osRelease\""
);
static ALLOW_RUN_HELP: &str = concat!( static ALLOW_RUN_HELP: &str = concat!(
"Allow running subprocesses. Optionally specify allowed runnable program names.\n", "Allow running subprocesses. Optionally specify allowed runnable program names.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2087,6 +2242,16 @@ static ALLOW_RUN_HELP: &str = concat!(
" --allow-run=\"whoami,ps\"" " --allow-run=\"whoami,ps\""
); );
static DENY_RUN_HELP: &str = concat!(
"Deny running subprocesses. Optionally specify denied runnable program names.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-run\n",
" --deny-run=\"whoami,ps\""
);
static ALLOW_FFI_HELP: &str = concat!( static ALLOW_FFI_HELP: &str = concat!(
"(Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files.\n", "(Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2097,6 +2262,16 @@ static ALLOW_FFI_HELP: &str = concat!(
" --allow-ffi=\"./libfoo.so\"" " --allow-ffi=\"./libfoo.so\""
); );
static DENY_FFI_HELP: &str = concat!(
"(Unstable) Deny loading dynamic libraries. Optionally specify denied directories or files.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n",
"Examples:\n",
" --deny-ffi\n",
" --deny-ffi=\"./libfoo.so\""
);
static ALLOW_HRTIME_HELP: &str = concat!( static ALLOW_HRTIME_HELP: &str = concat!(
"Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting.\n", "Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting.\n",
"Docs: https://deno.land/manual@v", "Docs: https://deno.land/manual@v",
@ -2104,6 +2279,13 @@ static ALLOW_HRTIME_HELP: &str = concat!(
"/basics/permissions\n" "/basics/permissions\n"
); );
static DENY_HRTIME_HELP: &str = concat!(
"Deny high-resolution time measurement. Note: this can prevent timing attacks and fingerprinting.\n",
"Docs: https://deno.land/manual@v",
env!("CARGO_PKG_VERSION"),
"/basics/permissions\n"
);
static ALLOW_ALL_HELP: &str = concat!( static ALLOW_ALL_HELP: &str = concat!(
"Allow all permissions. Learn more about permissions in Deno:\n", "Allow all permissions. Learn more about permissions in Deno:\n",
"https://deno.land/manual@v", "https://deno.land/manual@v",
@ -2124,6 +2306,17 @@ fn permission_args(app: Command) -> Command {
.value_parser(value_parser!(PathBuf)) .value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::AnyPath), .value_hint(ValueHint::AnyPath),
) )
.arg(
Arg::new("deny-read")
.long("deny-read")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("PATH")
.help(DENY_READ_HELP)
.value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::AnyPath),
)
.arg( .arg(
Arg::new("allow-write") Arg::new("allow-write")
.long("allow-write") .long("allow-write")
@ -2135,6 +2328,17 @@ fn permission_args(app: Command) -> Command {
.value_parser(value_parser!(PathBuf)) .value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::AnyPath), .value_hint(ValueHint::AnyPath),
) )
.arg(
Arg::new("deny-write")
.long("deny-write")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("PATH")
.help(DENY_WRITE_HELP)
.value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::AnyPath),
)
.arg( .arg(
Arg::new("allow-net") Arg::new("allow-net")
.long("allow-net") .long("allow-net")
@ -2143,7 +2347,17 @@ fn permission_args(app: Command) -> Command {
.require_equals(true) .require_equals(true)
.value_name("IP_OR_HOSTNAME") .value_name("IP_OR_HOSTNAME")
.help(ALLOW_NET_HELP) .help(ALLOW_NET_HELP)
.value_parser(flags_allow_net::validator), .value_parser(flags_net::validator),
)
.arg(
Arg::new("deny-net")
.long("deny-net")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("IP_OR_HOSTNAME")
.help(DENY_NET_HELP)
.value_parser(flags_net::validator),
) )
.arg(unsafely_ignore_certificate_errors_arg()) .arg(unsafely_ignore_certificate_errors_arg())
.arg( .arg(
@ -2166,6 +2380,26 @@ fn permission_args(app: Command) -> Command {
}) })
}), }),
) )
.arg(
Arg::new("deny-env")
.long("deny-env")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("VARIABLE_NAME")
.help(DENY_ENV_HELP)
.value_parser(|key: &str| {
if key.is_empty() || key.contains(&['=', '\0'] as &[char]) {
return Err(format!("invalid key \"{key}\""));
}
Ok(if cfg!(windows) {
key.to_uppercase()
} else {
key.to_string()
})
}),
)
.arg( .arg(
Arg::new("allow-sys") Arg::new("allow-sys")
.long("allow-sys") .long("allow-sys")
@ -2176,6 +2410,16 @@ fn permission_args(app: Command) -> Command {
.help(ALLOW_SYS_HELP) .help(ALLOW_SYS_HELP)
.value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)), .value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)),
) )
.arg(
Arg::new("deny-sys")
.long("deny-sys")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("API_NAME")
.help(DENY_SYS_HELP)
.value_parser(|key: &str| parse_sys_kind(key).map(ToString::to_string)),
)
.arg( .arg(
Arg::new("allow-run") Arg::new("allow-run")
.long("allow-run") .long("allow-run")
@ -2185,6 +2429,15 @@ fn permission_args(app: Command) -> Command {
.value_name("PROGRAM_NAME") .value_name("PROGRAM_NAME")
.help(ALLOW_RUN_HELP), .help(ALLOW_RUN_HELP),
) )
.arg(
Arg::new("deny-run")
.long("deny-run")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("PROGRAM_NAME")
.help(DENY_RUN_HELP),
)
.arg( .arg(
Arg::new("allow-ffi") Arg::new("allow-ffi")
.long("allow-ffi") .long("allow-ffi")
@ -2196,12 +2449,29 @@ fn permission_args(app: Command) -> Command {
.value_parser(value_parser!(PathBuf)) .value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::AnyPath), .value_hint(ValueHint::AnyPath),
) )
.arg(
Arg::new("deny-ffi")
.long("deny-ffi")
.num_args(0..)
.use_value_delimiter(true)
.require_equals(true)
.value_name("PATH")
.help(DENY_FFI_HELP)
.value_parser(value_parser!(PathBuf))
.value_hint(ValueHint::AnyPath),
)
.arg( .arg(
Arg::new("allow-hrtime") Arg::new("allow-hrtime")
.long("allow-hrtime") .long("allow-hrtime")
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.help(ALLOW_HRTIME_HELP), .help(ALLOW_HRTIME_HELP),
) )
.arg(
Arg::new("deny-hrtime")
.long("deny-hrtime")
.action(ArgAction::SetTrue)
.help(DENY_HRTIME_HELP),
)
.arg( .arg(
Arg::new("allow-all") Arg::new("allow-all")
.short('A') .short('A')
@ -2594,7 +2864,7 @@ fn unsafely_ignore_certificate_errors_arg() -> Arg {
.require_equals(true) .require_equals(true)
.value_name("HOSTNAMES") .value_name("HOSTNAMES")
.help("DANGER: Disables verification of TLS certificates") .help("DANGER: Disables verification of TLS certificates")
.value_parser(flags_allow_net::validator) .value_parser(flags_net::validator)
} }
fn bench_parse(flags: &mut Flags, matches: &mut ArgMatches) { fn bench_parse(flags: &mut Flags, matches: &mut ArgMatches) {
@ -3189,38 +3459,76 @@ fn permission_args_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.allow_read = Some(read_wl.collect()); flags.allow_read = Some(read_wl.collect());
} }
if let Some(read_wl) = matches.remove_many::<PathBuf>("deny-read") {
flags.deny_read = Some(read_wl.collect());
}
if let Some(write_wl) = matches.remove_many::<PathBuf>("allow-write") { if let Some(write_wl) = matches.remove_many::<PathBuf>("allow-write") {
flags.allow_write = Some(write_wl.collect()); flags.allow_write = Some(write_wl.collect());
} }
if let Some(write_wl) = matches.remove_many::<PathBuf>("deny-write") {
flags.deny_write = Some(write_wl.collect());
}
if let Some(net_wl) = matches.remove_many::<String>("allow-net") { if let Some(net_wl) = matches.remove_many::<String>("allow-net") {
let net_allowlist = flags_allow_net::parse(net_wl.collect()).unwrap(); let net_allowlist = flags_net::parse(net_wl.collect()).unwrap();
flags.allow_net = Some(net_allowlist); flags.allow_net = Some(net_allowlist);
} }
if let Some(net_wl) = matches.remove_many::<String>("deny-net") {
let net_denylist = flags_net::parse(net_wl.collect()).unwrap();
flags.deny_net = Some(net_denylist);
}
if let Some(env_wl) = matches.remove_many::<String>("allow-env") { if let Some(env_wl) = matches.remove_many::<String>("allow-env") {
flags.allow_env = Some(env_wl.collect()); flags.allow_env = Some(env_wl.collect());
debug!("env allowlist: {:#?}", &flags.allow_env); debug!("env allowlist: {:#?}", &flags.allow_env);
} }
if let Some(env_wl) = matches.remove_many::<String>("deny-env") {
flags.deny_env = Some(env_wl.collect());
debug!("env denylist: {:#?}", &flags.deny_env);
}
if let Some(run_wl) = matches.remove_many::<String>("allow-run") { if let Some(run_wl) = matches.remove_many::<String>("allow-run") {
flags.allow_run = Some(run_wl.collect()); flags.allow_run = Some(run_wl.collect());
debug!("run allowlist: {:#?}", &flags.allow_run); debug!("run allowlist: {:#?}", &flags.allow_run);
} }
if let Some(run_wl) = matches.remove_many::<String>("deny-run") {
flags.deny_run = Some(run_wl.collect());
debug!("run denylist: {:#?}", &flags.deny_run);
}
if let Some(sys_wl) = matches.remove_many::<String>("allow-sys") { if let Some(sys_wl) = matches.remove_many::<String>("allow-sys") {
flags.allow_sys = Some(sys_wl.collect()); flags.allow_sys = Some(sys_wl.collect());
debug!("sys info allowlist: {:#?}", &flags.allow_sys); debug!("sys info allowlist: {:#?}", &flags.allow_sys);
} }
if let Some(sys_wl) = matches.remove_many::<String>("deny-sys") {
flags.deny_sys = Some(sys_wl.collect());
debug!("sys info denylist: {:#?}", &flags.deny_sys);
}
if let Some(ffi_wl) = matches.remove_many::<PathBuf>("allow-ffi") { if let Some(ffi_wl) = matches.remove_many::<PathBuf>("allow-ffi") {
flags.allow_ffi = Some(ffi_wl.collect()); flags.allow_ffi = Some(ffi_wl.collect());
debug!("ffi allowlist: {:#?}", &flags.allow_ffi); debug!("ffi allowlist: {:#?}", &flags.allow_ffi);
} }
if let Some(ffi_wl) = matches.remove_many::<PathBuf>("deny-ffi") {
flags.deny_ffi = Some(ffi_wl.collect());
debug!("ffi denylist: {:#?}", &flags.deny_ffi);
}
if matches.get_flag("allow-hrtime") { if matches.get_flag("allow-hrtime") {
flags.allow_hrtime = true; flags.allow_hrtime = true;
} }
if matches.get_flag("deny-hrtime") {
flags.deny_hrtime = true;
}
if matches.get_flag("allow-all") { if matches.get_flag("allow-all") {
flags.allow_all = true; flags.allow_all = true;
flags.allow_read = Some(vec![]); flags.allow_read = Some(vec![]);
@ -3232,6 +3540,7 @@ fn permission_args_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.allow_ffi = Some(vec![]); flags.allow_ffi = Some(vec![]);
flags.allow_hrtime = true; flags.allow_hrtime = true;
} }
if matches.get_flag("no-prompt") { if matches.get_flag("no-prompt") {
flags.no_prompt = true; flags.no_prompt = true;
} }
@ -3243,7 +3552,7 @@ fn unsafely_ignore_certificate_errors_parse(
if let Some(ic_wl) = if let Some(ic_wl) =
matches.remove_many::<String>("unsafely-ignore-certificate-errors") matches.remove_many::<String>("unsafely-ignore-certificate-errors")
{ {
let ic_allowlist = flags_allow_net::parse(ic_wl.collect()).unwrap(); let ic_allowlist = flags_net::parse(ic_wl.collect()).unwrap();
flags.unsafely_ignore_certificate_errors = Some(ic_allowlist); flags.unsafely_ignore_certificate_errors = Some(ic_allowlist);
} }
} }
@ -3694,6 +4003,9 @@ mod tests {
let r = flags_from_vec(svec!["deno", "run", "--allow-read", "x.ts"]); let r = flags_from_vec(svec!["deno", "run", "--allow-read", "x.ts"]);
assert_eq!(r.unwrap().has_permission(), true); assert_eq!(r.unwrap().has_permission(), true);
let r = flags_from_vec(svec!["deno", "run", "--deny-read", "x.ts"]);
assert_eq!(r.unwrap().has_permission(), true);
let r = flags_from_vec(svec!["deno", "run", "x.ts"]); let r = flags_from_vec(svec!["deno", "run", "x.ts"]);
assert_eq!(r.unwrap().has_permission(), false); assert_eq!(r.unwrap().has_permission(), false);
} }
@ -3703,6 +4015,9 @@ mod tests {
let r = flags_from_vec(svec!["deno", "run", "x.ts", "--allow-read"]); let r = flags_from_vec(svec!["deno", "run", "x.ts", "--allow-read"]);
assert_eq!(r.unwrap().has_permission_in_argv(), true); assert_eq!(r.unwrap().has_permission_in_argv(), true);
let r = flags_from_vec(svec!["deno", "run", "x.ts", "--deny-read"]);
assert_eq!(r.unwrap().has_permission_in_argv(), true);
let r = flags_from_vec(svec!["deno", "run", "x.ts"]); let r = flags_from_vec(svec!["deno", "run", "x.ts"]);
assert_eq!(r.unwrap().has_permission_in_argv(), false); assert_eq!(r.unwrap().has_permission_in_argv(), false);
} }
@ -3771,6 +4086,22 @@ mod tests {
); );
} }
#[test]
fn deny_read() {
let r = flags_from_vec(svec!["deno", "run", "--deny-read", "gist.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "gist.ts".to_string(),
}),
deny_read: Some(vec![]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_hrtime() { fn allow_hrtime() {
let r = flags_from_vec(svec!["deno", "run", "--allow-hrtime", "gist.ts"]); let r = flags_from_vec(svec!["deno", "run", "--allow-hrtime", "gist.ts"]);
@ -3787,6 +4118,22 @@ mod tests {
); );
} }
#[test]
fn deny_hrtime() {
let r = flags_from_vec(svec!["deno", "run", "--deny-hrtime", "gist.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "gist.ts".to_string(),
}),
deny_hrtime: true,
..Flags::default()
}
);
}
#[test] #[test]
fn double_hyphen() { fn double_hyphen() {
// notice that flags passed after double dash will not // notice that flags passed after double dash will not
@ -4687,11 +5034,17 @@ mod tests {
allow_net: Some(vec![]), allow_net: Some(vec![]),
unsafely_ignore_certificate_errors: None, unsafely_ignore_certificate_errors: None,
allow_env: Some(vec![]), allow_env: Some(vec![]),
deny_env: None,
allow_run: Some(vec![]), allow_run: Some(vec![]),
deny_run: None,
allow_read: Some(vec![]), allow_read: Some(vec![]),
deny_read: None,
allow_sys: Some(vec![]), allow_sys: Some(vec![]),
deny_sys: None,
allow_write: Some(vec![]), allow_write: Some(vec![]),
deny_write: None,
allow_ffi: Some(vec![]), allow_ffi: Some(vec![]),
deny_ffi: None,
allow_hrtime: true, allow_hrtime: true,
..Flags::default() ..Flags::default()
} }
@ -4804,6 +5157,31 @@ mod tests {
); );
} }
#[test]
fn deny_read_denylist() {
use test_util::TempDir;
let temp_dir_guard = TempDir::new();
let temp_dir = temp_dir_guard.path().to_path_buf();
let r = flags_from_vec(svec![
"deno",
"run",
format!("--deny-read=.,{}", temp_dir.to_str().unwrap()),
"script.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
deny_read: Some(vec![PathBuf::from("."), temp_dir]),
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_write_allowlist() { fn allow_write_allowlist() {
use test_util::TempDir; use test_util::TempDir;
@ -4829,6 +5207,31 @@ mod tests {
); );
} }
#[test]
fn deny_write_denylist() {
use test_util::TempDir;
let temp_dir_guard = TempDir::new();
let temp_dir = temp_dir_guard.path().to_path_buf();
let r = flags_from_vec(svec![
"deno",
"run",
format!("--deny-write=.,{}", temp_dir.to_str().unwrap()),
"script.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
deny_write: Some(vec![PathBuf::from("."), temp_dir]),
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_net_allowlist() { fn allow_net_allowlist() {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
@ -4850,6 +5253,23 @@ mod tests {
); );
} }
#[test]
fn deny_net_denylist() {
let r =
flags_from_vec(svec!["deno", "run", "--deny-net=127.0.0.1", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_net: Some(svec!["127.0.0.1"]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_env_allowlist() { fn allow_env_allowlist() {
let r = let r =
@ -4867,6 +5287,23 @@ mod tests {
); );
} }
#[test]
fn deny_env_denylist() {
let r =
flags_from_vec(svec!["deno", "run", "--deny-env=HOME", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_env: Some(svec!["HOME"]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_env_allowlist_multiple() { fn allow_env_allowlist_multiple() {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
@ -4888,6 +5325,23 @@ mod tests {
); );
} }
#[test]
fn deny_env_denylist_multiple() {
let r =
flags_from_vec(svec!["deno", "run", "--deny-env=HOME,PATH", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_env: Some(svec!["HOME", "PATH"]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_env_allowlist_validator() { fn allow_env_allowlist_validator() {
let r = let r =
@ -4901,6 +5355,19 @@ mod tests {
assert!(r.is_err()); assert!(r.is_err());
} }
#[test]
fn deny_env_denylist_validator() {
let r =
flags_from_vec(svec!["deno", "run", "--deny-env=HOME", "script.ts"]);
assert!(r.is_ok());
let r =
flags_from_vec(svec!["deno", "run", "--deny-env=H=ME", "script.ts"]);
assert!(r.is_err());
let r =
flags_from_vec(svec!["deno", "run", "--deny-env=H\0ME", "script.ts"]);
assert!(r.is_err());
}
#[test] #[test]
fn allow_sys() { fn allow_sys() {
let r = flags_from_vec(svec!["deno", "run", "--allow-sys", "script.ts"]); let r = flags_from_vec(svec!["deno", "run", "--allow-sys", "script.ts"]);
@ -4917,6 +5384,22 @@ mod tests {
); );
} }
#[test]
fn deny_sys() {
let r = flags_from_vec(svec!["deno", "run", "--deny-sys", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_sys: Some(vec![]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_sys_allowlist() { fn allow_sys_allowlist() {
let r = let r =
@ -4934,6 +5417,23 @@ mod tests {
); );
} }
#[test]
fn deny_sys_denylist() {
let r =
flags_from_vec(svec!["deno", "run", "--deny-sys=hostname", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_sys: Some(svec!["hostname"]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_sys_allowlist_multiple() { fn allow_sys_allowlist_multiple() {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
@ -4955,6 +5455,27 @@ mod tests {
); );
} }
#[test]
fn deny_sys_denylist_multiple() {
let r = flags_from_vec(svec![
"deno",
"run",
"--deny-sys=hostname,osRelease",
"script.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_sys: Some(svec!["hostname", "osRelease"]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_sys_allowlist_validator() { fn allow_sys_allowlist_validator() {
let r = let r =
@ -4979,6 +5500,29 @@ mod tests {
assert!(r.is_err()); assert!(r.is_err());
} }
#[test]
fn deny_sys_denylist_validator() {
let r =
flags_from_vec(svec!["deno", "run", "--deny-sys=hostname", "script.ts"]);
assert!(r.is_ok());
let r = flags_from_vec(svec![
"deno",
"run",
"--deny-sys=hostname,osRelease",
"script.ts"
]);
assert!(r.is_ok());
let r = flags_from_vec(svec!["deno", "run", "--deny-sys=foo", "script.ts"]);
assert!(r.is_err());
let r = flags_from_vec(svec![
"deno",
"run",
"--deny-sys=hostname,foo",
"script.ts"
]);
assert!(r.is_err());
}
#[test] #[test]
fn reload_validator() { fn reload_validator() {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
@ -5850,6 +6394,35 @@ mod tests {
); );
} }
#[test]
fn deny_net_denylist_with_ports() {
let r = flags_from_vec(svec![
"deno",
"run",
"--deny-net=deno.land,:8000,:4545",
"script.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_net: Some(svec![
"deno.land",
"0.0.0.0:8000",
"127.0.0.1:8000",
"localhost:8000",
"0.0.0.0:4545",
"127.0.0.1:4545",
"localhost:4545"
]),
..Flags::default()
}
);
}
#[test] #[test]
fn allow_net_allowlist_with_ipv6_address() { fn allow_net_allowlist_with_ipv6_address() {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![
@ -5882,6 +6455,38 @@ mod tests {
); );
} }
#[test]
fn deny_net_denylist_with_ipv6_address() {
let r = flags_from_vec(svec![
"deno",
"run",
"--deny-net=deno.land,deno.land:80,::,127.0.0.1,[::1],1.2.3.4:5678,:5678,[::1]:8080",
"script.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags {
watch: None,
script: "script.ts".to_string(),
}),
deny_net: Some(svec![
"deno.land",
"deno.land:80",
"::",
"127.0.0.1",
"[::1]",
"1.2.3.4:5678",
"0.0.0.0:5678",
"127.0.0.1:5678",
"localhost:5678",
"[::1]:8080"
]),
..Flags::default()
}
);
}
#[test] #[test]
fn lock_write() { fn lock_write() {
let r = flags_from_vec(svec![ let r = flags_from_vec(svec![

View file

@ -2,7 +2,7 @@
mod config_file; mod config_file;
mod flags; mod flags;
mod flags_allow_net; mod flags_net;
mod import_map; mod import_map;
mod lockfile; mod lockfile;
pub mod package_json; pub mod package_json;
@ -1105,13 +1105,21 @@ impl CliOptions {
pub fn permissions_options(&self) -> PermissionsOptions { pub fn permissions_options(&self) -> PermissionsOptions {
PermissionsOptions { PermissionsOptions {
allow_env: self.flags.allow_env.clone(), allow_env: self.flags.allow_env.clone(),
deny_env: self.flags.deny_env.clone(),
allow_hrtime: self.flags.allow_hrtime, allow_hrtime: self.flags.allow_hrtime,
deny_hrtime: self.flags.deny_hrtime,
allow_net: self.flags.allow_net.clone(), allow_net: self.flags.allow_net.clone(),
deny_net: self.flags.deny_net.clone(),
allow_ffi: self.flags.allow_ffi.clone(), allow_ffi: self.flags.allow_ffi.clone(),
deny_ffi: self.flags.deny_ffi.clone(),
allow_read: self.flags.allow_read.clone(), allow_read: self.flags.allow_read.clone(),
deny_read: self.flags.deny_read.clone(),
allow_run: self.flags.allow_run.clone(), allow_run: self.flags.allow_run.clone(),
deny_run: self.flags.deny_run.clone(),
allow_sys: self.flags.allow_sys.clone(), allow_sys: self.flags.allow_sys.clone(),
deny_sys: self.flags.deny_sys.clone(),
allow_write: self.flags.allow_write.clone(), allow_write: self.flags.allow_write.clone(),
deny_write: self.flags.deny_write.clone(),
prompt: !self.no_prompt(), prompt: !self.no_prompt(),
} }
} }

View file

@ -601,7 +601,7 @@ fn _090_run_permissions_request() {
.with_pty(|mut console| { .with_pty(|mut console| {
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests run access to \"ls\".\r\n", "┌ ⚠️ Deno requests run access to \"ls\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-run to bypass this prompt.\r\n", "├ Run again with --allow-run to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
)); ));
@ -609,7 +609,7 @@ fn _090_run_permissions_request() {
console.expect("Granted run access to \"ls\"."); console.expect("Granted run access to \"ls\".");
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests run access to \"cat\".\r\n", "┌ ⚠️ Deno requests run access to \"cat\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-run to bypass this prompt.\r\n", "├ Run again with --allow-run to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
)); ));
@ -628,7 +628,7 @@ fn _090_run_permissions_request_sync() {
.with_pty(|mut console| { .with_pty(|mut console| {
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests run access to \"ls\".\r\n", "┌ ⚠️ Deno requests run access to \"ls\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-run to bypass this prompt.\r\n", "├ Run again with --allow-run to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
)); ));
@ -636,7 +636,7 @@ fn _090_run_permissions_request_sync() {
console.expect("Granted run access to \"ls\"."); console.expect("Granted run access to \"ls\".");
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests run access to \"cat\".\r\n", "┌ ⚠️ Deno requests run access to \"cat\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-run to bypass this prompt.\r\n", "├ Run again with --allow-run to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
)); ));
@ -656,7 +656,7 @@ fn permissions_prompt_allow_all() {
// "run" permissions // "run" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests run access to \"FOO\".\r\n", "┌ ⚠️ Deno requests run access to \"FOO\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-run to bypass this prompt.\r\n", "├ Run again with --allow-run to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
)); ));
@ -665,7 +665,7 @@ fn permissions_prompt_allow_all() {
// "read" permissions // "read" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access to \"FOO\".\r\n", "┌ ⚠️ Deno requests read access to \"FOO\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
@ -674,7 +674,7 @@ fn permissions_prompt_allow_all() {
// "write" permissions // "write" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests write access to \"FOO\".\r\n", "┌ ⚠️ Deno requests write access to \"FOO\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-write to bypass this prompt.\r\n", "├ Run again with --allow-write to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions)",
)); ));
@ -682,8 +682,8 @@ fn permissions_prompt_allow_all() {
console.expect("✅ Granted all write access."); console.expect("✅ Granted all write access.");
// "net" permissions // "net" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests network access to \"foo\".\r\n", "┌ ⚠️ Deno requests net access to \"foo\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-net to bypass this prompt.\r\n", "├ Run again with --allow-net to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions)",
)); ));
@ -692,7 +692,7 @@ fn permissions_prompt_allow_all() {
// "env" permissions // "env" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests env access to \"FOO\".\r\n", "┌ ⚠️ Deno requests env access to \"FOO\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-env to bypass this prompt.\r\n", "├ Run again with --allow-env to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)",
)); ));
@ -701,7 +701,7 @@ fn permissions_prompt_allow_all() {
// "sys" permissions // "sys" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n", "┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-sys to bypass this prompt.\r\n", "├ Run again with --allow-sys to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions)",
)); ));
@ -710,7 +710,7 @@ fn permissions_prompt_allow_all() {
// "ffi" permissions // "ffi" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests ffi access to \"FOO\".\r\n", "┌ ⚠️ Deno requests ffi access to \"FOO\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-ffi to bypass this prompt.\r\n", "├ Run again with --allow-ffi to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all ffi permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all ffi permissions)",
)); ));
@ -766,7 +766,7 @@ fn permissions_prompt_allow_all_lowercase_a() {
// "run" permissions // "run" permissions
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests run access to \"FOO\".\r\n", "┌ ⚠️ Deno requests run access to \"FOO\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-run to bypass this prompt.\r\n", "├ Run again with --allow-run to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
)); ));
@ -775,6 +775,36 @@ fn permissions_prompt_allow_all_lowercase_a() {
}); });
} }
itest!(deny_all_permission_args {
args: "run --deny-env --deny-read --deny-write --deny-ffi --deny-run --deny-sys --deny-net --deny-hrtime run/deny_all_permission_args.js",
output: "run/deny_all_permission_args.out",
});
itest!(deny_some_permission_args {
args: "run --allow-env --deny-env=FOO --allow-read --deny-read=/foo --allow-write --deny-write=/foo --allow-ffi --deny-ffi=/foo --allow-run --deny-run=foo --allow-sys --deny-sys=hostname --allow-net --deny-net=127.0.0.1 --allow-hrtime --deny-hrtime run/deny_some_permission_args.js",
output: "run/deny_some_permission_args.out",
});
#[test]
fn permissions_cache() {
TestContext::default()
.new_command()
.args_vec(["run", "--quiet", "run/permissions_cache.ts"])
.with_pty(|mut console| {
console.expect(concat!(
"prompt\r\n",
"┌ ⚠️ Deno requests read access to \"foo\".\r\n",
"├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
));
console.write_line_raw("y");
console.expect("✅ Granted read access to \"foo\".");
console.expect("granted");
console.expect("prompt");
});
}
itest!(_091_use_define_for_class_fields { itest!(_091_use_define_for_class_fields {
args: "run --check run/091_use_define_for_class_fields.ts", args: "run --check run/091_use_define_for_class_fields.ts",
output: "run/091_use_define_for_class_fields.ts.out", output: "run/091_use_define_for_class_fields.ts.out",
@ -2541,14 +2571,14 @@ mod permissions {
.with_pty(|mut console| { .with_pty(|mut console| {
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access to \"foo\".\r\n", "┌ ⚠️ Deno requests read access to \"foo\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
console.write_line_raw("y"); console.write_line_raw("y");
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access to \"bar\".\r\n", "┌ ⚠️ Deno requests read access to \"bar\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
@ -2567,14 +2597,14 @@ mod permissions {
.with_pty(|mut console| { .with_pty(|mut console| {
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access to \"foo\".\r\n", "┌ ⚠️ Deno requests read access to \"foo\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
console.write_line_raw("y"); console.write_line_raw("y");
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access to \"bar\".\r\n", "┌ ⚠️ Deno requests read access to \"bar\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
@ -2593,7 +2623,7 @@ mod permissions {
.with_pty(|mut console| { .with_pty(|mut console| {
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access.\r\n", "┌ ⚠️ Deno requests read access.\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
@ -2615,7 +2645,7 @@ mod permissions {
.with_pty(|mut console| { .with_pty(|mut console| {
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests read access.\r\n", "┌ ⚠️ Deno requests read access.\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-read to bypass this prompt.\r\n", "├ Run again with --allow-read to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
)); ));
@ -2756,7 +2786,7 @@ fn issue9750() {
console.write_line_raw("yy"); console.write_line_raw("yy");
console.expect(concat!( console.expect(concat!(
"┌ ⚠️ Deno requests env access.\r\n", "┌ ⚠️ Deno requests env access.\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n", "├ Requested by `Deno.permissions.request()` API.\r\n",
"├ Run again with --allow-env to bypass this prompt.\r\n", "├ Run again with --allow-env to bypass this prompt.\r\n",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)", "└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)",
)); ));

View file

@ -0,0 +1,8 @@
console.log(Deno.permissions.querySync({ name: "env" }));
console.log(Deno.permissions.querySync({ name: "read" }));
console.log(Deno.permissions.querySync({ name: "write" }));
console.log(Deno.permissions.querySync({ name: "ffi" }));
console.log(Deno.permissions.querySync({ name: "run" }));
console.log(Deno.permissions.querySync({ name: "sys" }));
console.log(Deno.permissions.querySync({ name: "net" }));
console.log(Deno.permissions.querySync({ name: "hrtime" }));

View file

@ -0,0 +1,8 @@
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "denied", onchange: null }

View file

@ -0,0 +1,22 @@
console.log(Deno.permissions.querySync({ name: "env" }));
console.log(Deno.permissions.querySync({ name: "env", variable: "FOO" }));
console.log(Deno.permissions.querySync({ name: "env", variable: "BAR" }));
console.log(Deno.permissions.querySync({ name: "read" }));
console.log(Deno.permissions.querySync({ name: "read", path: "/foo" }));
console.log(Deno.permissions.querySync({ name: "read", path: "/bar" }));
console.log(Deno.permissions.querySync({ name: "write" }));
console.log(Deno.permissions.querySync({ name: "write", path: "/foo" }));
console.log(Deno.permissions.querySync({ name: "write", path: "/bar" }));
console.log(Deno.permissions.querySync({ name: "ffi" }));
console.log(Deno.permissions.querySync({ name: "ffi", path: "/foo" }));
console.log(Deno.permissions.querySync({ name: "ffi", path: "/bar" }));
console.log(Deno.permissions.querySync({ name: "run" }));
console.log(Deno.permissions.querySync({ name: "run", command: "foo" }));
console.log(Deno.permissions.querySync({ name: "run", command: "bar" }));
console.log(Deno.permissions.querySync({ name: "sys" }));
console.log(Deno.permissions.querySync({ name: "sys", kind: "hostname" }));
console.log(Deno.permissions.querySync({ name: "sys", kind: "loadavg" }));
console.log(Deno.permissions.querySync({ name: "net" }));
console.log(Deno.permissions.querySync({ name: "net", host: "127.0.0.1" }));
console.log(Deno.permissions.querySync({ name: "net", host: "192.168.0.1" }));
console.log(Deno.permissions.querySync({ name: "hrtime" }));

View file

@ -0,0 +1,22 @@
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "granted", onchange: null, partial: true }
PermissionStatus { state: "denied", onchange: null }
PermissionStatus { state: "granted", onchange: null }
PermissionStatus { state: "denied", onchange: null }

View file

@ -0,0 +1,5 @@
const status = await Deno.permissions.query({ name: "read", path: "foo" });
console.log(status.state);
status.onchange = () => console.log(status.state);
await Deno.permissions.request({ name: "read", path: "foo" }); // y
await Deno.permissions.revoke({ name: "read", path: "foo" });

View file

@ -4403,9 +4403,12 @@ declare namespace Deno {
* *
* @category Permissions * @category Permissions
*/ */
export type PermissionState = "granted" | "denied" | "prompt"; export type PermissionState =
| "granted"
| "denied"
| "prompt";
/** The permission descriptor for the `allow-run` permission, which controls /** The permission descriptor for the `allow-run` and `deny-run` permissions, which controls
* access to what sub-processes can be executed by Deno. The option `command` * access to what sub-processes can be executed by Deno. The option `command`
* allows scoping the permission to a specific executable. * allows scoping the permission to a specific executable.
* *
@ -4416,12 +4419,12 @@ declare namespace Deno {
* @category Permissions */ * @category Permissions */
export interface RunPermissionDescriptor { export interface RunPermissionDescriptor {
name: "run"; name: "run";
/** The `allow-run` permission can be scoped to a specific executable, /** An `allow-run` or `deny-run` permission can be scoped to a specific executable,
* which would be relative to the start-up CWD of the Deno CLI. */ * which would be relative to the start-up CWD of the Deno CLI. */
command?: string | URL; command?: string | URL;
} }
/** The permission descriptor for the `allow-read` permissions, which controls /** The permission descriptor for the `allow-read` and `deny-read` permissions, which controls
* access to reading resources from the local host. The option `path` allows * access to reading resources from the local host. The option `path` allows
* scoping the permission to a specific path (and if the path is a directory * scoping the permission to a specific path (and if the path is a directory
* any sub paths). * any sub paths).
@ -4432,12 +4435,12 @@ declare namespace Deno {
* @category Permissions */ * @category Permissions */
export interface ReadPermissionDescriptor { export interface ReadPermissionDescriptor {
name: "read"; name: "read";
/** The `allow-read` permission can be scoped to a specific path (and if /** An `allow-read` or `deny-read` permission can be scoped to a specific path (and if
* the path is a directory, any sub paths). */ * the path is a directory, any sub paths). */
path?: string | URL; path?: string | URL;
} }
/** The permission descriptor for the `allow-write` permissions, which /** The permission descriptor for the `allow-write` and `deny-write` permissions, which
* controls access to writing to resources from the local host. The option * controls access to writing to resources from the local host. The option
* `path` allow scoping the permission to a specific path (and if the path is * `path` allow scoping the permission to a specific path (and if the path is
* a directory any sub paths). * a directory any sub paths).
@ -4448,12 +4451,12 @@ declare namespace Deno {
* @category Permissions */ * @category Permissions */
export interface WritePermissionDescriptor { export interface WritePermissionDescriptor {
name: "write"; name: "write";
/** The `allow-write` permission can be scoped to a specific path (and if /** An `allow-write` or `deny-write` permission can be scoped to a specific path (and if
* the path is a directory, any sub paths). */ * the path is a directory, any sub paths). */
path?: string | URL; path?: string | URL;
} }
/** The permission descriptor for the `allow-net` permissions, which controls /** The permission descriptor for the `allow-net` and `deny-net` permissions, which controls
* access to opening network ports and connecting to remote hosts via the * access to opening network ports and connecting to remote hosts via the
* network. The option `host` allows scoping the permission for outbound * network. The option `host` allows scoping the permission for outbound
* connection to a specific host and port. * connection to a specific host and port.
@ -4469,7 +4472,7 @@ declare namespace Deno {
host?: string; host?: string;
} }
/** The permission descriptor for the `allow-env` permissions, which controls /** The permission descriptor for the `allow-env` and `deny-env` permissions, which controls
* access to being able to read and write to the process environment variables * access to being able to read and write to the process environment variables
* as well as access other information about the environment. The option * as well as access other information about the environment. The option
* `variable` allows scoping the permission to a specific environment * `variable` allows scoping the permission to a specific environment
@ -4482,7 +4485,7 @@ declare namespace Deno {
variable?: string; variable?: string;
} }
/** The permission descriptor for the `allow-sys` permissions, which controls /** The permission descriptor for the `allow-sys` and `deny-sys` permissions, which controls
* access to sensitive host system information, which malicious code might * access to sensitive host system information, which malicious code might
* attempt to exploit. The option `kind` allows scoping the permission to a * attempt to exploit. The option `kind` allows scoping the permission to a
* specific piece of information. * specific piece of information.
@ -4502,7 +4505,7 @@ declare namespace Deno {
| "gid"; | "gid";
} }
/** The permission descriptor for the `allow-ffi` permissions, which controls /** The permission descriptor for the `allow-ffi` and `deny-ffi` permissions, which controls
* access to loading _foreign_ code and interfacing with it via the * access to loading _foreign_ code and interfacing with it via the
* [Foreign Function Interface API](https://deno.land/manual/runtime/ffi_api) * [Foreign Function Interface API](https://deno.land/manual/runtime/ffi_api)
* available in Deno. The option `path` allows scoping the permission to a * available in Deno. The option `path` allows scoping the permission to a
@ -4515,7 +4518,7 @@ declare namespace Deno {
path?: string | URL; path?: string | URL;
} }
/** The permission descriptor for the `allow-hrtime` permission, which /** The permission descriptor for the `allow-hrtime` and `deny-hrtime` permissions, which
* controls if the runtime code has access to high resolution time. High * controls if the runtime code has access to high resolution time. High
* resolution time is considered sensitive information, because it can be used * resolution time is considered sensitive information, because it can be used
* by malicious code to gain information about the host that it might not * by malicious code to gain information about the host that it might not
@ -4560,6 +4563,13 @@ declare namespace Deno {
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
onchange: ((this: PermissionStatus, ev: Event) => any) | null; onchange: ((this: PermissionStatus, ev: Event) => any) | null;
readonly state: PermissionState; readonly state: PermissionState;
/**
* Describes if permission is only granted partially, eg. an access
* might be granted to "/foo" directory, but denied for "/foo/bar".
* In such case this field will be set to `true` when querying for
* read permissions of "/foo" directory.
*/
readonly partial: boolean;
addEventListener<K extends keyof PermissionStatusEventMap>( addEventListener<K extends keyof PermissionStatusEventMap>(
type: K, type: K,
listener: ( listener: (

View file

@ -289,7 +289,7 @@ where
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
}; };
let symbol = PtrSymbol::new(pointer, &def)?; let symbol = PtrSymbol::new(pointer, &def)?;
@ -389,7 +389,7 @@ where
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
}; };
let symbol = PtrSymbol::new(pointer, &def)?; let symbol = PtrSymbol::new(pointer, &def)?;

View file

@ -546,7 +546,7 @@ where
{ {
check_unstable(state, "Deno.UnsafeCallback"); check_unstable(state, "Deno.UnsafeCallback");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
let v8_value = cb.v8_value; let v8_value = cb.v8_value;
let cb = v8::Local::<v8::Function>::try_from(v8_value)?; let cb = v8::Local::<v8::Function>::try_from(v8_value)?;

View file

@ -144,7 +144,7 @@ where
check_unstable(state, "Deno.dlopen"); check_unstable(state, "Deno.dlopen");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(Some(&PathBuf::from(&path)))?; permissions.check_partial(Some(&PathBuf::from(&path)))?;
let lib = Library::open(&path).map_err(|e| { let lib = Library::open(&path).map_err(|e| {
dlopen::Error::OpeningLibraryError(std::io::Error::new( dlopen::Error::OpeningLibraryError(std::io::Error::new(

View file

@ -64,7 +64,7 @@ pub fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
} }
pub trait FfiPermissions { pub trait FfiPermissions {
fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError>; fn check_partial(&mut self, path: Option<&Path>) -> Result<(), AnyError>;
} }
pub(crate) type PendingFfiAsyncWork = Box<dyn FnOnce()>; pub(crate) type PendingFfiAsyncWork = Box<dyn FnOnce()>;

View file

@ -24,7 +24,7 @@ where
{ {
check_unstable(state, "Deno.UnsafePointer#create"); check_unstable(state, "Deno.UnsafePointer#create");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
Ok(ptr_number as *mut c_void) Ok(ptr_number as *mut c_void)
} }
@ -40,7 +40,7 @@ where
{ {
check_unstable(state, "Deno.UnsafePointer#equals"); check_unstable(state, "Deno.UnsafePointer#equals");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
Ok(a == b) Ok(a == b)
} }
@ -55,7 +55,7 @@ where
{ {
check_unstable(state, "Deno.UnsafePointer#of"); check_unstable(state, "Deno.UnsafePointer#of");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
Ok(buf as *mut c_void) Ok(buf as *mut c_void)
} }
@ -71,7 +71,7 @@ where
{ {
check_unstable(state, "Deno.UnsafePointer#offset"); check_unstable(state, "Deno.UnsafePointer#offset");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid pointer to offset, pointer is null")); return Err(type_error("Invalid pointer to offset, pointer is null"));
@ -99,7 +99,7 @@ where
{ {
check_unstable(state, "Deno.UnsafePointer#value"); check_unstable(state, "Deno.UnsafePointer#value");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
let outptr = out.as_ptr() as *mut usize; let outptr = out.as_ptr() as *mut usize;
let length = out.len(); let length = out.len();
@ -129,7 +129,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getArrayBuffer"); check_unstable(state, "Deno.UnsafePointerView#getArrayBuffer");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid ArrayBuffer pointer, pointer is null")); return Err(type_error("Invalid ArrayBuffer pointer, pointer is null"));
@ -164,7 +164,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#copyInto"); check_unstable(state, "Deno.UnsafePointerView#copyInto");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if src.is_null() { if src.is_null() {
Err(type_error("Invalid ArrayBuffer pointer, pointer is null")) Err(type_error("Invalid ArrayBuffer pointer, pointer is null"))
@ -197,7 +197,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getCString"); check_unstable(state, "Deno.UnsafePointerView#getCString");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid CString pointer, pointer is null")); return Err(type_error("Invalid CString pointer, pointer is null"));
@ -227,7 +227,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getBool"); check_unstable(state, "Deno.UnsafePointerView#getBool");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid bool pointer, pointer is null")); return Err(type_error("Invalid bool pointer, pointer is null"));
@ -249,7 +249,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getUint8"); check_unstable(state, "Deno.UnsafePointerView#getUint8");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid u8 pointer, pointer is null")); return Err(type_error("Invalid u8 pointer, pointer is null"));
@ -273,7 +273,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getInt8"); check_unstable(state, "Deno.UnsafePointerView#getInt8");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid i8 pointer, pointer is null")); return Err(type_error("Invalid i8 pointer, pointer is null"));
@ -297,7 +297,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getUint16"); check_unstable(state, "Deno.UnsafePointerView#getUint16");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid u16 pointer, pointer is null")); return Err(type_error("Invalid u16 pointer, pointer is null"));
@ -321,7 +321,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getInt16"); check_unstable(state, "Deno.UnsafePointerView#getInt16");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid i16 pointer, pointer is null")); return Err(type_error("Invalid i16 pointer, pointer is null"));
@ -345,7 +345,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getUint32"); check_unstable(state, "Deno.UnsafePointerView#getUint32");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid u32 pointer, pointer is null")); return Err(type_error("Invalid u32 pointer, pointer is null"));
@ -367,7 +367,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getInt32"); check_unstable(state, "Deno.UnsafePointerView#getInt32");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid i32 pointer, pointer is null")); return Err(type_error("Invalid i32 pointer, pointer is null"));
@ -390,7 +390,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getBigUint64"); check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
let outptr = out.as_mut_ptr() as *mut u64; let outptr = out.as_mut_ptr() as *mut u64;
@ -425,7 +425,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getBigUint64"); check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
let outptr = out.as_mut_ptr() as *mut i64; let outptr = out.as_mut_ptr() as *mut i64;
@ -458,7 +458,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getFloat32"); check_unstable(state, "Deno.UnsafePointerView#getFloat32");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid f32 pointer, pointer is null")); return Err(type_error("Invalid f32 pointer, pointer is null"));
@ -480,7 +480,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getFloat64"); check_unstable(state, "Deno.UnsafePointerView#getFloat64");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid f64 pointer, pointer is null")); return Err(type_error("Invalid f64 pointer, pointer is null"));
@ -502,7 +502,7 @@ where
check_unstable(state, "Deno.UnsafePointerView#getPointer"); check_unstable(state, "Deno.UnsafePointerView#getPointer");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(None)?; permissions.check_partial(None)?;
if ptr.is_null() { if ptr.is_null() {
return Err(type_error("Invalid pointer pointer, pointer is null")); return Err(type_error("Invalid pointer pointer, pointer is null"));

View file

@ -23,7 +23,8 @@ use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
pub trait FsPermissions { pub trait FsPermissions {
fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>; fn check_read(&mut self, path: &Path, api_name: &str)
-> Result<(), AnyError>;
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>; fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_read_blind( fn check_read_blind(
&mut self, &mut self,
@ -31,7 +32,16 @@ pub trait FsPermissions {
display: &str, display: &str,
api_name: &str, api_name: &str,
) -> Result<(), AnyError>; ) -> Result<(), AnyError>;
fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>; fn check_write(
&mut self,
path: &Path,
api_name: &str,
) -> Result<(), AnyError>;
fn check_write_partial(
&mut self,
path: &Path,
api_name: &str,
) -> Result<(), AnyError>;
fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>; fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_write_blind( fn check_write_blind(
&mut self, &mut self,

View file

@ -294,9 +294,16 @@ where
let fs = { let fs = {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
state if recursive {
.borrow_mut::<P>() state
.check_write(&path, "Deno.remove()")?; .borrow_mut::<P>()
.check_write(&path, "Deno.remove()")?;
} else {
state
.borrow_mut::<P>()
.check_write_partial(&path, "Deno.remove()")?;
}
state.borrow::<FileSystemRc>().clone() state.borrow::<FileSystemRc>().clone()
}; };

View file

@ -111,7 +111,7 @@ mod startup_snapshot {
} }
impl deno_ffi::FfiPermissions for Permissions { impl deno_ffi::FfiPermissions for Permissions {
fn check( fn check_partial(
&mut self, &mut self,
_path: Option<&Path>, _path: Option<&Path>,
) -> Result<(), deno_core::error::AnyError> { ) -> Result<(), deno_core::error::AnyError> {
@ -204,6 +204,14 @@ mod startup_snapshot {
unreachable!("snapshotting!") unreachable!("snapshotting!")
} }
fn check_write_partial(
&mut self,
_path: &Path,
_api_name: &str,
) -> Result<(), AnyError> {
unreachable!("snapshotting!")
}
fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> { fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
unreachable!("snapshotting!") unreachable!("snapshotting!")
} }

View file

@ -30,6 +30,7 @@ const illegalConstructorKey = Symbol("illegalConstructorKey");
* @typedef StatusCacheValue * @typedef StatusCacheValue
* @property {PermissionState} state * @property {PermissionState} state
* @property {PermissionStatus} status * @property {PermissionStatus} status
* @property {boolean} partial
*/ */
/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */ /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */
@ -69,27 +70,32 @@ function opRequest(desc) {
} }
class PermissionStatus extends EventTarget { class PermissionStatus extends EventTarget {
/** @type {{ state: Deno.PermissionState }} */ /** @type {{ state: Deno.PermissionState, partial: boolean }} */
#state; #status;
/** @type {((this: PermissionStatus, event: Event) => any) | null} */ /** @type {((this: PermissionStatus, event: Event) => any) | null} */
onchange = null; onchange = null;
/** @returns {Deno.PermissionState} */ /** @returns {Deno.PermissionState} */
get state() { get state() {
return this.#state.state; return this.#status.state;
}
/** @returns {boolean} */
get partial() {
return this.#status.partial;
} }
/** /**
* @param {{ state: Deno.PermissionState }} state * @param {{ state: Deno.PermissionState, partial: boolean }} status
* @param {unknown} key * @param {unknown} key
*/ */
constructor(state = null, key = null) { constructor(status = null, key = null) {
if (key != illegalConstructorKey) { if (key != illegalConstructorKey) {
throw new TypeError("Illegal constructor."); throw new TypeError("Illegal constructor.");
} }
super(); super();
this.#state = state; this.#status = status;
} }
/** /**
@ -106,9 +112,9 @@ class PermissionStatus extends EventTarget {
} }
[SymbolFor("Deno.privateCustomInspect")](inspect) { [SymbolFor("Deno.privateCustomInspect")](inspect) {
return `${this.constructor.name} ${ const object = { state: this.state, onchange: this.onchange };
inspect({ state: this.state, onchange: this.onchange }) if (this.partial) object.partial = this.partial;
}`; return `${this.constructor.name} ${inspect(object)}`;
} }
} }
@ -117,10 +123,10 @@ const statusCache = new SafeMap();
/** /**
* @param {Deno.PermissionDescriptor} desc * @param {Deno.PermissionDescriptor} desc
* @param {Deno.PermissionState} state * @param {{ state: Deno.PermissionState, partial: boolean }} rawStatus
* @returns {PermissionStatus} * @returns {PermissionStatus}
*/ */
function cache(desc, state) { function cache(desc, rawStatus) {
let { name: key } = desc; let { name: key } = desc;
if ( if (
(desc.name === "read" || desc.name === "write" || desc.name === "ffi") && (desc.name === "read" || desc.name === "write" || desc.name === "ffi") &&
@ -139,18 +145,24 @@ function cache(desc, state) {
key += "$"; key += "$";
} }
if (MapPrototypeHas(statusCache, key)) { if (MapPrototypeHas(statusCache, key)) {
const status = MapPrototypeGet(statusCache, key); const cachedObj = MapPrototypeGet(statusCache, key);
if (status.state !== state) { if (
status.state = state; cachedObj.state !== rawStatus.state ||
status.status.dispatchEvent(new Event("change", { cancelable: false })); cachedObj.partial !== rawStatus.partial
) {
cachedObj.state = rawStatus.state;
cachedObj.partial = rawStatus.partial;
cachedObj.status.dispatchEvent(
new Event("change", { cancelable: false }),
);
} }
return status.status; return cachedObj.status;
} }
/** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ /** @type {{ state: Deno.PermissionState, partial: boolean, status?: PermissionStatus }} */
const status = { state }; const obj = rawStatus;
status.status = new PermissionStatus(status, illegalConstructorKey); obj.status = new PermissionStatus(obj, illegalConstructorKey);
MapPrototypeSet(statusCache, key, status); MapPrototypeSet(statusCache, key, obj);
return status.status; return obj.status;
} }
/** /**
@ -200,8 +212,8 @@ class Permissions {
formDescriptor(desc); formDescriptor(desc);
const state = opQuery(desc); const status = opQuery(desc);
return cache(desc, state); return cache(desc, status);
} }
revoke(desc) { revoke(desc) {
@ -221,8 +233,8 @@ class Permissions {
formDescriptor(desc); formDescriptor(desc);
const state = opRevoke(desc); const status = opRevoke(desc);
return cache(desc, state); return cache(desc, status);
} }
request(desc) { request(desc) {
@ -242,8 +254,8 @@ class Permissions {
formDescriptor(desc); formDescriptor(desc);
const state = opRequest(desc); const status = opRequest(desc);
return cache(desc, state); return cache(desc, status);
} }
} }

View file

@ -1,6 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use crate::permissions::parse_sys_kind; use crate::permissions::parse_sys_kind;
use crate::permissions::PermissionState;
use crate::permissions::PermissionsContainer; use crate::permissions::PermissionsContainer;
use deno_core::error::custom_error; use deno_core::error::custom_error;
use deno_core::error::uri_error; use deno_core::error::uri_error;
@ -9,6 +10,7 @@ use deno_core::op;
use deno_core::url; use deno_core::url;
use deno_core::OpState; use deno_core::OpState;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize;
use std::path::Path; use std::path::Path;
deno_core::extension!( deno_core::extension!(
@ -30,11 +32,30 @@ pub struct PermissionArgs {
command: Option<String>, command: Option<String>,
} }
#[derive(Serialize)]
pub struct PermissionStatus {
state: String,
partial: bool,
}
impl From<PermissionState> for PermissionStatus {
fn from(state: PermissionState) -> Self {
PermissionStatus {
state: if state == PermissionState::GrantedPartial {
PermissionState::Granted.to_string()
} else {
state.to_string()
},
partial: state == PermissionState::GrantedPartial,
}
}
}
#[op] #[op]
pub fn op_query_permission( pub fn op_query_permission(
state: &mut OpState, state: &mut OpState,
args: PermissionArgs, args: PermissionArgs,
) -> Result<String, AnyError> { ) -> Result<PermissionStatus, AnyError> {
let permissions = state.borrow::<PermissionsContainer>().0.lock(); let permissions = state.borrow::<PermissionsContainer>().0.lock();
let path = args.path.as_deref(); let path = args.path.as_deref();
let perm = match args.name.as_ref() { let perm = match args.name.as_ref() {
@ -61,14 +82,14 @@ pub fn op_query_permission(
)) ))
} }
}; };
Ok(perm.to_string()) Ok(PermissionStatus::from(perm))
} }
#[op] #[op]
pub fn op_revoke_permission( pub fn op_revoke_permission(
state: &mut OpState, state: &mut OpState,
args: PermissionArgs, args: PermissionArgs,
) -> Result<String, AnyError> { ) -> Result<PermissionStatus, AnyError> {
let mut permissions = state.borrow_mut::<PermissionsContainer>().0.lock(); let mut permissions = state.borrow_mut::<PermissionsContainer>().0.lock();
let path = args.path.as_deref(); let path = args.path.as_deref();
let perm = match args.name.as_ref() { let perm = match args.name.as_ref() {
@ -95,14 +116,14 @@ pub fn op_revoke_permission(
)) ))
} }
}; };
Ok(perm.to_string()) Ok(PermissionStatus::from(perm))
} }
#[op] #[op]
pub fn op_request_permission( pub fn op_request_permission(
state: &mut OpState, state: &mut OpState,
args: PermissionArgs, args: PermissionArgs,
) -> Result<String, AnyError> { ) -> Result<PermissionStatus, AnyError> {
let mut permissions = state.borrow_mut::<PermissionsContainer>().0.lock(); let mut permissions = state.borrow_mut::<PermissionsContainer>().0.lock();
let path = args.path.as_deref(); let path = args.path.as_deref();
let perm = match args.name.as_ref() { let perm = match args.name.as_ref() {
@ -129,7 +150,7 @@ pub fn op_request_permission(
)) ))
} }
}; };
Ok(perm.to_string()) Ok(PermissionStatus::from(perm))
} }
fn parse_host(host_str: &str) -> Result<(String, Option<u16>), AnyError> { fn parse_host(host_str: &str) -> Result<(String, Option<u16>), AnyError> {

File diff suppressed because it is too large Load diff