1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

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 super::flags_allow_net;
use super::flags_net;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileFlags {
@ -364,13 +364,21 @@ pub struct Flags {
pub allow_all: bool,
pub allow_env: Option<Vec<String>>,
pub deny_env: Option<Vec<String>>,
pub allow_hrtime: bool,
pub deny_hrtime: bool,
pub allow_net: Option<Vec<String>>,
pub deny_net: Option<Vec<String>>,
pub allow_ffi: Option<Vec<PathBuf>>,
pub deny_ffi: Option<Vec<PathBuf>>,
pub allow_read: Option<Vec<PathBuf>>,
pub deny_read: Option<Vec<PathBuf>>,
pub allow_run: Option<Vec<String>>,
pub deny_run: Option<Vec<String>>,
pub allow_sys: Option<Vec<String>>,
pub deny_sys: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>,
pub deny_write: Option<Vec<PathBuf>>,
pub ca_stores: Option<Vec<String>>,
pub ca_data: Option<CaData>,
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 {
Some(write_allowlist) if write_allowlist.is_empty() => {
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 {
Some(net_allowlist) if net_allowlist.is_empty() => {
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 {
Some(ic_allowlist) if ic_allowlist.is_empty() => {
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 {
Some(run_allowlist) if run_allowlist.is_empty() => {
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 {
Some(sys_allowlist) if sys_allowlist.is_empty() => {
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 {
Some(ffi_allowlist) if ffi_allowlist.is_empty() => {
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 {
args.push("--allow-hrtime".to_string());
}
if self.deny_hrtime {
args.push("--deny-hrtime".to_string());
}
args
}
@ -607,26 +696,42 @@ impl Flags {
pub fn has_permission(&self) -> bool {
self.allow_all
|| self.allow_hrtime
|| self.deny_hrtime
|| self.allow_env.is_some()
|| self.deny_env.is_some()
|| self.allow_ffi.is_some()
|| self.deny_ffi.is_some()
|| self.allow_net.is_some()
|| self.deny_net.is_some()
|| self.allow_read.is_some()
|| self.deny_read.is_some()
|| self.allow_run.is_some()
|| self.deny_run.is_some()
|| self.allow_sys.is_some()
|| self.deny_sys.is_some()
|| self.allow_write.is_some()
|| self.deny_write.is_some()
}
pub fn has_permission_in_argv(&self) -> bool {
self.argv.iter().any(|arg| {
arg == "--allow-all"
|| arg == "--allow-hrtime"
|| arg == "--deny-hrtime"
|| arg.starts_with("--allow-env")
|| arg.starts_with("--deny-env")
|| arg.starts_with("--allow-ffi")
|| arg.starts_with("--deny-ffi")
|| arg.starts_with("--allow-net")
|| arg.starts_with("--deny-net")
|| arg.starts_with("--allow-read")
|| arg.starts_with("--deny-read")
|| arg.starts_with("--allow-run")
|| arg.starts_with("--deny-run")
|| arg.starts_with("--allow-sys")
|| arg.starts_with("--deny-sys")
|| 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\""
);
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!(
"Allow file system write access. Optionally specify allowed paths.\n",
"Docs: https://deno.land/manual@v",
@ -2047,6 +2162,16 @@ static ALLOW_WRITE_HELP: &str = concat!(
" --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!(
"Allow network access. Optionally specify allowed IP addresses and host names, with ports as necessary.\n",
"Docs: https://deno.land/manual@v",
@ -2057,6 +2182,16 @@ static ALLOW_NET_HELP: &str = concat!(
" --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!(
"Allow access to system environment information. Optionally specify accessible environment variables.\n",
"Docs: https://deno.land/manual@v",
@ -2067,6 +2202,16 @@ static ALLOW_ENV_HELP: &str = concat!(
" --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!(
"Allow access to OS information. Optionally allow specific APIs by function name.\n",
"Docs: https://deno.land/manual@v",
@ -2077,6 +2222,16 @@ static ALLOW_SYS_HELP: &str = concat!(
" --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!(
"Allow running subprocesses. Optionally specify allowed runnable program names.\n",
"Docs: https://deno.land/manual@v",
@ -2087,6 +2242,16 @@ static ALLOW_RUN_HELP: &str = concat!(
" --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!(
"(Unstable) Allow loading dynamic libraries. Optionally specify allowed directories or files.\n",
"Docs: https://deno.land/manual@v",
@ -2097,6 +2262,16 @@ static ALLOW_FFI_HELP: &str = concat!(
" --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!(
"Allow high-resolution time measurement. Note: this can enable timing attacks and fingerprinting.\n",
"Docs: https://deno.land/manual@v",
@ -2104,6 +2279,13 @@ static ALLOW_HRTIME_HELP: &str = concat!(
"/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!(
"Allow all permissions. Learn more about permissions in Deno:\n",
"https://deno.land/manual@v",
@ -2124,6 +2306,17 @@ fn permission_args(app: Command) -> Command {
.value_parser(value_parser!(PathBuf))
.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::new("allow-write")
.long("allow-write")
@ -2135,6 +2328,17 @@ fn permission_args(app: Command) -> Command {
.value_parser(value_parser!(PathBuf))
.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::new("allow-net")
.long("allow-net")
@ -2143,7 +2347,17 @@ fn permission_args(app: Command) -> Command {
.require_equals(true)
.value_name("IP_OR_HOSTNAME")
.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(
@ -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::new("allow-sys")
.long("allow-sys")
@ -2176,6 +2410,16 @@ fn permission_args(app: Command) -> Command {
.help(ALLOW_SYS_HELP)
.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::new("allow-run")
.long("allow-run")
@ -2185,6 +2429,15 @@ fn permission_args(app: Command) -> Command {
.value_name("PROGRAM_NAME")
.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::new("allow-ffi")
.long("allow-ffi")
@ -2196,12 +2449,29 @@ fn permission_args(app: Command) -> Command {
.value_parser(value_parser!(PathBuf))
.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::new("allow-hrtime")
.long("allow-hrtime")
.action(ArgAction::SetTrue)
.help(ALLOW_HRTIME_HELP),
)
.arg(
Arg::new("deny-hrtime")
.long("deny-hrtime")
.action(ArgAction::SetTrue)
.help(DENY_HRTIME_HELP),
)
.arg(
Arg::new("allow-all")
.short('A')
@ -2594,7 +2864,7 @@ fn unsafely_ignore_certificate_errors_arg() -> Arg {
.require_equals(true)
.value_name("HOSTNAMES")
.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) {
@ -3189,38 +3459,76 @@ fn permission_args_parse(flags: &mut Flags, matches: &mut ArgMatches) {
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") {
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") {
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);
}
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") {
flags.allow_env = Some(env_wl.collect());
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") {
flags.allow_run = Some(run_wl.collect());
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") {
flags.allow_sys = Some(sys_wl.collect());
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") {
flags.allow_ffi = Some(ffi_wl.collect());
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") {
flags.allow_hrtime = true;
}
if matches.get_flag("deny-hrtime") {
flags.deny_hrtime = true;
}
if matches.get_flag("allow-all") {
flags.allow_all = true;
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_hrtime = true;
}
if matches.get_flag("no-prompt") {
flags.no_prompt = true;
}
@ -3243,7 +3552,7 @@ fn unsafely_ignore_certificate_errors_parse(
if let Some(ic_wl) =
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);
}
}
@ -3694,6 +4003,9 @@ mod tests {
let r = flags_from_vec(svec!["deno", "run", "--allow-read", "x.ts"]);
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"]);
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"]);
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"]);
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]
fn allow_hrtime() {
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]
fn double_hyphen() {
// notice that flags passed after double dash will not
@ -4687,11 +5034,17 @@ mod tests {
allow_net: Some(vec![]),
unsafely_ignore_certificate_errors: None,
allow_env: Some(vec![]),
deny_env: None,
allow_run: Some(vec![]),
deny_run: None,
allow_read: Some(vec![]),
deny_read: None,
allow_sys: Some(vec![]),
deny_sys: None,
allow_write: Some(vec![]),
deny_write: None,
allow_ffi: Some(vec![]),
deny_ffi: None,
allow_hrtime: true,
..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]
fn allow_write_allowlist() {
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]
fn allow_net_allowlist() {
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]
fn allow_env_allowlist() {
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]
fn allow_env_allowlist_multiple() {
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]
fn allow_env_allowlist_validator() {
let r =
@ -4901,6 +5355,19 @@ mod tests {
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]
fn allow_sys() {
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]
fn allow_sys_allowlist() {
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]
fn allow_sys_allowlist_multiple() {
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]
fn allow_sys_allowlist_validator() {
let r =
@ -4979,6 +5500,29 @@ mod tests {
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]
fn reload_validator() {
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]
fn allow_net_allowlist_with_ipv6_address() {
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]
fn lock_write() {
let r = flags_from_vec(svec![

View file

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

View file

@ -601,7 +601,7 @@ fn _090_run_permissions_request() {
.with_pty(|mut console| {
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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(concat!(
"┌ ⚠️ 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",
"└ 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| {
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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(concat!(
"┌ ⚠️ 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",
"└ 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
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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.");
// "net" permissions
console.expect(concat!(
"┌ ⚠️ Deno requests network access to \"foo\".\r\n",
"├ Requested by `Deno.permissions.query()` API.\r\n",
"┌ ⚠️ Deno requests net access to \"foo\".\r\n",
"├ Requested by `Deno.permissions.request()` API.\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)",
));
@ -692,7 +692,7 @@ fn permissions_prompt_allow_all() {
// "env" permissions
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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 {
args: "run --check run/091_use_define_for_class_fields.ts",
output: "run/091_use_define_for_class_fields.ts.out",
@ -2541,14 +2571,14 @@ mod permissions {
.with_pty(|mut console| {
console.expect(concat!(
"┌ ⚠️ 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",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
));
console.write_line_raw("y");
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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| {
console.expect(concat!(
"┌ ⚠️ 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",
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
));
console.write_line_raw("y");
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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| {
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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| {
console.expect(concat!(
"┌ ⚠️ 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",
"└ 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.expect(concat!(
"┌ ⚠️ 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",
"└ 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
*/
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`
* allows scoping the permission to a specific executable.
*
@ -4416,12 +4419,12 @@ declare namespace Deno {
* @category Permissions */
export interface RunPermissionDescriptor {
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. */
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
* scoping the permission to a specific path (and if the path is a directory
* any sub paths).
@ -4432,12 +4435,12 @@ declare namespace Deno {
* @category Permissions */
export interface ReadPermissionDescriptor {
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). */
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
* `path` allow scoping the permission to a specific path (and if the path is
* a directory any sub paths).
@ -4448,12 +4451,12 @@ declare namespace Deno {
* @category Permissions */
export interface WritePermissionDescriptor {
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). */
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
* network. The option `host` allows scoping the permission for outbound
* connection to a specific host and port.
@ -4469,7 +4472,7 @@ declare namespace Deno {
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
* as well as access other information about the environment. The option
* `variable` allows scoping the permission to a specific environment
@ -4482,7 +4485,7 @@ declare namespace Deno {
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
* attempt to exploit. The option `kind` allows scoping the permission to a
* specific piece of information.
@ -4502,7 +4505,7 @@ declare namespace Deno {
| "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
* [Foreign Function Interface API](https://deno.land/manual/runtime/ffi_api)
* available in Deno. The option `path` allows scoping the permission to a
@ -4515,7 +4518,7 @@ declare namespace Deno {
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
* resolution time is considered sensitive information, because it can be used
* 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
onchange: ((this: PermissionStatus, ev: Event) => any) | null;
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>(
type: K,
listener: (

View file

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

View file

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

View file

@ -144,7 +144,7 @@ where
check_unstable(state, "Deno.dlopen");
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| {
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 {
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()>;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use crate::permissions::parse_sys_kind;
use crate::permissions::PermissionState;
use crate::permissions::PermissionsContainer;
use deno_core::error::custom_error;
use deno_core::error::uri_error;
@ -9,6 +10,7 @@ use deno_core::op;
use deno_core::url;
use deno_core::OpState;
use serde::Deserialize;
use serde::Serialize;
use std::path::Path;
deno_core::extension!(
@ -30,11 +32,30 @@ pub struct PermissionArgs {
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]
pub fn op_query_permission(
state: &mut OpState,
args: PermissionArgs,
) -> Result<String, AnyError> {
) -> Result<PermissionStatus, AnyError> {
let permissions = state.borrow::<PermissionsContainer>().0.lock();
let path = args.path.as_deref();
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]
pub fn op_revoke_permission(
state: &mut OpState,
args: PermissionArgs,
) -> Result<String, AnyError> {
) -> Result<PermissionStatus, AnyError> {
let mut permissions = state.borrow_mut::<PermissionsContainer>().0.lock();
let path = args.path.as_deref();
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]
pub fn op_request_permission(
state: &mut OpState,
args: PermissionArgs,
) -> Result<String, AnyError> {
) -> Result<PermissionStatus, AnyError> {
let mut permissions = state.borrow_mut::<PermissionsContainer>().0.lock();
let path = args.path.as_deref();
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> {

File diff suppressed because it is too large Load diff