Skip to content
Merged
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ TinySecurity supplies security policy through a native TinyBus module. Hosts
compile the transport-free `tinysecurity-bus` contract and load the isolated
`tinysecurity-module` artifact. The bootstrap implements `Evaluate`, batched
`Check`, `PolicyInfo`, validated SDK configuration and policy generations.
Contract 1.23 adds confidential per-frame `DeviceFrame2` calls over the live
opaque tunnel capability issued by approved `ExecuteDevice2` work; see the
[device contract](MODULE.md#confidential-device-handles).
Contract 1.1 also supplies immutable native [filesystem scopes](docs/specs/immutable-path-scopes.md)
through `RegisterPathPolicy`, `ValidatePath`, `ValidateParent` and `CheckPath`.
Contract 1.3 adds native `Redact`, `ScanPrompt` and `ScanToolDefinition`, with a
Expand Down
9 changes: 9 additions & 0 deletions crates/tinysecurity-bus/src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@ pub struct DeviceConfig2 {
pub max_in_flight: u32,
/// Operations per scope per monotonic minute, in 1..=4096.
pub operations_per_minute: u32,
/// Frames per live tunnel per monotonic minute, in 1..=60000; defaults to 12000
/// (200 frames/second). This budget is independent of control-plane
/// `operations_per_minute`.
#[serde(default = "default_frames_per_minute")]
pub frames_per_minute: u32,
/// Maximum handle and operation lifetime, in 1..=300000 milliseconds.
pub max_ttl_ms: u32,
/// Plaintext bound, in 1..=1048576 bytes.
pub max_message_bytes: u32,
/// Authenticated bootstrap nonces retained across the registry, in 1..=4096.
pub max_bootstrap_nonces: u32,
}
/// Default independent tunnel-frame budget: 12,000 frames per monotonic minute.
fn default_frames_per_minute() -> u32 {
12_000
}
/// Init-only static credential and immutable approval registration reference.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
Expand Down
61 changes: 61 additions & 0 deletions crates/tinysecurity-bus/src/device_frame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Confidential per-frame operations over a previously approved native tunnel.

use serde::{Deserialize, Serialize};

/// Seal or open one bounded frame through a tunnel handle returned by approved device work.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum DeviceFrameOperation2 {
/// Encrypt plaintext as a version-two tunnel frame.
Seal {
/// Confidential plaintext, bounded by the configured device limit.
plaintext: Vec<u8>,
},
/// Authenticate and decrypt a version-two tunnel frame.
Open {
/// Confidential frame, bounded by the configured device limit.
frame: Vec<u8>,
},
}

/// Confidential frame request using an opaque tunnel handle from `ExecuteDevice2`.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DeviceFrameRequest2 {
/// Existing registered device scope that owns the handle.
pub scope_id: String,
/// Opaque tunnel capability returned by an approved `DeviceOperation2::Tunnel`.
pub handle: String,
/// Exact host execution instance, bounded to 128 bytes.
pub call_id: String,
/// One seal or open operation; no keys or caller identity are accepted.
pub operation: DeviceFrameOperation2,
}

/// Confidential frame result; opening returns plaintext only over the trusted bus link.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DeviceFrameResponse2 {
/// Sealed frame or authenticated plaintext.
pub bytes: Vec<u8>,
}

impl std::fmt::Debug for DeviceFrameOperation2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("DeviceFrameOperation2([REDACTED])")
}
}
impl std::fmt::Debug for DeviceFrameRequest2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("DeviceFrameRequest2([REDACTED])")
}
}
impl std::fmt::Debug for DeviceFrameResponse2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("DeviceFrameResponse2([REDACTED])")
}
}

#[cfg(test)]
#[path = "device_frame_tests.rs"]
mod tests;
74 changes: 74 additions & 0 deletions crates/tinysecurity-bus/src/device_frame_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use super::*;

#[test]
fn frame_requests_and_responses_pin_the_confidential_wire_shape()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let request = DeviceFrameRequest2 {
scope_id: "registered-device".into(),
handle: "opaque-tunnel-handle".into(),
call_id: "socket-frame-17".into(),
operation: DeviceFrameOperation2::Seal {
plaintext: b"device payload".to_vec(),
},
};
let wire = serde_json::json!({
"scope_id":"registered-device",
"handle":"opaque-tunnel-handle",
"call_id":"socket-frame-17",
"operation":{"kind":"seal","plaintext":[100,101,118,105,99,101,32,112,97,121,108,111,97,100]}
});
assert_eq!(serde_json::to_value(&request)?, wire);
assert_eq!(
serde_json::from_value::<DeviceFrameRequest2>(wire)?,
request
);
assert_eq!(format!("{request:?}"), "DeviceFrameRequest2([REDACTED])");

let mut forged = serde_json::to_value(&request)?;
forged["agent"] = serde_json::json!("model-controlled");
assert!(serde_json::from_value::<DeviceFrameRequest2>(forged).is_err());

let response = DeviceFrameResponse2 {
bytes: vec![0, 128, 255],
};
assert_eq!(
serde_json::to_value(&response)?,
serde_json::json!({"bytes":[0,128,255]})
);
assert_eq!(format!("{response:?}"), "DeviceFrameResponse2([REDACTED])");
Ok(())
}

#[test]
fn frame_operations_redact_both_payloads_without_changing_open_wire_shape()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let seal = DeviceFrameOperation2::Seal {
plaintext: b"private seal payload".to_vec(),
};
let open = DeviceFrameOperation2::Open {
frame: b"private open frame".to_vec(),
};

for operation in [&seal, &open] {
let debug = format!("{operation:?}");
assert_eq!(debug, "DeviceFrameOperation2([REDACTED])");
assert!(!debug.contains("private"));
}

