commit 5b0f2d9bcabada0beb8768356c3ac354b9c20aa5
parent 2b2505cbba02b5d85ee24d4472e0c9901c6374f0
Author: triesap <tyson@radroots.org>
Date: Sat, 15 Aug 2026 23:52:32 +0000
runtime-contracts: cap static toml admission
- reject oversized distribution documents before TOML parsing
- reject oversized management documents before TOML parsing
- cover exact, over-limit, and adversarial multi-megabyte inputs
- document and enforce the shared one-megabyte contract boundary
Diffstat:
9 files changed, 116 insertions(+), 5 deletions(-)
diff --git a/crates/runtime_distribution/README b/crates/runtime_distribution/README
@@ -14,6 +14,10 @@ distribution contract resolution for the `radroots` core libraries.
cached operations endpoints, and Linux x86_64/aarch64 Tier-1 eligibility;
* TOML-backed contract handling for modular runtime deployment metadata.
+Complete TOML documents are rejected before parsing when they exceed exactly
+1,048,576 UTF-8 bytes. Schema, version, unknown-field, and exact-inventory
+validation remains fail closed after bounded admission.
+
The hardened service-target inventory is metadata-only. It deliberately does
not define Myc or RHI binaries, packages, archives, channels, artifact names,
or qualified support claims.
diff --git a/crates/runtime_distribution/src/error.rs b/crates/runtime_distribution/src/error.rs
@@ -2,6 +2,8 @@ use thiserror::Error;
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
pub enum RadrootsRuntimeDistributionError {
+ #[error("runtime distribution contract exceeds its size limit")]
+ ContractTooLarge,
#[error("parse runtime distribution contract failed")]
Parse,
#[error("runtime distribution schema is unsupported")]
diff --git a/crates/runtime_distribution/src/lib.rs b/crates/runtime_distribution/src/lib.rs
@@ -11,9 +11,9 @@ pub use model::{
RadrootsRuntimeDistributionContract, RuntimeDistributionEntry, TargetSet, TargetSpec,
};
pub use resolve::{
- RUNTIME_DISTRIBUTION_SCHEMA, RUNTIME_DISTRIBUTION_SCHEMA_VERSION,
- RadrootsRuntimeDistributionResolver, ResolvedRuntimeArtifact, ResolvedServiceTarget,
- RuntimeArtifactRequest, ServiceTargetRequest,
+ RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES, RUNTIME_DISTRIBUTION_SCHEMA,
+ RUNTIME_DISTRIBUTION_SCHEMA_VERSION, RadrootsRuntimeDistributionResolver,
+ ResolvedRuntimeArtifact, ResolvedServiceTarget, RuntimeArtifactRequest, ServiceTargetRequest,
};
pub use service::{
HardenedServiceTarget, HardenedServiceTargets, ServiceAdminBasePath, ServiceAdminTransport,
@@ -28,7 +28,8 @@ mod tests {
use toml::Value;
use super::{
- HardenedServiceTarget, RUNTIME_DISTRIBUTION_SCHEMA, RadrootsRuntimeDistributionContract,
+ HardenedServiceTarget, RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES,
+ RUNTIME_DISTRIBUTION_SCHEMA, RadrootsRuntimeDistributionContract,
RadrootsRuntimeDistributionError, RadrootsRuntimeDistributionResolver,
RuntimeArtifactRequest, RuntimeDistributionEntry, ServiceAdminBasePath,
ServiceAdminTransport, ServiceConfigurationFormat, ServiceInstanceSupport,
@@ -265,6 +266,33 @@ tier_1_targets = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"]
}
#[test]
+ fn parse_str_caps_the_complete_document_before_toml_parsing() {
+ let mut exact = CONTRACT.to_owned();
+ exact.push('#');
+ exact.extend(std::iter::repeat_n(
+ 'x',
+ RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES - exact.len(),
+ ));
+ assert_eq!(exact.len(), RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES);
+ RadrootsRuntimeDistributionResolver::parse_str(&exact)
+ .expect("exact maximum contract remains admissible");
+
+ exact.push('x');
+ assert_eq!(
+ RadrootsRuntimeDistributionResolver::parse_str(&exact)
+ .expect_err("maximum plus one must fail"),
+ RadrootsRuntimeDistributionError::ContractTooLarge
+ );
+
+ let very_large = format!("{}#{}", CONTRACT, "x".repeat(4 * 1024 * 1024));
+ assert_eq!(
+ RadrootsRuntimeDistributionResolver::parse_str(&very_large)
+ .expect_err("very large contract must fail"),
+ RadrootsRuntimeDistributionError::ContractTooLarge
+ );
+ }
+
+ #[test]
fn new_rejects_unexpected_schema() {
let mut contract = contract_value();
contract["schema"] = Value::String("wrong-schema".to_string());
diff --git a/crates/runtime_distribution/src/resolve.rs b/crates/runtime_distribution/src/resolve.rs
@@ -7,6 +7,7 @@ use radroots_runtime_paths::ServiceId;
pub const RUNTIME_DISTRIBUTION_SCHEMA: &str = "radroots-runtime-distribution";
pub const RUNTIME_DISTRIBUTION_SCHEMA_VERSION: u32 = 1;
+pub const RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES: usize = 1_048_576;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceTargetRequest<'a> {
@@ -66,6 +67,9 @@ pub struct RadrootsRuntimeDistributionResolver {
impl RadrootsRuntimeDistributionResolver {
pub fn parse_str(raw: &str) -> Result<Self, RadrootsRuntimeDistributionError> {
+ if raw.len() > RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES {
+ return Err(RadrootsRuntimeDistributionError::ContractTooLarge);
+ }
let contract = toml::from_str::<RadrootsRuntimeDistributionContract>(raw)
.map_err(|_| RadrootsRuntimeDistributionError::Parse)?;
Self::new(contract)
diff --git a/crates/runtime_distribution/tests/package_boundary.rs b/crates/runtime_distribution/tests/package_boundary.rs
@@ -1,4 +1,5 @@
const SERVICE_SOURCE: &str = include_str!("../src/service.rs");
+const RESOLVER_SOURCE: &str = include_str!("../src/resolve.rs");
const SERVICE_FIXTURE: &str = include_str!("fixtures/hardened_service_targets.v1.toml");
#[test]
@@ -32,3 +33,20 @@ fn hardened_service_metadata_has_no_artifact_or_runtime_authority() {
);
}
}
+
+#[test]
+fn contract_parser_is_bounded_before_toml_admission() {
+ assert!(
+ RESOLVER_SOURCE.contains("if raw.len() > RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES")
+ );
+ let bound = RESOLVER_SOURCE
+ .find("if raw.len() > RUNTIME_DISTRIBUTION_CONTRACT_MAX_UTF8_BYTES")
+ .expect("pre-parser bound");
+ let parser = RESOLVER_SOURCE
+ .find("toml::from_str::<RadrootsRuntimeDistributionContract>(raw)")
+ .expect("TOML parser");
+ assert!(
+ bound < parser,
+ "contract size must be checked before parsing"
+ );
+}
diff --git a/crates/runtime_manager/README b/crates/runtime_manager/README
@@ -19,6 +19,10 @@ runtime lifecycle and inspection helpers for the `radroots` core libraries.
* cleanup behavior limited to manager-owned install and tracking artifacts,
preserving canonical service state and secrets.
+Complete management TOML documents are rejected before parsing when they
+exceed exactly 1,048,576 UTF-8 bytes. Bounded documents still require the exact
+schema, version, closed field set, and complete hardened-service inventory.
+
Myc and RHI remain metadata-only management targets until their public CLI,
Unix-admin, status, and artifact integrations are separately implemented.
diff --git a/crates/runtime_manager/src/error.rs b/crates/runtime_manager/src/error.rs
@@ -7,6 +7,8 @@ use thiserror::Error;
/// and error-chain traversal therefore cannot disclose them.
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
pub enum RadrootsRuntimeManagerError {
+ #[error("runtime management contract exceeds its size limit")]
+ ContractTooLarge,
#[error("parse runtime management contract failed")]
Parse,
#[error("runtime management schema is unsupported")]
diff --git a/crates/runtime_manager/src/lib.rs b/crates/runtime_manager/src/lib.rs
@@ -32,6 +32,7 @@ pub use registry::{instance, load_registry, save_registry};
pub const RUNTIME_MANAGEMENT_SCHEMA: &str = "radroots-runtime-management";
pub const RUNTIME_MANAGEMENT_SCHEMA_VERSION: u32 = 1;
+pub const RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES: usize = 1_048_576;
pub(crate) const HARDENED_MANAGEMENT_CONTRACT: &str =
include_str!("../tests/fixtures/hardened_service_management.v1.toml");
@@ -39,6 +40,9 @@ pub(crate) const HARDENED_MANAGEMENT_CONTRACT: &str =
pub fn parse_contract_str(
raw: &str,
) -> Result<RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError> {
+ if raw.len() > RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES {
+ return Err(RadrootsRuntimeManagerError::ContractTooLarge);
+ }
let contract = toml::from_str::<RadrootsRuntimeManagementContract>(raw)
.map_err(|_| RadrootsRuntimeManagerError::Parse)?;
if contract.schema != RUNTIME_MANAGEMENT_SCHEMA {
@@ -67,7 +71,10 @@ pub(crate) fn validate_hardened_management_contract(
mod tests {
use std::error::Error as _;
- use super::{HARDENED_MANAGEMENT_CONTRACT, RUNTIME_MANAGEMENT_SCHEMA, parse_contract_str};
+ use super::{
+ HARDENED_MANAGEMENT_CONTRACT, RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES,
+ RUNTIME_MANAGEMENT_SCHEMA, RadrootsRuntimeManagerError, parse_contract_str,
+ };
const CONTRACT: &str = HARDENED_MANAGEMENT_CONTRACT;
@@ -85,6 +92,30 @@ mod tests {
}
#[test]
+ fn contract_parser_caps_the_complete_document_before_toml_parsing() {
+ let mut exact = CONTRACT.to_owned();
+ exact.push('#');
+ exact.extend(std::iter::repeat_n(
+ 'x',
+ RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES - exact.len(),
+ ));
+ assert_eq!(exact.len(), RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES);
+ parse_contract_str(&exact).expect("exact maximum contract remains admissible");
+
+ exact.push('x');
+ assert_eq!(
+ parse_contract_str(&exact),
+ Err(RadrootsRuntimeManagerError::ContractTooLarge)
+ );
+
+ let very_large = format!("{}#{}", CONTRACT, "x".repeat(4 * 1024 * 1024));
+ assert_eq!(
+ parse_contract_str(&very_large),
+ Err(RadrootsRuntimeManagerError::ContractTooLarge)
+ );
+ }
+
+ #[test]
fn contract_errors_redact_raw_schema_values_and_parser_causes() {
for (raw, secret) in [
(
diff --git a/crates/runtime_manager/tests/service_target_boundary.rs b/crates/runtime_manager/tests/service_target_boundary.rs
@@ -1,4 +1,5 @@
const MANAGEMENT_FIXTURE: &str = include_str!("fixtures/hardened_service_management.v1.toml");
+const MANAGER_ROOT_SOURCE: &str = include_str!("../src/lib.rs");
#[test]
fn hardened_services_remain_metadata_only_in_management_contract() {
@@ -23,3 +24,20 @@ fn hardened_services_remain_metadata_only_in_management_contract() {
assert!(MANAGEMENT_FIXTURE.contains("destructive_actions = []"));
assert!(MANAGEMENT_FIXTURE.contains("[bootstrap]"));
}
+
+#[test]
+fn management_contract_is_bounded_before_toml_admission() {
+ assert!(
+ MANAGER_ROOT_SOURCE.contains("if raw.len() > RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES")
+ );
+ let bound = MANAGER_ROOT_SOURCE
+ .find("if raw.len() > RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES")
+ .expect("pre-parser bound");
+ let parser = MANAGER_ROOT_SOURCE
+ .find("toml::from_str::<RadrootsRuntimeManagementContract>(raw)")
+ .expect("TOML parser");
+ assert!(
+ bound < parser,
+ "contract size must be checked before parsing"
+ );
+}