Reject unknown config keys
This commit is contained in:
@@ -145,6 +145,9 @@ pub struct FileConfig {
|
||||
pub options: Options,
|
||||
#[serde(default)]
|
||||
pub profile: HashMap<String, Options>,
|
||||
// Collects unrecognized keys; deny_unknown_fields is incompatible with flatten.
|
||||
#[serde(flatten)]
|
||||
_unknown: HashMap<String, toml::Value>,
|
||||
}
|
||||
|
||||
impl FileConfig {
|
||||
@@ -153,12 +156,26 @@ impl FileConfig {
|
||||
path: path.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
toml::from_str(&contents).map_err(|e| SandboxError::ConfigParse {
|
||||
Self::parse(&contents).map_err(|e| match e {
|
||||
SandboxError::ConfigParse { source, .. } => SandboxError::ConfigParse {
|
||||
path: path.to_path_buf(),
|
||||
source: e,
|
||||
source,
|
||||
},
|
||||
other => other,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse(contents: &str) -> Result<Self, SandboxError> {
|
||||
let config: Self = toml::from_str(contents).map_err(|e| SandboxError::ConfigParse {
|
||||
path: PathBuf::new(),
|
||||
source: e,
|
||||
})?;
|
||||
if let Some(key) = config._unknown.keys().next() {
|
||||
return Err(SandboxError::UnknownConfigKey(key.clone()));
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn resolve_profile(&self, name: Option<&str>) -> Result<Options, SandboxError> {
|
||||
match name {
|
||||
Some(n) => self
|
||||
@@ -172,7 +189,7 @@ impl FileConfig {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct Options {
|
||||
pub blacklist: Option<bool>,
|
||||
pub whitelist: Option<bool>,
|
||||
@@ -396,6 +413,7 @@ mod tests {
|
||||
..Options::default()
|
||||
},
|
||||
)]),
|
||||
..FileConfig::default()
|
||||
};
|
||||
let args = Args {
|
||||
profile: Some("relaxed".into()),
|
||||
@@ -461,6 +479,7 @@ mod tests {
|
||||
..Options::default()
|
||||
},
|
||||
)]),
|
||||
..FileConfig::default()
|
||||
};
|
||||
let args = Args {
|
||||
profile: Some("extra".into()),
|
||||
@@ -603,6 +622,7 @@ mod tests {
|
||||
..Options::default()
|
||||
},
|
||||
)]),
|
||||
..FileConfig::default()
|
||||
};
|
||||
let args = Args {
|
||||
profile: Some("extra".into()),
|
||||
@@ -613,6 +633,31 @@ mod tests {
|
||||
assert_eq!(config.mask.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_option_rejected() {
|
||||
let toml = r#"
|
||||
hardened = true
|
||||
bogus = "nope"
|
||||
"#;
|
||||
assert!(matches!(
|
||||
FileConfig::parse(toml),
|
||||
Err(SandboxError::UnknownConfigKey(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_profile_option_rejected() {
|
||||
let toml = r#"
|
||||
[profile.test]
|
||||
hardened = true
|
||||
frobnicate = 42
|
||||
"#;
|
||||
assert!(matches!(
|
||||
FileConfig::parse(toml),
|
||||
Err(SandboxError::ConfigParse { .. })
|
||||
));
|
||||
}
|
||||
|
||||
fn assert_paths(actual: &[PathBuf], expected: &[&str]) {
|
||||
let expected: Vec<PathBuf> = expected.iter().map(PathBuf::from).collect();
|
||||
assert_eq!(actual, &expected);
|
||||
|
||||
@@ -22,6 +22,7 @@ pub enum SandboxError {
|
||||
},
|
||||
ProfileNotFound(String),
|
||||
ConflictingMode,
|
||||
UnknownConfigKey(String),
|
||||
ConfigPathNotAbsolute(PathBuf),
|
||||
}
|
||||
|
||||
@@ -60,6 +61,7 @@ impl std::fmt::Display for SandboxError {
|
||||
f,
|
||||
"config section sets both blacklist and whitelist to true"
|
||||
),
|
||||
Self::UnknownConfigKey(key) => write!(f, "unknown config key: {key}"),
|
||||
Self::ConfigPathNotAbsolute(p) => {
|
||||
write!(f, "config path is not absolute: {}", p.display())
|
||||
}
|
||||
|
||||
@@ -578,6 +578,24 @@ fn config_invalid_toml_errors() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_unknown_key_errors() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let cfg = write_config(&dir, "hardened = true\nbogus = \"nope\"\n");
|
||||
|
||||
let output = sandbox(&["--config", &cfg])
|
||||
.args(["--", "true"])
|
||||
.output()
|
||||
.expect("failed to execute");
|
||||
|
||||
assert!(!output.status.success());
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(
|
||||
stderr.contains("unknown config key"),
|
||||
"expected unknown key error, got: {stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_hides_directory() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user