confium_sandbox_wasm/
imports.rs1use std::collections::HashSet;
18use std::sync::Mutex;
19
20use crate::sandbox::Capability;
21
22#[derive(Debug, Default)]
27pub(crate) struct CapabilitySet {
28 caps: Mutex<HashSet<Capability>>,
29}
30
31impl CapabilitySet {
32 pub(crate) fn new() -> Self {
33 Self::default()
34 }
35
36 pub(crate) fn grant(&self, cap: Capability) {
37 let mut guard = self.caps.lock().expect("capability mutex poisoned");
38 guard.insert(cap);
39 }
40
41 pub(crate) fn revoke(&self, cap: &Capability) {
42 let mut guard = self.caps.lock().expect("capability mutex poisoned");
43 guard.remove(cap);
44 }
45
46 #[allow(dead_code)]
50 pub(crate) fn has(&self, cap: &Capability) -> bool {
51 let guard = self.caps.lock().expect("capability mutex poisoned");
52 guard.contains(cap)
53 }
54
55 pub(crate) fn has_interface(&self, name: &str) -> bool {
58 let guard = self.caps.lock().expect("capability mutex poisoned");
59 guard.iter().any(|c| match c {
60 Capability::InterfaceAccess { name: n } => n == name,
61 _ => false,
62 })
63 }
64}
65
66#[derive(Debug)]
68pub(crate) enum ImportOutcome {
69 Done(i64),
71 Denied,
74}
75
76pub(crate) struct HostImports;
84
85impl HostImports {
86 pub(crate) fn cfm_hash_update(caps: &CapabilitySet, len: i32) -> ImportOutcome {
91 if !Self::has_interface(caps, "hash") {
92 return ImportOutcome::Denied;
93 }
94 ImportOutcome::Done(i64::from(len))
96 }
97
98 pub(crate) fn cfm_net_send(caps: &CapabilitySet, url_id: i64) -> ImportOutcome {
107 if !Self::has_any_network(caps) {
108 return ImportOutcome::Denied;
109 }
110 ImportOutcome::Done(url_id)
112 }
113
114 pub(crate) fn cfm_key_get_secret(caps: &CapabilitySet, key_id: i64) -> ImportOutcome {
118 if !Self::has_any_key(caps) {
119 return ImportOutcome::Denied;
120 }
121 let _ = key_id;
123 ImportOutcome::Done(32)
124 }
125
126 fn has_interface(caps: &CapabilitySet, name: &str) -> bool {
130 caps.has_interface(name)
131 }
132
133 fn has_any_network(caps: &CapabilitySet) -> bool {
134 let guard = caps.caps.lock().expect("capability mutex poisoned");
135 guard
136 .iter()
137 .any(|c| matches!(c, Capability::NetworkEndpoint { .. }))
138 }
139
140 fn has_any_key(caps: &CapabilitySet) -> bool {
141 let guard = caps.caps.lock().expect("capability mutex poisoned");
142 guard
143 .iter()
144 .any(|c| matches!(c, Capability::KeyAccess { .. }))
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn cfm_hash_update_denied_without_capability() {
154 let caps = CapabilitySet::new();
155 let out = HostImports::cfm_hash_update(&caps, 7);
156 match out {
157 ImportOutcome::Denied => {}
158 other => panic!("expected Denied, got {:?}", other),
159 }
160 }
161
162 #[test]
163 fn cfm_hash_update_permitted_with_capability() {
164 let caps = CapabilitySet::new();
165 caps.grant(Capability::InterfaceAccess {
166 name: "hash".into(),
167 });
168 let out = HostImports::cfm_hash_update(&caps, 7);
169 assert!(matches!(out, ImportOutcome::Done(7)));
170 }
171
172 #[test]
173 fn cfm_key_get_secret_denied_without_key_capability() {
174 let caps = CapabilitySet::new();
175 let out = HostImports::cfm_key_get_secret(&caps, 0);
176 assert!(matches!(out, ImportOutcome::Denied));
177 }
178
179 #[test]
180 fn cfm_key_get_secret_permitted_with_key_capability() {
181 let caps = CapabilitySet::new();
182 caps.grant(Capability::KeyAccess {
183 key_id: "k1".into(),
184 });
185 let out = HostImports::cfm_key_get_secret(&caps, 0);
186 assert!(matches!(out, ImportOutcome::Done(32)));
187 }
188
189 #[test]
190 fn revoke_takes_effect() {
191 let caps = CapabilitySet::new();
192 caps.grant(Capability::InterfaceAccess {
193 name: "hash".into(),
194 });
195 assert!(matches!(
196 HostImports::cfm_hash_update(&caps, 1),
197 ImportOutcome::Done(1)
198 ));
199 caps.revoke(&Capability::InterfaceAccess {
200 name: "hash".into(),
201 });
202 assert!(matches!(
203 HostImports::cfm_hash_update(&caps, 1),
204 ImportOutcome::Denied
205 ));
206 }
207}