let wire = serde_json::to_value(&open)?;
assert_eq!(
wire,
serde_json::json!({
"kind": "open",
"frame": b"private open frame"
})
);
assert_eq!(serde_json::from_value::<DeviceFrameOperation2>(wire)?, open);
Ok(())
}

#[test]
fn frame_member_name_is_stable_and_contract_version_is_bumped() {
assert_eq!(crate::names::methods::DEVICE_FRAME2, "DeviceFrame2");
assert_eq!(crate::CONTRACT_VERSION, (1, 23));
}
19 changes: 18 additions & 1 deletion crates/tinysecurity-bus/src/device_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,37 @@ fn device_configuration_and_scopes_pin_bounds_origin_and_optional_secret()
max_handles: 101,
max_in_flight: 7,
operations_per_minute: 103,
frames_per_minute: 104,
max_ttl_ms: 104,
max_message_bytes: 105,
max_bootstrap_nonces: 106,
},
json!({
"scopes":[scope_wire],"max_handles":101,"max_in_flight":7,
"operations_per_minute":103,"max_ttl_ms":104,"max_message_bytes":105,
"operations_per_minute":103,"frames_per_minute":104,"max_ttl_ms":104,"max_message_bytes":105,
"max_bootstrap_nonces":106,
}),
)?;
}
Ok(())
}

#[test]
fn older_device_config_defaults_the_independent_frame_budget()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let config: DeviceConfig2 = serde_json::from_value(serde_json::json!({
"scopes": [{"scope_id":"registered-scope","origin":"operator","static_secret":null}],
"max_handles": 8,
"max_in_flight": 2,
"operations_per_minute": 600,
"max_ttl_ms": 300_000,
"max_message_bytes": 1024,
"max_bootstrap_nonces": 8
}))?;
assert_eq!(config.frames_per_minute, 12_000);
Ok(())
}

#[test]
fn device_results_pin_optional_handle_public_key_and_byte_arrays()
-> std::result::Result<(), Box<dyn std::error::Error>> {
Expand Down
4 changes: 3 additions & 1 deletion crates/tinysecurity-bus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ mod secret;
pub use secret::*;
mod device;
pub use device::*;
mod device_frame;
pub use device_frame::*;
mod sandbox;
pub use sandbox::*;
mod sandbox_scopes;
Expand Down Expand Up @@ -57,7 +59,7 @@ pub use redact::RedactorConfig;
pub use scoped_policy::*;
pub mod names;
/// Current wire vocabulary version, independent of the artifact version.
pub const CONTRACT_VERSION: (u32, u32) = (1, 22);
pub const CONTRACT_VERSION: (u32, u32) = (1, 23);
/// Whether a remote module's advertised version supplies this host's vocabulary.
///
/// `version` is the provider version received by the host, not a host requirement
Expand Down
3 changes: 3 additions & 0 deletions crates/tinysecurity-bus/src/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub mod methods {
pub const EVALUATE_DEVICE2: &str = "EvaluateDevice2";
/// Execute exact reviewed confidential work using scoped opaque handles.
pub const EXECUTE_DEVICE2: &str = "ExecuteDevice2";
/// Seal or open a frame through a live tunnel handle created by approved device work.
pub const DEVICE_FRAME2: &str = "DeviceFrame2";
/// Trusted legacy history migration without broad grant revival.
pub const IMPORT_APPROVAL_HISTORY2: &str = "ImportApprovalHistory2";
/// Durable exact approval evaluation.
Expand Down Expand Up @@ -169,6 +171,7 @@ pub const METHODS: &[&str] = &[
methods::PAIRING_CONTROL2,
methods::EVALUATE_DEVICE2,
methods::EXECUTE_DEVICE2,
methods::DEVICE_FRAME2,
methods::PLAN,
methods::EVALUATE_SANDBOX_APPROVAL2,
methods::CONSUME_SANDBOX_PLAN2,
Expand Down
7 changes: 4 additions & 3 deletions crates/tinysecurity-bus/tests/scoped_wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ fn new_members_are_advertised_and_named_by_contract_constants() {
names::methods::SCOPED_POLICY_INFO,
names::methods::EVALUATE_SCOPED_AUDITED,
names::methods::CHECK_SCOPED_AUDITED,
names::methods::DEVICE_FRAME2,
] {
assert!(names::METHODS.contains(&name), "{name}");
}
Expand All @@ -82,10 +83,10 @@ fn new_members_are_advertised_and_named_by_contract_constants() {

#[test]
fn contract_bump_is_additive_for_older_hosts_and_modules() {
assert_eq!(CONTRACT_VERSION, (1, 22));
// A host built against 1.22 accepts this module and newer minors, not a 1.21 module.
assert!(is_compatible((1, 22)));
assert_eq!(CONTRACT_VERSION, (1, 23));
// A host built against 1.23 accepts this module and newer minors, not a 1.22 module.
assert!(is_compatible((1, 23)));
assert!(!is_compatible((1, 22)));
assert!(!is_compatible((1, 21)));
assert!(!is_compatible((1, 20)));
assert!(!is_compatible((1, 19)));
Expand Down
2 changes: 1 addition & 1 deletion crates/tinysecurity-bus/tests/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ fn redactor_init_is_additive_and_secrets_never_enter_public_info()
serde_json::to_value(&config)?["redactor"]["secrets"][0],
"literal-private"
);
assert_eq!(CONTRACT_VERSION, (1, 22));
assert_eq!(CONTRACT_VERSION, (1, 23));
for (mode, wire) in [
(RedactMode::Text, "text"),
(RedactMode::StructuredArguments, "structured_arguments"),
Expand Down
Loading
Loading