Skip to content

Commit 59ed57d

Browse files
authored
Merge pull request #2093 from harehare/feat/allow-all-flag
✨ feat(mq-lang,mq-run): add --allow-all flag to grant every sandbox permission
2 parents 4db2e03 + f60065d commit 59ed57d

3 files changed

Lines changed: 95 additions & 5 deletions

File tree

crates/mq-lang/src/io/sandboxed.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ impl<Inner: Io> SandboxedIo<Inner> {
7979
self
8080
}
8181

82+
/// Grants every permission at once (read/write/net/run/env), fully and
83+
/// unrestricted, matching `--allow-all`.
84+
pub fn allow_all(self) -> Self {
85+
self.allow_read(true)
86+
.allow_write(true)
87+
.allow_net(true)
88+
.allow_run(true)
89+
.allow_env(true)
90+
}
91+
8292
/// Whether any read access is granted (fully or restricted to an allowlist).
8393
pub fn is_read_allowed(&self) -> bool {
8494
!self.allow_read.is_denied()
@@ -405,4 +415,14 @@ mod tests {
405415
let io = io.allow_run(true);
406416
assert_eq!(io.execute("echo", &["hi".to_string()]).unwrap(), "hi");
407417
}
418+
419+
#[test]
420+
fn test_allow_all_grants_every_permission() {
421+
let io = SandboxedIo::new(MemIo::default()).allow_all();
422+
assert!(io.is_read_allowed());
423+
assert!(io.is_write_allowed());
424+
assert!(io.is_net_allowed());
425+
assert!(io.is_run_allowed());
426+
assert!(io.is_env_allowed());
427+
}
408428
}

crates/mq-run/src/cli.rs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,16 @@ struct InputArgs {
411411
/// swallowed as a query/file positional instead.
412412
#[arg(long = "allow-env", num_args = 0.., require_equals = true, value_delimiter = ',', value_name = "NAME")]
413413
allow_env: Option<Vec<String>>,
414+
415+
/// Grant every sandboxed permission at once (read/write/net/run/env). Disabled by
416+
/// default. Cannot be combined with the individual --allow-* flags above.
417+
#[arg(
418+
short = 'a',
419+
long = "allow-all",
420+
default_value_t = false,
421+
conflicts_with_all = ["allow_net", "allow_read", "allow_write", "allow_run", "allow_env"]
422+
)]
423+
allow_all: bool,
414424
}
415425

416426
#[derive(Clone, Debug, clap::Args, Default)]
@@ -839,15 +849,19 @@ impl Cli {
839849
// full native access (unchanged from before this Io existed) — --allow-read/
840850
// write/net/run instead gate what a running query's read_file/write_file/http/
841851
// system builtins can do at runtime, via the ambient Io set below.
842-
let mut engine = mq_lang::DefaultEngine::default();
843-
engine.set_io(Shared::new(
844-
mq_lang::SandboxedIo::new(mq_lang::NativeIo::default())
852+
let sandboxed_io = mq_lang::SandboxedIo::new(mq_lang::NativeIo::default());
853+
let sandboxed_io = if self.input.allow_all {
854+
sandboxed_io.allow_all()
855+
} else {
856+
sandboxed_io
845857
.allow_read(self.input.allow_read.clone())
846858
.allow_write(self.input.allow_write.clone())
847859
.allow_net(self.input.allow_net)
848860
.allow_run(self.input.allow_run)
849-
.allow_env(self.input.allow_env.clone()),
850-
));
861+
.allow_env(self.input.allow_env.clone())
862+
};
863+
let mut engine = mq_lang::DefaultEngine::default();
864+
engine.set_io(Shared::new(sandboxed_io));
851865
engine.load_builtin_module();
852866
engine.set_optimization_level(self.optimize_level.clone().into());
853867

@@ -1836,6 +1850,59 @@ mod tests {
18361850
);
18371851
}
18381852

1853+
#[test]
1854+
fn test_allow_all_flag_grants_every_capability() {
1855+
// SAFETY: no other threads read/write this env var concurrently in this test.
1856+
unsafe { std::env::set_var("MQ_ALLOW_ALL_TEST_VAR", "test_value") };
1857+
defer! {
1858+
unsafe { std::env::remove_var("MQ_ALLOW_ALL_TEST_VAR") };
1859+
}
1860+
1861+
let base_cli = |query: &str, allow_all: bool| Cli {
1862+
input: InputArgs {
1863+
input_format: Some(InputFormat::Null),
1864+
allow_all,
1865+
..Default::default()
1866+
},
1867+
output: OutputArgs::default(),
1868+
commands: None,
1869+
query: Some(query.to_string()),
1870+
files: None,
1871+
..Cli::default()
1872+
};
1873+
1874+
assert!(
1875+
base_cli("$MQ_ALLOW_ALL_TEST_VAR", false).run().is_err(),
1876+
"$VAR should be blocked without --allow-all"
1877+
);
1878+
assert!(
1879+
base_cli("$MQ_ALLOW_ALL_TEST_VAR", true).run().is_ok(),
1880+
"$VAR should succeed with --allow-all"
1881+
);
1882+
1883+
let system_query = "system(\"echo\", [\"hi\"])";
1884+
assert!(
1885+
base_cli(system_query, false).run().is_err(),
1886+
"system should be blocked without --allow-all"
1887+
);
1888+
assert!(
1889+
base_cli(system_query, true).run().is_ok(),
1890+
"system should succeed with --allow-all"
1891+
);
1892+
}
1893+
1894+
#[rstest]
1895+
#[case(&["mq", "--allow-all", "--allow-read=/tmp", "self"])]
1896+
#[case(&["mq", "--allow-all", "--allow-write=/tmp", "self"])]
1897+
#[case(&["mq", "--allow-all", "--allow-run", "self"])]
1898+
#[case(&["mq", "--allow-all", "--allow-env", "self"])]
1899+
fn test_allow_all_conflicts_with_individual_allow_flags(#[case] args: &[&str]) {
1900+
assert!(
1901+
Cli::try_parse_from(args).is_err(),
1902+
"--allow-all should conflict with individual --allow-* flags"
1903+
);
1904+
}
1905+
18391906
#[rstest]
18401907
#[case(0.0)]
18411908
#[case(-1.0)]

docs/books/src/reference/env.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ mq --allow-env '$HOME' README.md
2323
mq --allow-env=HOME '$HOME' README.md
2424
```
2525

26+
`--allow-all` grants `--allow-read`/`--allow-write`/`--allow-net`/`--allow-run`/`--allow-env`
27+
all at once, including `$VAR` access.
28+
2629
## Color Configuration
2730

2831
### `NO_COLOR`

0 commit comments

Comments
 (0)