micromegas_object_cache/validation.rs
1use anyhow::{Result, bail};
2
3/// Validate a request key: reject structurally unsafe keys (empty, absolute, or
4/// containing `..` traversal segments) and enforce that it falls under one of
5/// the `allowed_prefixes`.
6///
7/// An empty `allowed_prefixes` list means allow-all (still enforcing structure).
8/// Callers that must fail closed are responsible for refusing to configure an
9/// empty list.
10pub fn validate_key(key: &str, allowed_prefixes: &[String]) -> Result<()> {
11 if key.is_empty() {
12 bail!("empty key");
13 }
14 if key.starts_with('/') {
15 bail!("key must not start with /");
16 }
17 if key.split('/').any(|seg| seg == "..") {
18 bail!("key must not contain ..");
19 }
20 if allowed_prefixes.is_empty() {
21 return Ok(());
22 }
23 // Admit the key if it matches any prefix on the equal-or-`{p}/`-boundary
24 // rule, so `blobs` admits `blobs` and `blobs/x` but not `blobs-secret/y`.
25 let allowed = allowed_prefixes
26 .iter()
27 .any(|p| key == p || key.starts_with(&format!("{p}/")));
28 if !allowed {
29 bail!("key {key} is outside allowed prefixes {allowed_prefixes:?}");
30 }
31 Ok(())
32}