Fail closed on unsupported Rust schema constructs

This commit is contained in:
ms2k 2026-07-19 14:27:41 +09:00
commit a61ba4d365
2 changed files with 366 additions and 11 deletions

View file

@ -98,6 +98,46 @@ impl<'de, const MIN: usize, const MAX: usize> serde::Deserialize<'de> for Bounde
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
#[serde(transparent)]
pub struct BoundedUrl<const MIN: usize, const MAX: usize>(url::Url);
impl<'de, const MIN: usize, const MAX: usize> serde::Deserialize<'de> for BoundedUrl<MIN, MAX> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <String as serde::Deserialize>::deserialize(deserializer)?;
if !(MIN..=MAX).contains(&value.chars().count()) {
return Err(serde::de::Error::custom("URL length outside bounds"));
}
url::Url::parse(&value)
.map(Self)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
#[serde(transparent)]
pub struct BoundedVec<T, const MIN: usize, const MAX: usize>(Vec<T>);
impl<'de, T, const MIN: usize, const MAX: usize> serde::Deserialize<'de> for BoundedVec<T, MIN, MAX>
where
T: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = <Vec<T> as serde::Deserialize>::deserialize(deserializer)?;
if (MIN..=MAX).contains(&value.len()) {
Ok(Self(value))
} else {
Err(serde::de::Error::custom("array length outside bounds"))
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
#[serde(transparent)]
pub struct OpaqueId(String);
@ -212,7 +252,7 @@ pub struct TargetOverride {
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct HttpSourceSubmission {
pub url: url::Url,
pub url: BoundedUrl<0, 8192>,
pub result_path: ResultPath,
pub credential_id: Option<CredentialId>,
}
@ -231,8 +271,8 @@ pub struct ObjectStoreSourceSubmission {
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct HuggingFaceSelection {
pub paths: Option<Vec<ResultPath>>,
pub prefixes: Option<Vec<ResultPath>>,
pub paths: Option<BoundedVec<ResultPath, 0, 10000>>,
pub prefixes: Option<BoundedVec<ResultPath, 0, 10000>>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
@ -324,9 +364,9 @@ pub struct EventProgress {
pub attempt_id: OpaqueId,
pub fence: BoundedU64<1, 9007199254740991>,
pub completed_bytes: BoundedU64<0, 9007199254740991>,
pub total_bytes: Option<Option<i64>>,
pub total_bytes: Option<Option<BoundedU64<0, 9007199254740991>>>,
pub completed_members: BoundedU64<0, 9007199254740991>,
pub total_members: Option<Option<i64>>,
pub total_members: Option<Option<BoundedU64<0, 9007199254740991>>>,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]

View file

@ -50,6 +50,20 @@ fn generated_roots_reject_governance_regressions() {
"occurred_at": "2026-07-15T00:00:00Z",
"unexpected": true
}));
let paths: Vec<Value> = (0..=10_000)
.map(|index| Value::String(format!("file-{index}")))
.collect();
assert_invalid::<JobSubmission>(json!({
"source": {
"kind": "hugging_face",
"repository_type": "model",
"repository_id": "owner/repository",
"revision": "main",
"selection": {
"paths": paths
}
}
}));
}
fn assert_all<T: DeserializeOwned>(values: &Value) {