Validate that a sandbox policy does not contain unsafe content. Returns `Ok(())` if the policy is safe, or `Err(violations)` listing all safety violations found. Callers decide how to handle violations (hard error vs. logged warning). Checks performed: - `run_as_user` / `run_as_group` must be "sandbox" - Filesystem paths must be absolute (start with `/`) - Filesystem paths must not contain `..`
(
policy: &SandboxPolicy,
)
| 1202 | /// - Total path count must not exceed [`MAX_FILESYSTEM_PATHS`] |
| 1203 | /// - Network endpoint hosts must not use TLD wildcards (e.g. `*.com`) |
| 1204 | pub fn validate_sandbox_policy( |
| 1205 | policy: &SandboxPolicy, |
| 1206 | ) -> std::result::Result<(), Vec<PolicyViolation>> { |
| 1207 | let mut violations = Vec::new(); |
| 1208 | |
| 1209 | // Check process identity — must be "sandbox" or a numeric UID/GID |
| 1210 | // within the acceptable sandbox range. |
| 1211 | // `ensure_sandbox_process_identity` should be called before this to |
| 1212 | // fill in defaults; any invalid value is rejected. |
| 1213 | if let Some(ref process) = policy.process { |
| 1214 | if !is_valid_sandbox_identity(&process.run_as_user) { |
| 1215 | violations.push(PolicyViolation::InvalidProcessIdentity { |
| 1216 | field: "run_as_user", |
| 1217 | value: process.run_as_user.clone(), |
| 1218 | }); |
| 1219 | } |
| 1220 | if !is_valid_sandbox_identity(&process.run_as_group) { |
| 1221 | violations.push(PolicyViolation::InvalidProcessIdentity { |
| 1222 | field: "run_as_group", |
| 1223 | value: process.run_as_group.clone(), |
| 1224 | }); |
| 1225 | } |
| 1226 | } |
| 1227 | |
| 1228 | // Check filesystem paths |
| 1229 | if let Some(ref fs) = policy.filesystem { |
| 1230 | let total_paths = fs.read_only.len() + fs.read_write.len(); |
| 1231 | if total_paths > MAX_FILESYSTEM_PATHS { |
| 1232 | violations.push(PolicyViolation::TooManyPaths { count: total_paths }); |
| 1233 | } |
| 1234 | |
| 1235 | for path_str in fs.read_only.iter().chain(fs.read_write.iter()) { |
| 1236 | if path_str.len() > MAX_PATH_LENGTH { |
| 1237 | violations.push(PolicyViolation::FieldTooLong { |
| 1238 | path: truncate_for_display(path_str), |
| 1239 | length: path_str.len(), |
| 1240 | }); |
| 1241 | continue; |
| 1242 | } |
| 1243 | |
| 1244 | let path = Path::new(path_str); |
| 1245 | |
| 1246 | if !path.has_root() { |
| 1247 | violations.push(PolicyViolation::RelativePath { |
| 1248 | path: path_str.clone(), |
| 1249 | }); |
| 1250 | } |
| 1251 | |
| 1252 | if path |
| 1253 | .components() |
| 1254 | .any(|c| matches!(c, std::path::Component::ParentDir)) |
| 1255 | { |
| 1256 | violations.push(PolicyViolation::PathTraversal { |
| 1257 | path: path_str.clone(), |
| 1258 | }); |
| 1259 | } |
| 1260 | } |
| 1261 |