Skip to main content

camel_component_wasm/
capabilities.rs

1//! Per-world capability allowlist for WASM host functions.
2//!
3//! Controls which host operations a WASM guest can invoke based on the
4//! plugin kind. Policy worlds (AuthorizationPolicy, SecurityPolicy) get a
5//! fully denied set — they cannot call `camel_call`, `camel_poll`,
6//! `host_store`, or `host_load`. Processor and Bean worlds get an
7//! explicitly-configured scheme allowlist (fail-closed: empty by default).
8
9use std::collections::HashSet;
10
11/// Per-world capability allowlist.
12#[derive(Clone, Debug, Default)]
13pub struct WasmCapabilities {
14    /// URI schemes the guest may call via `camel_call` / `camel_poll`.
15    /// Empty set = deny all (fail-closed).
16    pub call_schemes: HashSet<String>,
17    /// Whether `host_store` / `host_load` are available.
18    pub host_kv: bool,
19}
20
21impl WasmCapabilities {
22    /// Check whether a URI scheme may be called.
23    pub fn can_call(&self, scheme: &str) -> bool {
24        self.call_schemes.contains(scheme)
25    }
26
27    /// Policy world capabilities — everything denied.
28    pub fn denied() -> Self {
29        Self::default()
30    }
31
32    /// Build capabilities from a comma-separated scheme list (e.g. "log,direct").
33    /// host_kv defaults to true for Processor/Bean (trusted plugin kinds).
34    pub fn from_scheme_list(schemes: &str) -> Self {
35        let call_schemes = schemes
36            .split(',')
37            .map(|s| s.trim().to_string())
38            .filter(|s| !s.is_empty())
39            .collect();
40        Self {
41            call_schemes,
42            host_kv: true,
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn denied_capabilities_block_all_calls() {
53        let caps = WasmCapabilities::denied();
54        assert!(!caps.can_call("log"));
55        assert!(!caps.can_call("direct"));
56        assert!(!caps.host_kv);
57    }
58
59    #[test]
60    fn from_scheme_list_grants_listed_schemes() {
61        let caps = WasmCapabilities::from_scheme_list("log,direct,file");
62        assert!(caps.can_call("log"));
63        assert!(caps.can_call("direct"));
64        assert!(caps.can_call("file"));
65        assert!(!caps.can_call("kafka"));
66        assert!(caps.host_kv);
67    }
68
69    #[test]
70    fn from_scheme_list_empty_is_denied() {
71        let caps = WasmCapabilities::from_scheme_list("");
72        assert!(caps.call_schemes.is_empty());
73        assert!(!caps.can_call("anything"));
74    }
75
76    #[test]
77    fn from_scheme_list_trims_whitespace() {
78        let caps = WasmCapabilities::from_scheme_list(" log , direct ");
79        assert!(caps.can_call("log"));
80        assert!(caps.can_call("direct"));
81    }
82
83    #[test]
84    fn default_is_fail_closed() {
85        let caps = WasmCapabilities::default();
86        assert!(caps.call_schemes.is_empty());
87        assert!(!caps.host_kv);
88    }
89}