feat(hololake): bind release chain to public path namespace

This commit is contained in:
冰朔 2026-08-13 19:11:26 +08:00
commit 9006075310
13 changed files with 223 additions and 29 deletions

View file

@ -3,6 +3,7 @@ use tauri::{AppHandle, Runtime, Url};
use tauri_plugin_updater::UpdaterExt;
const EMBEDDED_RELEASE_TRUST: &str = include_str!("../release-trust.json");
const PUBLIC_RELEASE_ENDPOINT_PATH: &str = "/hololake/releases/latest.json";
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
@ -108,6 +109,12 @@ fn validate_release_trust(raw: &str) -> Result<ValidatedReleaseTrust, String> {
"the updater endpoint is not owned by the configured HoloLake host".into(),
);
}
if endpoint.path() != PUBLIC_RELEASE_ENDPOINT_PATH
|| endpoint.query().is_some()
|| endpoint.fragment().is_some()
{
return Err("the HoloLake release endpoint path is not registered".into());
}
Ok(ValidatedReleaseTrust::Enabled {
endpoints: vec![endpoint],
public_key: trust.public_key,
@ -166,7 +173,7 @@ mod tests {
fn accepts_https_shape_but_rejects_insecure_endpoints() {
let owned_shape = document(
"PROVISIONED",
"\"https://releases.example.test/latest.json\"",
"\"https://releases.example.test/hololake/releases/latest.json\"",
"public",
"\"releases.example.test\"",
);
@ -176,18 +183,25 @@ mod tests {
);
let insecure = document(
"PROVISIONED",
"\"http://releases.example.test/latest.json\"",
"\"http://releases.example.test/hololake/releases/latest.json\"",
"public",
"\"releases.example.test\"",
);
assert!(validate_release_trust(&insecure).is_err());
let mismatched_host = document(
"PROVISIONED",
"\"https://upstream.example.test/latest.json\"",
"\"https://upstream.example.test/hololake/releases/latest.json\"",
"public",
"\"releases.example.test\"",
);
assert!(validate_release_trust(&mismatched_host).is_err());
let mismatched_path = document(
"PROVISIONED",
"\"https://releases.example.test/latest.json\"",
"public",
"\"releases.example.test\"",
);
assert!(validate_release_trust(&mismatched_path).is_err());
}
#[test]

View file

@ -20,6 +20,7 @@ const RECEIPT_SCHEMA: &str = "hololake.release-install-receipt/v1";
const RECOVERY_SCHEMA: &str = "hololake.release-recovery/v1";
const CANDIDATE_TTL_MS: u128 = 30 * 60 * 1000;
const EXPECTED_BUNDLE_IDENTIFIER: &str = "world.guanghu.hololake";
const PUBLIC_RELEASE_PACKAGE_PREFIX: &str = "/hololake/releases/";
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
@ -472,7 +473,12 @@ fn validate_update(update: &Update, allowed_host: &str) -> Result<UpdateSnapshot
return Err("HOLOLAKE_RELEASE_BROADCAST_POLICY_INVALID".into());
}
let url = &update.download_url;
if url.scheme() != "https" || url.host_str() != Some(allowed_host) {
if url.scheme() != "https"
|| url.host_str() != Some(allowed_host)
|| !url.path().starts_with(PUBLIC_RELEASE_PACKAGE_PREFIX)
|| url.query().is_some()
|| url.fragment().is_some()
{
return Err("HOLOLAKE_RELEASE_PACKAGE_HOST_NOT_TRUSTED".into());
}
let platform = envelope