use crate::actor::{decode_meta_strings, meta_str, new_request_id, pack_visible_request_id};
use crate::audit::fmt_filter;
use crate::store::{declared_constraints, warn_if_credentials_exfil_risk};
use crate::*;
use std::sync::Arc;
#[test]
fn well_known_metadata_keys_are_extracted_for_the_envelope() {
let md = vec![
("std:session-id".to_string(), "abc123".to_string()),
("std:agent-id".to_string(), "claude-code".to_string()),
("std:traceparent".to_string(), "00-aa-bb-01".to_string()),
("other".to_string(), "x".to_string()),
];
assert_eq!(meta_str(&md, "std:session-id").as_deref(), Some("abc123"));
assert_eq!(
meta_str(&md, "std:agent-id").as_deref(),
Some("claude-code")
);
assert_eq!(
meta_str(&md, "std:traceparent").as_deref(),
Some("00-aa-bb-01")
);
assert_eq!(meta_str(&md, "std:request-id"), None);
assert_eq!(meta_str(&[], "std:session-id"), None);
}
#[test]
fn visible_request_id_prefixes_are_distinct_for_hundreds_of_counters_at_one_salt() {
let salt = 0x1234;
let mut seen = std::collections::HashSet::new();
for counter in 0..500u64 {
let visible = pack_visible_request_id(counter, salt);
assert!(
seen.insert(visible),
"counter {counter} collided with an earlier one at salt {salt:#x}: \
visible={visible:#08x}"
);
}
}
#[test]
fn fixed_width_packing_avoids_a_confirmed_variable_width_collision() {
let naive = |counter: u64, salt: u32| -> String {
let s = format!("{counter:x}{salt:x}");
s.chars().take(6).collect()
};
assert_eq!(
naive(1, 0x11111),
naive(0x11, 0x11111),
"sanity check: this is the confirmed collision in the naive scheme"
);
let a = format!("{:06x}", pack_visible_request_id(1, 0x11111));
let b = format!("{:06x}", pack_visible_request_id(0x11, 0x11111));
assert_ne!(
a, b,
"fixed-width packing must not reproduce the naive collision"
);
}
#[test]
fn decode_meta_strings_reads_cbor_text_and_drops_everything_else() {
let raw: Vec<(String, Vec<u8>)> = vec![
(
"std:session-id".to_string(),
act_types::cbor::to_cbor(&"abc123".to_string()),
),
(
"std:request-id".to_string(),
act_types::cbor::to_cbor(&42u64),
),
];
let decoded = decode_meta_strings(&raw);
assert_eq!(
meta_str(&decoded, "std:session-id").as_deref(),
Some("abc123")
);
assert_eq!(meta_str(&decoded, "std:request-id"), None);
}
#[test]
fn a_request_id_is_always_available() {
let a = new_request_id();
let b = new_request_id();
assert_ne!(a, b);
assert!(!a.is_empty());
}
#[test]
fn load_component_reports_the_digest_of_the_file_bytes() {
let engine = create_engine().expect("engine");
let path = std::path::Path::new("tests/fixtures/ask-canary.wasm");
if !path.exists() {
return;
}
let bytes = std::fs::read(path).expect("read fixture");
let (_component, digest) = load_component(&engine, path).expect("load");
assert_eq!(digest, crate::audit::sha256_hex(&bytes));
}
use act_policy::grant::PolicyMode;
use act_policy::providers::credentials::CAP_CREDENTIALS;
fn info_from_act_toml(src: &str) -> ComponentInfo {
toml::from_str(src).expect("act.toml fragment must parse into ComponentInfo")
}
#[test]
fn a_bare_credentials_table_is_handed_to_the_provider_as_declared() {
let info = info_from_act_toml(
r#"
[std]
name = "notion"
[std.capabilities."act:credentials"]
"#,
);
assert!(
info.std
.capabilities
.get(CAP_CREDENTIALS)
.expect("capability must be present in the parsed manifest")
.constraints
.is_empty(),
"the bare-table form is expected to carry zero constraints; if this \
ever changes, this test stops exercising the trap"
);
assert!(
declared_constraints(&info, CAP_CREDENTIALS).is_some(),
"a component that declared act:credentials must reach the provider \
as Some, even though its constraint list is empty"
);
}
#[test]
fn an_undeclared_credentials_capability_is_handed_over_as_none() {
let info = info_from_act_toml(
r#"
[std]
name = "no-secrets"
[std.capabilities."wasi:http"]
constraints = [{ host = "api.notion.com" }]
"#,
);
assert!(
!info.std.capabilities.has(CAP_CREDENTIALS),
"sanity: this manifest must not declare act:credentials"
);
assert_eq!(
declared_constraints(&info, CAP_CREDENTIALS),
None,
"an undeclared act:credentials must come back as None so the provider denies it"
);
}
#[test]
fn a_bare_declaration_reports_some_empty_for_any_class() {
let info = info_from_act_toml(
r#"
[std]
name = "bare-physical"
[std.capabilities."wasi:filesystem"]
[std.capabilities."wasi:http"]
[std.capabilities."wasi:sockets"]
"#,
);
for cap in [
act_types::constants::CAP_FILESYSTEM,
act_types::constants::CAP_HTTP,
act_types::constants::CAP_SOCKETS,
] {
assert!(
info.std.capabilities.has(cap),
"sanity: {cap} must be declared in this manifest"
);
assert_eq!(
declared_constraints(&info, cap),
Some(Vec::new()),
"{cap} is declared bare, so its declared slice must be Some(vec![])"
);
}
}
#[test]
fn declared_constraints_passes_physical_constraints_through_verbatim() {
let info = info_from_act_toml(
r#"
[std]
name = "scoped"
[std.capabilities."wasi:http"]
constraints = [{ host = "api.notion.com" }, { host = "*.example.com" }]
"#,
);
assert_eq!(
declared_constraints(&info, act_types::constants::CAP_HTTP),
Some(vec![
serde_json::json!({ "host": "api.notion.com" }),
serde_json::json!({ "host": "*.example.com" }),
]),
"physical classes must see their manifest constraints untouched"
);
}
async fn ceilings_for(
info: &ComponentInfo,
policy: &act_policy::grant::GrantPolicy,
) -> Vec<(String, Arc<dyn act_policy::provider::CompiledCeiling>)> {
let engine = create_engine().expect("engine");
let (_store, ceilings) = create_store(
&engine,
&[],
policy,
info,
None,
Arc::new(act_policy::consent::DenyPrompter),
Arc::new(act_policy::consent::DecisionCache::new()),
None,
"./test.wasm",
)
.await
.expect("create_store");
ceilings
}
#[tokio::test(flavor = "current_thread")]
async fn the_credentials_class_is_among_the_ceilings_the_audit_header_renders() {
let info = info_from_act_toml(
r#"
[std]
name = "notion"
[std.capabilities."act:credentials"]
"#,
);
let ceilings = ceilings_for(&info, &grants(&[(CAP_CREDENTIALS, PolicyMode::Ask)])).await;
let (_, ceiling) = ceilings
.iter()
.find(|(id, _)| id == CAP_CREDENTIALS)
.expect("act:credentials must be one of the resolved ceilings");
assert!(
ceiling.declared(),
"the manifest declared it, so the header must not report otherwise"
);
assert_eq!(ceiling.effective_mode(), PolicyMode::Ask);
}
#[tokio::test(flavor = "current_thread")]
async fn a_declared_semantic_class_gets_a_ceiling() {
let info = info_from_act_toml(
r#"
[std]
name = "inventory"
[std.capabilities."db:drop"]
"#,
);
let ceilings = ceilings_for(&info, &grants(&[("db:drop", PolicyMode::Ask)])).await;
assert!(
ceilings.iter().any(|(id, _)| id == "db:drop"),
"a declared semantic class must appear among the resolved ceilings"
);
}
#[tokio::test(flavor = "current_thread")]
async fn an_undeclared_credentials_class_is_still_reported_and_resolves_to_deny() {
let info = info_from_act_toml(
r#"
[std]
name = "crypto"
"#,
);
let ceilings = ceilings_for(&info, &grants(&[(CAP_CREDENTIALS, PolicyMode::Open)])).await;
let (_, ceiling) = ceilings
.iter()
.find(|(id, _)| id == CAP_CREDENTIALS)
.expect("act:credentials must be reported even when undeclared");
assert!(!ceiling.declared());
assert_eq!(
ceiling.effective_mode(),
PolicyMode::Deny,
"an open grant must not widen a class the component never declared"
);
}
#[derive(Clone, Default)]
struct CapturedLog(Arc<std::sync::Mutex<Vec<u8>>>);
impl CapturedLog {
fn contents(&self) -> String {
String::from_utf8(self.0.lock().unwrap().clone()).expect("utf-8 log output")
}
}
impl std::io::Write for CapturedLog {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for CapturedLog {
type Writer = Self;
fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
fn grants(pairs: &[(&str, PolicyMode)]) -> act_policy::grant::GrantPolicy {
act_policy::grant::GrantPolicy {
default: PolicyMode::Deny,
entries: pairs
.iter()
.map(|(id, mode)| {
(
(*id).to_string(),
act_policy::grant::CapabilityGrant {
mode: *mode,
allow: vec![],
deny: vec![],
},
)
})
.collect(),
}
}
fn capture_exfil_warning(info: &ComponentInfo, network_grants: &[(&str, PolicyMode)]) -> String {
use tracing_subscriber::prelude::*;
let log = CapturedLog::default();
let subscriber = tracing_subscriber::registry().with(
tracing_subscriber::fmt::layer()
.with_writer(log.clone())
.with_ansi(false)
.with_filter(fmt_filter(tracing_subscriber::EnvFilter::new("act=info"))),
);
let policy = grants(network_grants);
tracing::subscriber::with_default(subscriber, || {
warn_if_credentials_exfil_risk(info, &policy);
});
log.contents()
}
fn credentials_and_reachable_http() -> ComponentInfo {
info_from_act_toml(
r#"
[std]
name = "notion-sync"
[std.capabilities."act:credentials"]
[std.capabilities."wasi:http"]
constraints = [{ host = "api.notion.com" }, { host = "*.notion.so" }]
"#,
)
}
fn credentials_and_reachable_sockets() -> ComponentInfo {
info_from_act_toml(
r#"
[std]
name = "pg-sync"
[std.capabilities."act:credentials"]
[std.capabilities."wasi:sockets"]
constraints = [{ host = "db.internal", ports = [5432], protocols = ["tcp"] }]
"#,
)
}
#[test]
fn credentials_plus_open_http_warns_about_exfiltration_not_just_the_two_names() {
let out = capture_exfil_warning(
&credentials_and_reachable_http(),
&[
(act_types::constants::CAP_HTTP, PolicyMode::Open),
(act_types::constants::CAP_SOCKETS, PolicyMode::Deny),
],
);
assert!(
!out.is_empty(),
"the warning must actually reach an output layer — addressed to \
`act::audit` it is dropped by `fmt_filter` and then again by \
`AuditLayer::on_event`, and nothing is printed at all"
);
assert!(
out.contains("WARN"),
"the combination must be reported at WARN, got: {out}"
);
assert!(
out.contains("notion-sync"),
"must name the component it is about, got: {out}"
);
assert!(
out.contains("wasi:http"),
"must name the class whose grant is open, got: {out}"
);
assert!(
out.contains("send your credentials anywhere that declaration permits"),
"must state that credentials can be sent across that reach, got: {out}"
);
assert!(
out.contains("only limit on where it can reach"),
"must state that the artifact's own declaration is the last bound \
standing once the operator's grant is open, got: {out}"
);
assert!(
!out.contains("any host"),
"must not claim unbounded reach — an open grant is still bounded by \
the component's declaration, got: {out}"
);
}
#[test]
fn credentials_plus_open_sockets_warns_too_because_raw_tcp_exfiltrates_as_well() {
let out = capture_exfil_warning(
&credentials_and_reachable_sockets(),
&[
(act_types::constants::CAP_HTTP, PolicyMode::Deny),
(act_types::constants::CAP_SOCKETS, PolicyMode::Open),
],
);
assert!(
out.contains("wasi:sockets"),
"an open wasi:sockets grant must warn and name the class, got: {out}"
);
assert!(
out.contains("pg-sync"),
"must name the component it is about, got: {out}"
);
}
#[test]
fn both_network_classes_open_are_both_named() {
let info = info_from_act_toml(
r#"
[std]
name = "wide-open"
[std.capabilities."act:credentials"]
[std.capabilities."wasi:http"]
constraints = [{ host = "api.example.com" }]
[std.capabilities."wasi:sockets"]
constraints = [{ host = "db.internal", ports = [5432], protocols = ["tcp"] }]
"#,
);
let out = capture_exfil_warning(
&info,
&[
(act_types::constants::CAP_HTTP, PolicyMode::Open),
(act_types::constants::CAP_SOCKETS, PolicyMode::Open),
],
);
assert!(
out.contains("wasi:http") && out.contains("wasi:sockets"),
"both open classes must be named, got: {out}"
);
}
#[test]
fn an_open_grant_on_a_class_the_component_cannot_actually_reach_stays_silent() {
let bare_declaration = info_from_act_toml(
r#"
[std]
name = "bare-net"
[std.capabilities."act:credentials"]
[std.capabilities."wasi:http"]
[std.capabilities."wasi:sockets"]
"#,
);
let out = capture_exfil_warning(
&bare_declaration,
&[
(act_types::constants::CAP_HTTP, PolicyMode::Open),
(act_types::constants::CAP_SOCKETS, PolicyMode::Open),
],
);
assert!(
out.is_empty(),
"a bare network declaration is forced to Deny (effective.rs:118), so \
an open grant on it reaches nothing and must not warn, got: {out}"
);
let never_declared = info_from_act_toml(
r#"
[std]
name = "no-net"
[std.capabilities."act:credentials"]
"#,
);
let out = capture_exfil_warning(
&never_declared,
&[
(act_types::constants::CAP_HTTP, PolicyMode::Open),
(act_types::constants::CAP_SOCKETS, PolicyMode::Open),
],
);
assert!(
out.is_empty(),
"an undeclared class is forced to Deny (effective.rs:100) — \
`--allow wasi:http` buys such a component nothing, got: {out}"
);
}
#[test]
fn neither_capability_alone_triggers_the_exfiltration_warning() {
for mode in [PolicyMode::Deny, PolicyMode::Allowlist, PolicyMode::Ask] {
let out = capture_exfil_warning(
&credentials_and_reachable_http(),
&[
(act_types::constants::CAP_HTTP, mode),
(act_types::constants::CAP_SOCKETS, mode),
],
);
assert!(
out.is_empty(),
"act:credentials with network in {mode} mode must not warn, got: {out}"
);
}
let no_credentials = info_from_act_toml(
r#"
[std]
name = "plain-fetcher"
[std.capabilities."wasi:http"]
constraints = [{ host = "api.example.com" }]
[std.capabilities."wasi:sockets"]
constraints = [{ host = "db.internal", ports = [5432], protocols = ["tcp"] }]
"#,
);
let out = capture_exfil_warning(
&no_credentials,
&[
(act_types::constants::CAP_HTTP, PolicyMode::Open),
(act_types::constants::CAP_SOCKETS, PolicyMode::Open),
],
);
assert!(
out.is_empty(),
"open network without act:credentials must not warn, got: {out}"
);
}
#[tokio::test]
async fn a_closed_actor_channel_surfaces_as_an_internal_error() {
let (tx, rx) = tokio::sync::mpsc::channel(1);
drop(rx);
let handle = ComponentHandle::new(tx);
let err = handle
.list_tools(&Metadata::default())
.await
.expect_err("a dropped actor must not resolve");
match err {
ComponentError::Internal(e) => assert_eq!(e.to_string(), "component actor unavailable"),
ComponentError::Tool(_) => panic!("a dead actor is a host failure, not a tool error"),
}
}
#[test]
fn the_wildcard_bind_wasmtime_checks_before_a_connect_is_waved_through() {
use std::net::SocketAddr;
use wasmtime_wasi::sockets::SocketAddrUse;
for addr in ["0.0.0.0:0", "[::]:0"] {
let addr: SocketAddr = addr.parse().unwrap();
assert!(
store::is_local_implicit_bind(addr, SocketAddrUse::TcpBind),
"{addr} is the local side of an outbound socket, not a destination"
);
assert!(store::is_local_implicit_bind(addr, SocketAddrUse::UdpBind));
}
}
#[test]
fn nothing_that_confers_reach_is_waved_through() {
use std::net::SocketAddr;
use wasmtime_wasi::sockets::SocketAddrUse;
let wildcard: SocketAddr = "0.0.0.0:0".parse().unwrap();
for reason in [
SocketAddrUse::TcpConnect,
SocketAddrUse::TcpListen,
SocketAddrUse::TcpAccept,
SocketAddrUse::UdpSend,
SocketAddrUse::UdpReceive,
] {
assert!(
!store::is_local_implicit_bind(wildcard, reason),
"{reason:?} confers reach and must be gated even on {wildcard}"
);
}
for addr in ["0.0.0.0:8080", "127.0.0.1:0", "10.0.0.1:53"] {
let addr: SocketAddr = addr.parse().unwrap();
assert!(
!store::is_local_implicit_bind(addr, SocketAddrUse::TcpBind),
"{addr} names something concrete and must meet the ceiling"
);
}
}