lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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:
Mcrates/runtime_distribution/README | 4++++
Mcrates/runtime_distribution/src/error.rs | 2++
Mcrates/runtime_distribution/src/lib.rs | 36++++++++++++++++++++++++++++++++----
Mcrates/runtime_distribution/src/resolve.rs | 4++++
Mcrates/runtime_distribution/tests/package_boundary.rs | 18++++++++++++++++++
Mcrates/runtime_manager/README | 4++++
Mcrates/runtime_manager/src/error.rs | 2++
Mcrates/runtime_manager/src/lib.rs | 33++++++++++++++++++++++++++++++++-
Mcrates/runtime_manager/tests/service_target_boundary.rs | 18++++++++++++++++++
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" + ); +}