summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/args/flags.rs130
-rw-r--r--cli/dts/lib.deno.ns.d.ts24
-rw-r--r--cli/dts/lib.deno.unstable.d.ts28
-rw-r--r--cli/tests/unit/network_interfaces_test.ts5
-rw-r--r--cli/tests/unit/os_test.ts62
-rw-r--r--cli/tests/unit/permissions_test.ts17
-rw-r--r--cli/tools/standalone.rs1
-rw-r--r--runtime/js/10_permissions.js7
-rw-r--r--runtime/ops/os.rs45
-rw-r--r--runtime/ops/permissions.rs21
-rw-r--r--runtime/permissions.rs238
11 files changed, 527 insertions, 51 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 513307e92..46fa8c552 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -291,6 +291,7 @@ pub struct Flags {
pub allow_ffi: Option<Vec<PathBuf>>,
pub allow_read: Option<Vec<PathBuf>>,
pub allow_run: Option<Vec<String>>,
+ pub allow_sys: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>,
pub ca_stores: Option<Vec<String>>,
pub ca_file: Option<String>,
@@ -413,6 +414,17 @@ impl Flags {
_ => {}
}
+ match &self.allow_sys {
+ Some(sys_allowlist) if sys_allowlist.is_empty() => {
+ args.push("--allow-sys".to_string());
+ }
+ Some(sys_allowlist) => {
+ let s = format!("--allow-sys={}", sys_allowlist.join(","));
+ args.push(s)
+ }
+ _ => {}
+ }
+
match &self.allow_ffi {
Some(ffi_allowlist) if ffi_allowlist.is_empty() => {
args.push("--allow-ffi".to_string());
@@ -470,6 +482,7 @@ impl Flags {
allow_ffi: self.allow_ffi.clone(),
allow_read: self.allow_read.clone(),
allow_run: self.allow_run.clone(),
+ allow_sys: self.allow_sys.clone(),
allow_write: self.allow_write.clone(),
prompt: !self.no_prompt,
}
@@ -590,6 +603,7 @@ fn handle_repl_flags(flags: &mut Flags, repl_flags: ReplFlags) {
flags.allow_env = Some(vec![]);
flags.allow_run = Some(vec![]);
flags.allow_read = Some(vec![]);
+ flags.allow_sys = Some(vec![]);
flags.allow_write = Some(vec![]);
flags.allow_ffi = Some(vec![]);
flags.allow_hrtime = true;
@@ -1811,6 +1825,27 @@ fn permission_args(app: Command) -> Command {
}),
)
.arg(
+ Arg::new("allow-sys")
+ .long("allow-sys")
+ .min_values(0)
+ .takes_value(true)
+ .use_value_delimiter(true)
+ .require_equals(true)
+ .help("Allow access to system info")
+ .validator(|keys| {
+ for key in keys.split(',') {
+ match key {
+ "hostname" | "osRelease" | "loadavg" | "networkInterfaces"
+ | "systemMemoryInfo" | "getUid" | "getGid" => {}
+ _ => {
+ return Err(format!("unknown system info kind \"{}\"", key));
+ }
+ }
+ }
+ Ok(())
+ }),
+ )
+ .arg(
Arg::new("allow-run")
.long("allow-run")
.min_values(0)
@@ -2367,6 +2402,7 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.allow_env = Some(vec![]);
flags.allow_run = Some(vec![]);
flags.allow_read = Some(vec![]);
+ flags.allow_sys = Some(vec![]);
flags.allow_write = Some(vec![]);
flags.allow_ffi = Some(vec![]);
flags.allow_hrtime = true;
@@ -2870,6 +2906,12 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
debug!("run allowlist: {:#?}", &flags.allow_run);
}
+ if let Some(sys_wl) = matches.values_of("allow-sys") {
+ let sys_allowlist: Vec<String> = sys_wl.map(ToString::to_string).collect();
+ flags.allow_sys = Some(sys_allowlist);
+ debug!("sys info allowlist: {:#?}", &flags.allow_sys);
+ }
+
if let Some(ffi_wl) = matches.values_of("allow-ffi") {
let ffi_allowlist: Vec<PathBuf> = ffi_wl.map(PathBuf::from).collect();
flags.allow_ffi = Some(ffi_allowlist);
@@ -2886,6 +2928,7 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.allow_net = Some(vec![]);
flags.allow_run = Some(vec![]);
flags.allow_write = Some(vec![]);
+ flags.allow_sys = Some(vec![]);
flags.allow_ffi = Some(vec![]);
flags.allow_hrtime = true;
}
@@ -3351,6 +3394,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -3978,6 +4022,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4001,6 +4046,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4025,6 +4071,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4062,6 +4109,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4092,6 +4140,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4115,6 +4164,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4151,6 +4201,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4175,6 +4226,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4203,6 +4255,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -4330,6 +4383,81 @@ mod tests {
}
#[test]
+ fn allow_sys() {
+ let r = flags_from_vec(svec!["deno", "run", "--allow-sys", "script.ts"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Run(RunFlags {
+ script: "script.ts".to_string(),
+ }),
+ allow_sys: Some(vec![]),
+ ..Flags::default()
+ }
+ );
+ }
+
+ #[test]
+ fn allow_sys_allowlist() {
+ let r =
+ flags_from_vec(svec!["deno", "run", "--allow-sys=hostname", "script.ts"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Run(RunFlags {
+ script: "script.ts".to_string(),
+ }),
+ allow_sys: Some(svec!["hostname"]),
+ ..Flags::default()
+ }
+ );
+ }
+
+ #[test]
+ fn allow_sys_allowlist_multiple() {
+ let r = flags_from_vec(svec![
+ "deno",
+ "run",
+ "--allow-sys=hostname,osRelease",
+ "script.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Run(RunFlags {
+ script: "script.ts".to_string(),
+ }),
+ allow_sys: Some(svec!["hostname", "osRelease"]),
+ ..Flags::default()
+ }
+ );
+ }
+
+ #[test]
+ fn allow_sys_allowlist_validator() {
+ let r =
+ flags_from_vec(svec!["deno", "run", "--allow-sys=hostname", "script.ts"]);
+ assert!(r.is_ok());
+ let r = flags_from_vec(svec![
+ "deno",
+ "run",
+ "--allow-sys=hostname,osRelease",
+ "script.ts"
+ ]);
+ assert!(r.is_ok());
+ let r =
+ flags_from_vec(svec!["deno", "run", "--allow-sys=foo", "script.ts"]);
+ assert!(r.is_err());
+ let r = flags_from_vec(svec![
+ "deno",
+ "run",
+ "--allow-sys=hostname,foo",
+ "script.ts"
+ ]);
+ assert!(r.is_err());
+ }
+
+ #[test]
fn reload_validator() {
let r = flags_from_vec(svec![
"deno",
@@ -4931,6 +5059,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
@@ -5012,6 +5141,7 @@ mod tests {
allow_env: Some(vec![]),
allow_run: Some(vec![]),
allow_read: Some(vec![]),
+ allow_sys: Some(vec![]),
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts
index 97d3fed04..095ae139a 100644
--- a/cli/dts/lib.deno.ns.d.ts
+++ b/cli/dts/lib.deno.ns.d.ts
@@ -183,6 +183,15 @@ declare namespace Deno {
*/
env?: "inherit" | boolean | string[];
+ /** Specifies if the `sys` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `sys` permission will be inherited.
+ * If set to `true`, the global `sys` permission will be requested.
+ * If set to `false`, the global `sys` permission will be revoked.
+ *
+ * Defaults to `false`.
+ */
+ sys?: "inherit" | boolean | string[];
+
/** Specifies if the `hrtime` permission should be requested or revoked.
* If set to `"inherit"`, the current `hrtime` permission will be inherited.
* If set to `true`, the global `hrtime` permission will be requested.
@@ -2913,6 +2922,7 @@ declare namespace Deno {
| "write"
| "net"
| "env"
+ | "sys"
| "ffi"
| "hrtime";
@@ -2958,6 +2968,19 @@ declare namespace Deno {
}
/** @category Permissions */
+ export interface SysPermissionDescriptor {
+ name: "sys";
+ kind?:
+ | "loadavg"
+ | "hostname"
+ | "systemMemoryInfo"
+ | "networkInterfaces"
+ | "osRelease"
+ | "getUid"
+ | "getGid";
+ }
+
+ /** @category Permissions */
export interface FfiPermissionDescriptor {
name: "ffi";
path?: string | URL;
@@ -2979,6 +3002,7 @@ declare namespace Deno {
| WritePermissionDescriptor
| NetPermissionDescriptor
| EnvPermissionDescriptor
+ | SysPermissionDescriptor
| FfiPermissionDescriptor
| HrtimePermissionDescriptor;
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index ec87fa9ca..6cb062396 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -255,11 +255,11 @@ declare namespace Deno {
* console.log(Deno.loadavg()); // e.g. [ 0.71, 0.44, 0.44 ]
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
* There are questions around which permission this needs. And maybe should be
* renamed (loadAverage?).
*
- * @tags allow-env
+ * @tags allow-sys
* @category Observability
*/
export function loadavg(): number[];
@@ -272,11 +272,11 @@ declare namespace Deno {
* console.log(Deno.osRelease());
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
* Under consideration to possibly move to Deno.build or Deno.versions and if
* it should depend sys-info, which may not be desirable.
*
- * @tags allow-env
+ * @tags allow-sys
* @category Runtime Environment
*/
export function osRelease(): string;
@@ -292,9 +292,9 @@ declare namespace Deno {
* console.log(Deno.systemMemoryInfo());
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
*
- * @tags allow-env
+ * @tags allow-sys
* @category Runtime Environment
*/
export function systemMemoryInfo(): SystemMemoryInfo;
@@ -355,9 +355,9 @@ declare namespace Deno {
* console.log(Deno.networkInterfaces());
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
*
- * @tags allow-env
+ * @tags allow-sys
* @category Network
*/
export function networkInterfaces(): NetworkInterfaceInfo[];
@@ -370,9 +370,9 @@ declare namespace Deno {
* console.log(Deno.getUid());
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
*
- * @tags allow-env
+ * @tags allow-sys
* @category Runtime Environment
*/
export function getUid(): number | null;
@@ -385,9 +385,9 @@ declare namespace Deno {
* console.log(Deno.getGid());
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
*
- * @tags allow-env
+ * @tags allow-sys
* @category Runtime Environment
*/
export function getGid(): number | null;
@@ -980,11 +980,11 @@ declare namespace Deno {
* console.log(Deno.hostname());
* ```
*
- * Requires `allow-env` permission.
+ * Requires `allow-sys` permission.
* Additional consideration is still necessary around the permissions
* required.
*
- * @tags allow-env
+ * @tags allow-sys
* @category Runtime Environment
*/
export function hostname(): string;
diff --git a/cli/tests/unit/network_interfaces_test.ts b/cli/tests/unit/network_interfaces_test.ts
index 120f58763..a0e6e691a 100644
--- a/cli/tests/unit/network_interfaces_test.ts
+++ b/cli/tests/unit/network_interfaces_test.ts
@@ -1,7 +1,10 @@
import { assert } from "./test_util.ts";
Deno.test(
- { name: "Deno.networkInterfaces", permissions: { env: true } },
+ {
+ name: "Deno.networkInterfaces",
+ permissions: { sys: ["networkInterfaces"] },
+ },
() => {
const networkInterfaces = Deno.networkInterfaces();
assert(Array.isArray(networkInterfaces));
diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts
index bdbf7f0ca..d620ae4e3 100644
--- a/cli/tests/unit/os_test.ts
+++ b/cli/tests/unit/os_test.ts
@@ -187,49 +187,61 @@ Deno.test({ permissions: { read: false } }, function execPathPerm() {
);
});
-Deno.test({ permissions: { env: true } }, function loadavgSuccess() {
- const load = Deno.loadavg();
- assertEquals(load.length, 3);
-});
+Deno.test(
+ { permissions: { sys: ["loadavg"] } },
+ function loadavgSuccess() {
+ const load = Deno.loadavg();
+ assertEquals(load.length, 3);
+ },
+);
-Deno.test({ permissions: { env: false } }, function loadavgPerm() {
+Deno.test({ permissions: { sys: false } }, function loadavgPerm() {
assertThrows(() => {
Deno.loadavg();
}, Deno.errors.PermissionDenied);
});
-Deno.test({ permissions: { env: true } }, function hostnameDir() {
- assertNotEquals(Deno.hostname(), "");
-});
+Deno.test(
+ { permissions: { sys: ["hostname"] } },
+ function hostnameDir() {
+ assertNotEquals(Deno.hostname(), "");
+ },
+);
-Deno.test({ permissions: { env: false } }, function hostnamePerm() {
+Deno.test({ permissions: { sys: false } }, function hostnamePerm() {
assertThrows(() => {
Deno.hostname();
}, Deno.errors.PermissionDenied);
});
-Deno.test({ permissions: { env: true } }, function releaseDir() {
- assertNotEquals(Deno.osRelease(), "");
-});
+Deno.test(
+ { permissions: { sys: ["osRelease"] } },
+ function releaseDir() {
+ assertNotEquals(Deno.osRelease(), "");
+ },
+);
-Deno.test({ permissions: { env: false } }, function releasePerm() {
+Deno.test({ permissions: { sys: false } }, function releasePerm() {
assertThrows(() => {
Deno.osRelease();
}, Deno.errors.PermissionDenied);
});
-Deno.test({ permissions: { env: true } }, function systemMemoryInfo() {
- const info = Deno.systemMemoryInfo();
- assert(info.total >= 0);
- assert(info.free >= 0);
- assert(info.available >= 0);
- assert(info.buffers >= 0);
- assert(info.cached >= 0);
- assert(info.swapTotal >= 0);
- assert(info.swapFree >= 0);
-});
+Deno.test(
+ { permissions: { sys: ["systemMemoryInfo"] } },
+ function systemMemoryInfo() {
+ const info = Deno.systemMemoryInfo();
+ assert(info.total >= 0);
+ assert(info.free >= 0);
+ assert(info.available >= 0);
+ assert(info.buffers >= 0);
+ assert(info.cached >= 0);
+ assert(info.swapTotal >= 0);
+ assert(info.swapFree >= 0);
+ },
+);
-Deno.test({ permissions: { env: true } }, function getUid() {
+Deno.test({ permissions: { sys: ["getUid"] } }, function getUid() {
if (Deno.build.os === "windows") {
assertEquals(Deno.getUid(), null);
} else {
@@ -239,7 +251,7 @@ Deno.test({ permissions: { env: true } }, function getUid() {
}
});
-Deno.test({ permissions: { env: true } }, function getGid() {
+Deno.test({ permissions: { sys: ["getGid"] } }, function getGid() {
if (Deno.build.os === "windows") {
assertEquals(Deno.getGid(), null);
} else {
diff --git a/cli/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts
index 551e9bdcd..c0945bb59 100644
--- a/cli/tests/unit/permissions_test.ts
+++ b/cli/tests/unit/permissions_test.ts
@@ -19,6 +19,23 @@ Deno.test(async function permissionNetInvalidHost() {
}, URIError);
});
+Deno.test(async function permissionSysValidKind() {
+ await Deno.permissions.query({ name: "sys", kind: "loadavg" });
+ await Deno.permissions.query({ name: "sys", kind: "osRelease" });
+ await Deno.permissions.query({ name: "sys", kind: "networkInterfaces" });
+ await Deno.permissions.query({ name: "sys", kind: "systemMemoryInfo" });
+ await Deno.permissions.query({ name: "sys", kind: "hostname" });
+ await Deno.permissions.query({ name: "sys", kind: "getUid" });
+ await Deno.permissions.query({ name: "sys", kind: "getGid" });
+});
+
+Deno.test(async function permissionSysInvalidKind() {
+ await assertRejects(async () => {
+ // deno-lint-ignore no-explicit-any
+ await Deno.permissions.query({ name: "sys", kind: "abc" as any });
+ }, TypeError);
+});
+
Deno.test(async function permissionQueryReturnsEventTarget() {
const status = await Deno.permissions.query({ name: "hrtime" });
assert(["granted", "denied", "prompt"].includes(status.state));
diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs
index 03118f4b6..eeedf8bd5 100644
--- a/cli/tools/standalone.rs
+++ b/cli/tools/standalone.rs
@@ -257,6 +257,7 @@ pub fn compile_to_runtime_flags(
allow_ffi: flags.allow_ffi.clone(),
allow_read: flags.allow_read.clone(),
allow_run: flags.allow_run.clone(),
+ allow_sys: flags.allow_sys.clone(),
allow_write: flags.allow_write.clone(),
ca_stores: flags.ca_stores.clone(),
ca_file: flags.ca_file.clone(),
diff --git a/runtime/js/10_permissions.js b/runtime/js/10_permissions.js
index 2573816df..6aad0677d 100644
--- a/runtime/js/10_permissions.js
+++ b/runtime/js/10_permissions.js
@@ -32,12 +32,13 @@
* @property {PermissionStatus} status
*/
- /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "ffi" | "hrtime">} */
+ /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */
const permissionNames = [
"read",
"write",
"net",
"env",
+ "sys",
"run",
"ffi",
"hrtime",
@@ -132,6 +133,8 @@
key += `-${desc.command}&`;
} else if (desc.name === "env" && desc.variable) {
key += `-${desc.variable}&`;
+ } else if (desc.name === "sys" && desc.kind) {
+ key += `-${desc.kind}&`;
} else {
key += "$";
}
@@ -242,7 +245,7 @@
serializedPermissions[key] = permissions[key];
}
}
- for (const key of ["env", "hrtime", "net"]) {
+ for (const key of ["env", "hrtime", "net", "sys"]) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]);
} else {
diff --git a/runtime/ops/os.rs b/runtime/ops/os.rs
index 14c4229a1..0e51e3120 100644
--- a/runtime/ops/os.rs
+++ b/runtime/ops/os.rs
@@ -161,7 +161,10 @@ fn op_exit(state: &mut OpState) {
#[op]
fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> {
super::check_unstable(state, "Deno.loadavg");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("loadavg", Some("Deno.loadavg()"))?;
match sys_info::loadavg() {
Ok(loadavg) => Ok((loadavg.one, loadavg.five, loadavg.fifteen)),
Err(_) => Ok((0.0, 0.0, 0.0)),
@@ -171,7 +174,10 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> {
#[op]
fn op_hostname(state: &mut OpState) -> Result<String, AnyError> {
super::check_unstable(state, "Deno.hostname");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("hostname", Some("Deno.hostname()"))?;
let hostname = sys_info::hostname().unwrap_or_else(|_| "".to_string());
Ok(hostname)
}
@@ -179,7 +185,10 @@ fn op_hostname(state: &mut OpState) -> Result<String, AnyError> {
#[op]
fn op_os_release(state: &mut OpState) -> Result<String, AnyError> {
super::check_unstable(state, "Deno.osRelease");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("osRelease", Some("Deno.osRelease()"))?;
let release = sys_info::os_release().unwrap_or_else(|_| "".to_string());
Ok(release)
}
@@ -189,7 +198,10 @@ fn op_network_interfaces(
state: &mut OpState,
) -> Result<Vec<NetworkInterface>, AnyError> {
super::check_unstable(state, "Deno.networkInterfaces");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("networkInterfaces", Some("Deno.networkInterfaces()"))?;
Ok(netif::up()?.map(NetworkInterface::from).collect())
}
@@ -255,7 +267,10 @@ fn op_system_memory_info(
state: &mut OpState,
) -> Result<Option<MemInfo>, AnyError> {
super::check_unstable(state, "Deno.systemMemoryInfo");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("systemMemoryInfo", Some("Deno.systemMemoryInfo()"))?;
match sys_info::mem_info() {
Ok(info) => Ok(Some(MemInfo {
total: info.total,
@@ -274,7 +289,10 @@ fn op_system_memory_info(
#[op]
fn op_getgid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
super::check_unstable(state, "Deno.getGid");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("getGid", Some("Deno.getGid()"))?;
// TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
@@ -286,7 +304,10 @@ fn op_getgid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[op]
fn op_getgid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
super::check_unstable(state, "Deno.getGid");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("getGid", Some("Deno.getGid()"))?;
Ok(None)
}
@@ -294,7 +315,10 @@ fn op_getgid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[op]
fn op_getuid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
super::check_unstable(state, "Deno.getUid");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("getUid", Some("Deno.getUid()"))?;
// TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
@@ -306,6 +330,9 @@ fn op_getuid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[op]
fn op_getuid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
super::check_unstable(state, "Deno.getUid");
- state.borrow_mut::<Permissions>().env.check_all()?;
+ state
+ .borrow_mut::<Permissions>()
+ .sys
+ .check("getUid", Some("Deno.getUid()"))?;
Ok(None)
}
diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs
index ed1797701..b79330f7f 100644
--- a/runtime/ops/permissions.rs
+++ b/runtime/ops/permissions.rs
@@ -2,6 +2,7 @@
use crate::permissions::Permissions;
use deno_core::error::custom_error;
+use deno_core::error::type_error;
use deno_core::error::uri_error;
use deno_core::error::AnyError;
use deno_core::op;
@@ -27,6 +28,7 @@ pub struct PermissionArgs {
path: Option<String>,
host: Option<String>,
variable: Option<String>,
+ kind: Option<String>,
command: Option<String>,
}
@@ -48,6 +50,7 @@ pub fn op_query_permission(
.as_ref(),
),
"env" => permissions.env.query(args.variable.as_deref()),
+ "sys" => permissions.sys.query(parse_sys_kind(args.kind.as_deref())?),
"run" => permissions.run.query(args.command.as_deref()),
"ffi" => permissions.ffi.query(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.query(),
@@ -79,6 +82,9 @@ pub fn op_revoke_permission(
.as_ref(),
),
"env" => permissions.env.revoke(args.variable.as_deref()),
+ "sys" => permissions
+ .sys
+ .revoke(parse_sys_kind(args.kind.as_deref())?),
"run" => permissions.run.revoke(args.command.as_deref()),
"ffi" => permissions.ffi.revoke(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.revoke(),
@@ -110,6 +116,9 @@ pub fn op_request_permission(
.as_ref(),
),
"env" => permissions.env.request(args.variable.as_deref()),
+ "sys" => permissions
+ .sys
+ .request(parse_sys_kind(args.kind.as_deref())?),
"run" => permissions.run.request(args.command.as_deref()),
"ffi" => permissions.ffi.request(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.request(),
@@ -132,3 +141,15 @@ fn parse_host(host_str: &str) -> Result<(String, Option<u16>), AnyError> {
let hostname = url.host_str().unwrap();
Ok((hostname.to_string(), url.port()))
}
+
+fn parse_sys_kind(kind: Option<&str>) -> Result<Option<&str>, AnyError> {
+ if let Some(kind) = kind {
+ match kind {
+ "hostname" | "osRelease" | "loadavg" | "networkInterfaces"
+ | "systemMemoryInfo" | "getUid" | "getGid" => Ok(Some(kind)),
+ _ => Err(type_error(format!("unknown system info kind \"{}\"", kind))),
+ }
+ } else {
+ Ok(kind)
+ }
+}
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index a50295910..84ff286b4 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -302,6 +302,9 @@ impl ToString for RunDescriptor {
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
+pub struct SysDescriptor(pub String);
+
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct FfiDescriptor(pub PathBuf);
impl UnaryPermission<ReadDescriptor> {
@@ -928,6 +931,128 @@ impl Default for UnaryPermission<EnvDescriptor> {
}
}
+impl UnaryPermission<SysDescriptor> {
+ pub fn query(&self, kind: Option<&str>) -> PermissionState {
+ if self.global_state == PermissionState::Denied
+ && match kind {
+ None => true,
+ Some(kind) => {
+ self.denied_list.contains(&SysDescriptor(kind.to_string()))
+ }
+ }
+ {
+ PermissionState::Denied
+ } else if self.global_state == PermissionState::Granted
+ || match kind {
+ None => false,
+ Some(kind) => {
+ self.granted_list.contains(&SysDescriptor(kind.to_string()))
+ }
+ }
+ {
+ PermissionState::Granted
+ } else {
+ PermissionState::Prompt
+ }
+ }
+
+ pub fn request(&mut self, kind: Option<&str>) -> PermissionState {
+ let state = self.query(kind);
+ if state != PermissionState::Prompt {
+ return state;
+ }
+ if let Some(kind) = kind {
+ let desc = SysDescriptor(kind.to_string());
+ if permission_prompt(
+ &format!("sys access to \"{}\"", kind),
+ self.name,
+ Some("Deno.permissions.query()"),
+ ) {
+ self.granted_list.insert(desc);
+ PermissionState::Granted
+ } else {
+ self.denied_list.insert(desc);
+ self.global_state = PermissionState::Denied;
+ PermissionState::Denied
+ }
+ } else {
+ if permission_prompt(
+ "sys access",
+ self.name,
+ Some("Deno.permissions.query()"),
+ ) {
+ self.global_state = PermissionState::Granted;
+ } else {
+ self.granted_list.clear();
+ self.global_state = PermissionState::Denied;
+ }
+ self.global_state
+ }
+ }
+
+ pub fn revoke(&mut self, kind: Option<&str>) -> PermissionState {
+ if let Some(kind) = kind {
+ self.granted_list.remove(&SysDescriptor(kind.to_string()));
+ } else {
+ self.granted_list.clear();
+ }
+ if self.global_state == PermissionState::Granted {
+ self.global_state = PermissionState::Prompt;
+ }
+ self.query(kind)
+ }
+
+ pub fn check(
+ &mut self,
+ kind: &str,
+ api_name: Option<&str>,
+ ) -> Result<(), AnyError> {
+ let (result, prompted) = self.query(Some(kind)).check(
+ self.name,
+ api_name,
+ Some(&format!("\"{}\"", kind)),
+ self.prompt,
+ );
+ if prompted {
+ if result.is_ok() {
+ self.granted_list.insert(SysDescriptor(kind.to_string()));
+ } else {
+ self.denied_list.insert(SysDescriptor(kind.to_string()));
+ self.global_state = PermissionState::Denied;
+ }
+ }
+ result
+ }
+
+ pub fn check_all(&mut self) -> Result<(), AnyError> {
+ let (result, prompted) =
+ self
+ .query(None)
+ .check(self.name, None, Some("all"), self.prompt);
+ if prompted {
+ if result.is_ok() {
+ self.global_state = PermissionState::Granted;
+ } else {
+ self.global_state = PermissionState::Denied;
+ }
+ }
+ result
+ }
+}
+
+impl Default for UnaryPermission<SysDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<SysDescriptor> {
+ name: "sys",
+ description: "system information",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
+}
+
impl UnaryPermission<RunDescriptor> {
pub fn query(&self, cmd: Option<&str>) -> PermissionState {
if self.global_state == PermissionState::Denied
@@ -1221,6 +1346,7 @@ pub struct Permissions {
pub write: UnaryPermission<WriteDescriptor>,
pub net: UnaryPermission<NetDescriptor>,
pub env: UnaryPermission<EnvDescriptor>,
+ pub sys: UnaryPermission<SysDescriptor>,
pub run: UnaryPermission<RunDescriptor>,
pub ffi: UnaryPermission<FfiDescriptor>,
pub hrtime: UnitPermission,
@@ -1233,6 +1359,7 @@ impl Default for Permissions {
write: Permissions::new_write(&None, false).unwrap(),
net: Permissions::new_net(&None, false).unwrap(),
env: Permissions::new_env(&None, false).unwrap(),
+ sys: Permissions::new_sys(&None, false).unwrap(),
run: Permissions::new_run(&None, false).unwrap(),
ffi: Permissions::new_ffi(&None, false).unwrap(),
hrtime: Permissions::new_hrtime(false),
@@ -1248,6 +1375,7 @@ pub struct PermissionsOptions {
pub allow_ffi: Option<Vec<PathBuf>>,
pub allow_read: Option<Vec<PathBuf>>,
pub allow_run: Option<Vec<String>>,
+ pub allow_sys: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>,
pub prompt: bool,
}
@@ -1321,6 +1449,31 @@ impl Permissions {
})
}
+ pub fn new_sys(
+ state: &Option<Vec<String>>,
+ prompt: bool,
+ ) -> Result<UnaryPermission<SysDescriptor>, AnyError> {
+ Ok(UnaryPermission::<SysDescriptor> {
+ global_state: global_state_from_option(state),
+ granted_list: state.as_ref().map_or_else(
+ || Ok(HashSet::new()),
+ |v| {
+ v.iter()
+ .map(|x| {
+ if x.is_empty() {
+ Err(AnyError::msg("emtpy"))
+ } else {
+ Ok(SysDescriptor(x.to_string()))
+ }
+ })
+ .collect()
+ },
+ )?,
+ prompt,
+ ..Default::default()
+ })
+ }
+
pub fn new_run(
state: &Option<Vec<String>>,
prompt: bool,
@@ -1373,6 +1526,7 @@ impl Permissions {
write: Permissions::new_write(&opts.allow_write, opts.prompt)?,
net: Permissions::new_net(&opts.allow_net, opts.prompt)?,
env: Permissions::new_env(&opts.allow_env, opts.prompt)?,
+ sys: Permissions::new_sys(&opts.allow_sys, opts.prompt)?,
run: Permissions::new_run(&opts.allow_run, opts.prompt)?,
ffi: Permissions::new_ffi(&opts.allow_ffi, opts.prompt)?,
hrtime: Permissions::new_hrtime(opts.allow_hrtime),
@@ -1385,6 +1539,7 @@ impl Permissions {
write: Permissions::new_write(&Some(vec![]), false).unwrap(),
net: Permissions::new_net(&Some(vec![]), false).unwrap(),
env: Permissions::new_env(&Some(vec![]), false).unwrap(),
+ sys: Permissions::new_sys(&Some(vec![]), false).unwrap(),
run: Permissions::new_run(&Some(vec![]), false).unwrap(),
ffi: Permissions::new_ffi(&Some(vec![]), false).unwrap(),
hrtime: Permissions::new_hrtime(true),
@@ -1722,6 +1877,7 @@ pub struct ChildPermissionsArg {
ffi: ChildUnaryPermissionArg,
read: ChildUnaryPermissionArg,
run: ChildUnaryPermissionArg,
+ sys: ChildUnaryPermissionArg,
write: ChildUnaryPermissionArg,
}
@@ -1734,6 +1890,7 @@ impl ChildPermissionsArg {
ffi: ChildUnaryPermissionArg::Inherit,
read: ChildUnaryPermissionArg::Inherit,
run: ChildUnaryPermissionArg::Inherit,
+ sys: ChildUnaryPermissionArg::Inherit,
write: ChildUnaryPermissionArg::Inherit,
}
}
@@ -1746,6 +1903,7 @@ impl ChildPermissionsArg {
ffi: ChildUnaryPermissionArg::NotGranted,
read: ChildUnaryPermissionArg::NotGranted,
run: ChildUnaryPermissionArg::NotGranted,
+ sys: ChildUnaryPermissionArg::NotGranted,
write: ChildUnaryPermissionArg::NotGranted,
}
}
@@ -1822,6 +1980,11 @@ impl<'de> Deserialize<'de> for ChildPermissionsArg {
child_permissions_arg.run = arg.map_err(|e| {
de::Error::custom(format!("(deno.permissions.run) {}", e))
})?;
+ } else if key == "sys" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.sys = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.sys) {}", e))
+ })?;
} else if key == "write" {
let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
child_permissions_arg.write = arg.map_err(|e| {
@@ -1872,6 +2035,35 @@ pub fn create_child_permissions(
worker_perms.env.global_state = PermissionState::Denied;
}
worker_perms.env.prompt = main_perms.env.prompt;
+ match child_permissions_arg.sys {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.sys = main_perms.sys.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.sys.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.sys.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.sys.granted_list =
+ Permissions::new_sys(&Some(granted_list), false)?.granted_list;
+ if !worker_perms
+ .sys
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.sys.check(&desc.0, None).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.sys.denied_list = main_perms.sys.denied_list.clone();
+ if main_perms.sys.global_state == PermissionState::Denied {
+ worker_perms.sys.global_state = PermissionState::Denied;
+ }
+ worker_perms.sys.prompt = main_perms.sys.prompt;
match child_permissions_arg.hrtime {
ChildUnitPermissionArg::Inherit => {
worker_perms.hrtime = main_perms.hrtime.clone();
@@ -2608,6 +2800,10 @@ mod tests {
global_state: PermissionState::Prompt,
..Permissions::new_env(&Some(svec!["HOME"]), false).unwrap()
},
+ sys: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ ..Permissions::new_sys(&Some(svec!["hostname"]), false).unwrap()
+ },
run: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_run(&Some(svec!["deno"]), false).unwrap()
@@ -2642,6 +2838,10 @@ mod tests {
assert_eq!(perms1.env.query(Some("HOME")), PermissionState::Granted);
assert_eq!(perms2.env.query(None), PermissionState::Prompt);
assert_eq!(perms2.env.query(Some("HOME")), PermissionState::Granted);
+ assert_eq!(perms1.sys.query(None), PermissionState::Granted);
+ assert_eq!(perms1.sys.query(Some("HOME")), PermissionState::Granted);
+ assert_eq!(perms2.env.query(None), PermissionState::Prompt);
+ assert_eq!(perms2.sys.query(Some("hostname")), PermissionState::Granted);
assert_eq!(perms1.run.query(None), PermissionState::Granted);
assert_eq!(perms1.run.query(Some("deno")), PermissionState::Granted);
assert_eq!(perms2.run.query(None), PermissionState::Prompt);
@@ -2681,6 +2881,11 @@ mod tests {
prompt_value.set(false);
assert_eq!(perms.env.request(Some("HOME")), PermissionState::Granted);
prompt_value.set(true);
+ assert_eq!(perms.sys.request(Some("hostname")), PermissionState::Granted);
+ assert_eq!(perms.sys.query(None), PermissionState::Prompt);
+ prompt_value.set(false);
+ assert_eq!(perms.sys.request(Some("hostname")), PermissionState::Granted);
+ prompt_value.set(true);
assert_eq!(perms.run.request(Some("deno")), PermissionState::Granted);
assert_eq!(perms.run.query(None), PermissionState::Prompt);
prompt_value.set(false);
@@ -2728,6 +2933,10 @@ mod tests {
global_state: PermissionState::Prompt,
..Permissions::new_env(&Some(svec!["HOME"]), false).unwrap()
},
+ sys: UnaryPermission {
+ global_state: PermissionState::Prompt,
+ ..Permissions::new_sys(&Some(svec!["hostname"]), false).unwrap()
+ },
run: UnaryPermission {
global_state: PermissionState::Prompt,
..Permissions::new_run(&Some(svec!["deno"]), false).unwrap()
@@ -2754,6 +2963,7 @@ mod tests {
assert_eq!(perms.net.query(Some(&("127.0.0.1", None))), PermissionState::Prompt);
assert_eq!(perms.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted);
assert_eq!(perms.env.revoke(Some("HOME")), PermissionState::Prompt);
+ assert_eq!(perms.env.revoke(Some("hostname")), PermissionState::Prompt);
assert_eq!(perms.run.revoke(Some("deno")), PermissionState::Prompt);
assert_eq!(perms.ffi.revoke(Some(Path::new("deno"))), PermissionState::Prompt);
assert_eq!(perms.hrtime.revoke(), PermissionState::Denied);
@@ -2767,6 +2977,7 @@ mod tests {
write: Permissions::new_write(&None, true).unwrap(),
net: Permissions::new_net(&None, true).unwrap(),
env: Permissions::new_env(&None, true).unwrap(),
+ sys: Permissions::new_sys(&None, true).unwrap(),
run: Permissions::new_run(&None, true).unwrap(),
ffi: Permissions::new_ffi(&None, true).unwrap(),
hrtime: Permissions::new_hrtime(false),
@@ -2807,6 +3018,12 @@ mod tests {
assert!(perms.env.check("HOME").is_ok());
assert!(perms.env.check("PATH").is_err());
+ prompt_value.set(true);
+ assert!(perms.env.check("hostname").is_ok());
+ prompt_value.set(false);
+ assert!(perms.env.check("hostname").is_ok());
+ assert!(perms.env.check("osRelease").is_err());
+
assert!(perms.hrtime.check().is_err());
}
@@ -2817,6 +3034,7 @@ mod tests {
write: Permissions::new_write(&None, true).unwrap(),
net: Permissions::new_net(&None, true).unwrap(),
env: Permissions::new_env(&None, true).unwrap(),
+ sys: Permissions::new_sys(&None, true).unwrap(),
run: Permissions::new_run(&None, true).unwrap(),
ffi: Permissions::new_ffi(&None, true).unwrap(),
hrtime: Permissions::new_hrtime(false),
@@ -2867,6 +3085,14 @@ mod tests {
assert!(perms.env.check("PATH").is_ok());
prompt_value.set(false);
+ assert!(perms.sys.check("hostname", None).is_err());
+ prompt_value.set(true);
+ assert!(perms.sys.check("hostname", None).is_err());
+ assert!(perms.sys.check("osRelease", None).is_ok());
+ prompt_value.set(false);
+ assert!(perms.sys.check("osRelease", None).is_ok());
+
+ prompt_value.set(false);
assert!(perms.hrtime.check().is_err());
prompt_value.set(true);
assert!(perms.hrtime.check().is_err());
@@ -2902,6 +3128,7 @@ mod tests {
ffi: ChildUnaryPermissionArg::Inherit,
read: ChildUnaryPermissionArg::Inherit,
run: ChildUnaryPermissionArg::Inherit,
+ sys: ChildUnaryPermissionArg::Inherit,
write: ChildUnaryPermissionArg::Inherit,
}
);
@@ -2914,6 +3141,7 @@ mod tests {
ffi: ChildUnaryPermissionArg::NotGranted,
read: ChildUnaryPermissionArg::NotGranted,
run: ChildUnaryPermissionArg::NotGranted,
+ sys: ChildUnaryPermissionArg::NotGranted,
write: ChildUnaryPermissionArg::NotGranted,
}
);
@@ -2966,6 +3194,7 @@ mod tests {
"ffi": true,
"read": true,
"run": true,
+ "sys": true,
"write": true,
}))
.unwrap(),
@@ -2975,6 +3204,7 @@ mod tests {
ffi: ChildUnaryPermissionArg::Granted,
read: ChildUnaryPermissionArg::Granted,
run: ChildUnaryPermissionArg::Granted,
+ sys: ChildUnaryPermissionArg::Granted,
write: ChildUnaryPermissionArg::Granted,
..ChildPermissionsArg::none()
}
@@ -2986,6 +3216,7 @@ mod tests {
"ffi": false,
"read": false,
"run": false,
+ "sys": false,
"write": false,
}))
.unwrap(),
@@ -2995,6 +3226,7 @@ mod tests {
ffi: ChildUnaryPermissionArg::NotGranted,
read: ChildUnaryPermissionArg::NotGranted,
run: ChildUnaryPermissionArg::NotGranted,
+ sys: ChildUnaryPermissionArg::NotGranted,
write: ChildUnaryPermissionArg::NotGranted,
..ChildPermissionsArg::none()
}
@@ -3006,6 +3238,7 @@ mod tests {
"ffi": ["foo", "file:///bar/baz"],
"read": ["foo", "file:///bar/baz"],
"run": ["foo", "file:///bar/baz", "./qux"],
+ "sys": ["hostname", "osRelease"],
"write": ["foo", "file:///bar/baz"],
}))
.unwrap(),
@@ -3025,6 +3258,10 @@ mod tests {
"file:///bar/baz",
"./qux"
]),
+ sys: ChildUnaryPermissionArg::GrantedList(svec![
+ "hostname",
+ "osRelease"
+ ]),
write: ChildUnaryPermissionArg::GrantedList(svec![
"foo",
"file:///bar/baz"
@@ -3129,6 +3366,7 @@ mod tests {
fn test_handle_empty_value() {
assert!(Permissions::new_read(&Some(vec![PathBuf::new()]), false).is_err());
assert!(Permissions::new_env(&Some(vec![String::new()]), false).is_err());
+ assert!(Permissions::new_sys(&Some(vec![String::new()]), false).is_err());
assert!(Permissions::new_run(&Some(vec![String::new()]), false).is_err());
assert!(Permissions::new_ffi(&Some(vec![PathBuf::new()]), false).is_err());
assert!(Permissions::new_net(&Some(svec![String::new()]), false).is_err());