Reject unknown config keys

This commit is contained in:
2026-04-01 23:51:47 +02:00
parent c7c4c673cb
commit db60fb9ddb
3 changed files with 69 additions and 4 deletions

View File

@@ -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 {
path: path.to_path_buf(),
source: e,
Self::parse(&contents).map_err(|e| match e {
SandboxError::ConfigParse { source, .. } => SandboxError::ConfigParse {
path: path.to_path_buf(),
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);

View File

@@ -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())
}