Skip to content

Commit b729bf0

Browse files
yazan-abdalrahmanbartlomiejudsherret
authored
feat(permission): support suffix wildcards in --allow-env flag (#25255)
This commit adds support for suffix wildcard for `--allow-env` flag. Specifying flag like `--allow-env=DENO_*` will enable access to all environmental variables starting with `DENO_*`. Closes #24847 --------- Co-authored-by: Bartek Iwańczuk <[email protected]> Co-authored-by: David Sherret <[email protected]>
1 parent 56f3162 commit b729bf0

File tree

7 files changed

+250
-20
lines changed

7 files changed

+250
-20
lines changed

runtime/permissions/lib.rs

+183-20
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ impl UnitPermission {
294294
/// A normalized environment variable name. On Windows this will
295295
/// be uppercase and on other platforms it will stay as-is.
296296
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
297-
struct EnvVarName {
297+
pub struct EnvVarName {
298298
inner: String,
299299
}
300300

@@ -1114,15 +1114,37 @@ impl ImportDescriptor {
11141114
pub struct EnvDescriptorParseError;
11151115

11161116
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
1117-
pub struct EnvDescriptor(EnvVarName);
1117+
pub enum EnvDescriptor {
1118+
Name(EnvVarName),
1119+
PrefixPattern(EnvVarName),
1120+
}
11181121

11191122
impl EnvDescriptor {
11201123
pub fn new(env: impl AsRef<str>) -> Self {
1121-
Self(EnvVarName::new(env))
1124+
if let Some(prefix_pattern) = env.as_ref().strip_suffix('*') {
1125+
Self::PrefixPattern(EnvVarName::new(prefix_pattern))
1126+
} else {
1127+
Self::Name(EnvVarName::new(env))
1128+
}
1129+
}
1130+
}
1131+
1132+
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
1133+
enum EnvQueryDescriptorInner {
1134+
Name(EnvVarName),
1135+
PrefixPattern(EnvVarName),
1136+
}
1137+
1138+
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
1139+
pub struct EnvQueryDescriptor(EnvQueryDescriptorInner);
1140+
1141+
impl EnvQueryDescriptor {
1142+
pub fn new(env: impl AsRef<str>) -> Self {
1143+
Self(EnvQueryDescriptorInner::Name(EnvVarName::new(env)))
11221144
}
11231145
}
11241146

1125-
impl QueryDescriptor for EnvDescriptor {
1147+
impl QueryDescriptor for EnvQueryDescriptor {
11261148
type AllowDesc = EnvDescriptor;
11271149
type DenyDesc = EnvDescriptor;
11281150

@@ -1131,19 +1153,45 @@ impl QueryDescriptor for EnvDescriptor {
11311153
}
11321154

11331155
fn display_name(&self) -> Cow<str> {
1134-
Cow::from(self.0.as_ref())
1156+
Cow::from(match &self.0 {
1157+
EnvQueryDescriptorInner::Name(env_var_name) => env_var_name.as_ref(),
1158+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1159+
env_var_name.as_ref()
1160+
}
1161+
})
11351162
}
11361163

11371164
fn from_allow(allow: &Self::AllowDesc) -> Self {
1138-
allow.clone()
1165+
match allow {
1166+
Self::AllowDesc::Name(s) => {
1167+
Self(EnvQueryDescriptorInner::Name(s.clone()))
1168+
}
1169+
Self::AllowDesc::PrefixPattern(s) => {
1170+
Self(EnvQueryDescriptorInner::PrefixPattern(s.clone()))
1171+
}
1172+
}
11391173
}
11401174

11411175
fn as_allow(&self) -> Option<Self::AllowDesc> {
1142-
Some(self.clone())
1176+
Some(match &self.0 {
1177+
EnvQueryDescriptorInner::Name(env_var_name) => {
1178+
Self::AllowDesc::Name(env_var_name.clone())
1179+
}
1180+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1181+
Self::AllowDesc::PrefixPattern(env_var_name.clone())
1182+
}
1183+
})
11431184
}
11441185

11451186
fn as_deny(&self) -> Self::DenyDesc {
1146-
self.clone()
1187+
match &self.0 {
1188+
EnvQueryDescriptorInner::Name(env_var_name) => {
1189+
Self::DenyDesc::Name(env_var_name.clone())
1190+
}
1191+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1192+
Self::DenyDesc::PrefixPattern(env_var_name.clone())
1193+
}
1194+
}
11471195
}
11481196

11491197
fn check_in_permission(
@@ -1156,29 +1204,94 @@ impl QueryDescriptor for EnvDescriptor {
11561204
}
11571205

11581206
fn matches_allow(&self, other: &Self::AllowDesc) -> bool {
1159-
self == other
1207+
match other {
1208+
Self::AllowDesc::Name(n) => match &self.0 {
1209+
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
1210+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1211+
env_var_name.as_ref().starts_with(n.as_ref())
1212+
}
1213+
},
1214+
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
1215+
EnvQueryDescriptorInner::Name(env_var_name) => {
1216+
env_var_name.as_ref().starts_with(p.as_ref())
1217+
}
1218+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1219+
env_var_name.as_ref().starts_with(p.as_ref())
1220+
}
1221+
},
1222+
}
11601223
}
11611224

11621225
fn matches_deny(&self, other: &Self::DenyDesc) -> bool {
1163-
self == other
1226+
match other {
1227+
Self::AllowDesc::Name(n) => match &self.0 {
1228+
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
1229+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1230+
env_var_name.as_ref().starts_with(n.as_ref())
1231+
}
1232+
},
1233+
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
1234+
EnvQueryDescriptorInner::Name(env_var_name) => {
1235+
env_var_name.as_ref().starts_with(p.as_ref())
1236+
}
1237+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1238+
p == env_var_name
1239+
}
1240+
},
1241+
}
11641242
}
11651243

11661244
fn revokes(&self, other: &Self::AllowDesc) -> bool {
1167-
self == other
1245+
match other {
1246+
Self::AllowDesc::Name(n) => match &self.0 {
1247+
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
1248+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1249+
env_var_name.as_ref().starts_with(n.as_ref())
1250+
}
1251+
},
1252+
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
1253+
EnvQueryDescriptorInner::Name(env_var_name) => {
1254+
env_var_name.as_ref().starts_with(p.as_ref())
1255+
}
1256+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1257+
p == env_var_name
1258+
}
1259+
},
1260+
}
11681261
}
11691262

11701263
fn stronger_than_deny(&self, other: &Self::DenyDesc) -> bool {
1171-
self == other
1264+
match other {
1265+
Self::AllowDesc::Name(n) => match &self.0 {
1266+
EnvQueryDescriptorInner::Name(env_var_name) => n == env_var_name,
1267+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1268+
env_var_name.as_ref().starts_with(n.as_ref())
1269+
}
1270+
},
1271+
Self::AllowDesc::PrefixPattern(p) => match &self.0 {
1272+
EnvQueryDescriptorInner::Name(env_var_name) => {
1273+
env_var_name.as_ref().starts_with(p.as_ref())
1274+
}
1275+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1276+
p == env_var_name
1277+
}
1278+
},
1279+
}
11721280
}
11731281

11741282
fn overlaps_deny(&self, _other: &Self::DenyDesc) -> bool {
11751283
false
11761284
}
11771285
}
11781286

1179-
impl AsRef<str> for EnvDescriptor {
1287+
impl AsRef<str> for EnvQueryDescriptor {
11801288
fn as_ref(&self) -> &str {
1181-
self.0.as_ref()
1289+
match &self.0 {
1290+
EnvQueryDescriptorInner::Name(env_var_name) => env_var_name.as_ref(),
1291+
EnvQueryDescriptorInner::PrefixPattern(env_var_name) => {
1292+
env_var_name.as_ref()
1293+
}
1294+
}
11821295
}
11831296
}
11841297

@@ -1749,20 +1862,20 @@ impl UnaryPermission<ImportDescriptor> {
17491862
}
17501863
}
17511864

1752-
impl UnaryPermission<EnvDescriptor> {
1865+
impl UnaryPermission<EnvQueryDescriptor> {
17531866
pub fn query(&self, env: Option<&str>) -> PermissionState {
17541867
self.query_desc(
1755-
env.map(EnvDescriptor::new).as_ref(),
1868+
env.map(EnvQueryDescriptor::new).as_ref(),
17561869
AllowPartial::TreatAsPartialGranted,
17571870
)
17581871
}
17591872

17601873
pub fn request(&mut self, env: Option<&str>) -> PermissionState {
1761-
self.request_desc(env.map(EnvDescriptor::new).as_ref())
1874+
self.request_desc(env.map(EnvQueryDescriptor::new).as_ref())
17621875
}
17631876

17641877
pub fn revoke(&mut self, env: Option<&str>) -> PermissionState {
1765-
self.revoke_desc(env.map(EnvDescriptor::new).as_ref())
1878+
self.revoke_desc(env.map(EnvQueryDescriptor::new).as_ref())
17661879
}
17671880

17681881
pub fn check(
@@ -1771,7 +1884,7 @@ impl UnaryPermission<EnvDescriptor> {
17711884
api_name: Option<&str>,
17721885
) -> Result<(), PermissionDeniedError> {
17731886
skip_check_if_is_permission_fully_granted!(self);
1774-
self.check_desc(Some(&EnvDescriptor::new(env)), false, api_name)
1887+
self.check_desc(Some(&EnvQueryDescriptor::new(env)), false, api_name)
17751888
}
17761889

17771890
pub fn check_all(&mut self) -> Result<(), PermissionDeniedError> {
@@ -1905,7 +2018,7 @@ pub struct Permissions {
19052018
pub read: UnaryPermission<ReadQueryDescriptor>,
19062019
pub write: UnaryPermission<WriteQueryDescriptor>,
19072020
pub net: UnaryPermission<NetDescriptor>,
1908-
pub env: UnaryPermission<EnvDescriptor>,
2021+
pub env: UnaryPermission<EnvQueryDescriptor>,
19092022
pub sys: UnaryPermission<SysDescriptor>,
19102023
pub run: UnaryPermission<RunQueryDescriptor>,
19112024
pub ffi: UnaryPermission<FfiQueryDescriptor>,
@@ -4564,6 +4677,56 @@ mod tests {
45644677
assert_eq!(perms.env.revoke(Some("HomE")), PermissionState::Prompt);
45654678
}
45664679

4680+
#[test]
4681+
fn test_env_wildcards() {
4682+
set_prompter(Box::new(TestPrompter));
4683+
let _prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock();
4684+
let mut perms = Permissions::allow_all();
4685+
perms.env = UnaryPermission {
4686+
granted_global: false,
4687+
..Permissions::new_unary(
4688+
Some(HashSet::from([EnvDescriptor::new("HOME_*")])),
4689+
None,
4690+
false,
4691+
)
4692+
};
4693+
assert_eq!(perms.env.query(Some("HOME")), PermissionState::Prompt);
4694+
assert_eq!(perms.env.query(Some("HOME_")), PermissionState::Granted);
4695+
assert_eq!(perms.env.query(Some("HOME_TEST")), PermissionState::Granted);
4696+
4697+
// assert no privilege escalation
4698+
let parser = TestPermissionDescriptorParser;
4699+
assert!(perms
4700+
.env
4701+
.create_child_permissions(
4702+
ChildUnaryPermissionArg::GrantedList(vec!["HOME_SUB".to_string()]),
4703+
|value| parser.parse_env_descriptor(value).map(Some),
4704+
)
4705+
.is_ok());
4706+
assert!(perms
4707+
.env
4708+
.create_child_permissions(
4709+
ChildUnaryPermissionArg::GrantedList(vec!["HOME*".to_string()]),
4710+
|value| parser.parse_env_descriptor(value).map(Some),
4711+
)
4712+
.is_err());
4713+
assert!(perms
4714+
.env
4715+
.create_child_permissions(
4716+
ChildUnaryPermissionArg::GrantedList(vec!["OUTSIDE".to_string()]),
4717+
|value| parser.parse_env_descriptor(value).map(Some),
4718+
)
4719+
.is_err());
4720+
assert!(perms
4721+
.env
4722+
.create_child_permissions(
4723+
// ok because this is a subset of HOME_*
4724+
ChildUnaryPermissionArg::GrantedList(vec!["HOME_S*".to_string()]),
4725+
|value| parser.parse_env_descriptor(value).map(Some),
4726+
)
4727+
.is_ok());
4728+
}
4729+
45674730
#[test]
45684731
fn test_check_partial_denied() {
45694732
let parser = TestPermissionDescriptorParser;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"tempDir": true,
3+
"tests": {
4+
"deno_env_wildcard_tests": {
5+
"envs": {
6+
"MYAPP_HELLO": "Hello\tworld,",
7+
"MYAPP_GOODBYE": "farewell",
8+
"OTHER_VAR": "ignore"
9+
},
10+
"steps": [
11+
{
12+
"args": "run --allow-env=MYAPP_* main.js",
13+
"output": "Hello\tworld,\nfarewell\ndone\n"
14+
},
15+
{
16+
"args": "run --allow-env main.js",
17+
"output": "Hello\tworld,\nfarewell\ndone\n"
18+
},
19+
{
20+
"args": "run --allow-env=MYAPP_HELLO,MYAPP_GOODBYE,MYAPP_TEST,MYAPP_DONE main.js",
21+
"output": "Hello\tworld,\nfarewell\ndone\n"
22+
}
23+
]
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
console.log(Deno.env.get("MYAPP_HELLO"));
2+
console.log(Deno.env.get("MYAPP_GOODBYE"));
3+
Deno.env.set("MYAPP_TEST", "done");
4+
Deno.env.set("MYAPP_DONE", "done");
5+
console.log(Deno.env.get("MYAPP_DONE"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"envs": {
3+
"DENO_HELLO": "hello",
4+
"DENO_BYE": "bye",
5+
"AWS_HELLO": "aws"
6+
},
7+
"args": "run --allow-env --allow-read --unstable-worker-options main.js",
8+
"output": "main.out",
9+
"exitCode": 1
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
console.log("main1", Deno.env.get("DENO_HELLO"));
2+
console.log("main2", Deno.env.get("DENO_BYE"));
3+
console.log("main3", Deno.env.get("AWS_HELLO"));
4+
5+
new Worker(import.meta.resolve("./worker.js"), {
6+
type: "module",
7+
deno: {
8+
permissions: {
9+
env: ["DENO_*"],
10+
},
11+
},
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
main1 hello
2+
main2 bye
3+
main3 aws
4+
worker1 hello
5+
worker2 bye
6+
error: Uncaught (in worker "") (in promise) NotCapable: Requires env access to "AWS_HELLO", run again with the --allow-env flag
7+
console.log("worker3", Deno.env.get("AWS_HELLO"));
8+
^
9+
[WILDCARD]
10+
error: Uncaught (in promise) Error: Unhandled error in child worker.
11+
[WILDCARD]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
console.log("worker1", Deno.env.get("DENO_HELLO"));
2+
console.log("worker2", Deno.env.get("DENO_BYE"));
3+
console.log("worker3", Deno.env.get("AWS_HELLO"));

0 commit comments

Comments
 (0)