Skip to main content

spin_capabilities/
deny.rs

1use crate::{
2    AI_MODELS, ALLOWED_OUTBOUND_HOSTS, CAPABILITY_SETS, ENVIRONMENT, FILES, InheritConfiguration,
3    KEY_VALUE_STORES, SQLITE_DATABASES, VARIABLES,
4};
5use wac_graph::types::{SubtypeChecker, are_semver_compatible};
6use wac_graph::{CompositionGraph, types::Package};
7
8/// Composes a deny adapter into a Wasm component to block host capabilities that
9/// are not explicitly inherited.
10///
11/// Given the raw bytes of a Wasm component (`source`) and an [`InheritConfiguration`]
12/// describing which capability sets should remain accessible, this function uses
13/// `wac-graph` to wire a bundled deny-all adapter into the component's imports.
14/// Interfaces listed in the allow set (derived from `inherits`) are left untouched
15/// so the host can satisfy them at runtime; all other matching imports are fulfilled
16/// by the deny adapter, which traps on any call.
17///
18/// If the deny adapter has no exports that match the component's imports (i.e. no
19/// plugging is needed), the original `source` bytes are returned unchanged.
20pub fn apply_deny_adapter(
21    source: &[u8],
22    inherits: InheritConfiguration,
23) -> anyhow::Result<Vec<u8>> {
24    let allow = allow_list(inherits);
25
26    const SPIN_DENY_ADAPTER_BYTES: &[u8] = include_bytes!("../deny_adapter.wasm");
27
28    let mut graph = CompositionGraph::new();
29
30    let dependency_package = Package::from_bytes("dependency", None, source, graph.types_mut())?;
31
32    let dependency_id = graph.register_package(dependency_package)?;
33
34    let deny_adapter_package = Package::from_bytes(
35        "spin-deny-all-adapter",
36        None,
37        SPIN_DENY_ADAPTER_BYTES,
38        graph.types_mut(),
39    )?;
40
41    let deny_adapter_id = graph.register_package(deny_adapter_package)?;
42
43    // Selective plug: wire up only exports NOT in the allow list.
44    let socket_instantiation = graph.instantiate(dependency_id);
45
46    let mut plug_exports: Vec<(String, String)> = Vec::new();
47    let mut cache = Default::default();
48    let mut checker = SubtypeChecker::new(&mut cache);
49    for (name, plug_ty) in &graph.types()[graph[deny_adapter_id].ty()].exports {
50        // Skip interfaces that should be allowed (inherited from host).
51        if allow.iter().any(|a| *a == name) {
52            continue;
53        }
54
55        let matching_import = graph.types()[graph[dependency_id].ty()]
56            .imports
57            .get(name)
58            .map(|ty| (name.clone(), ty))
59            .or_else(|| {
60                graph.types()[graph[dependency_id].ty()]
61                    .imports
62                    .iter()
63                    .find(|(import_name, _)| are_semver_compatible(name, import_name))
64                    .map(|(import_name, ty)| (import_name.clone(), ty))
65            });
66
67        if let Some((socket_name, socket_ty)) = matching_import {
68            if checker
69                .is_subtype(*plug_ty, graph.types(), *socket_ty, graph.types())
70                .is_ok()
71            {
72                plug_exports.push((name.clone(), socket_name));
73            }
74        }
75    }
76
77    if plug_exports.is_empty() {
78        // No plugging needed — return the original source as-is.
79        return Ok(source.to_vec());
80    }
81
82    let plug_instantiation = graph.instantiate(deny_adapter_id);
83    for (plug_name, socket_name) in plug_exports {
84        let export = graph.alias_instance_export(plug_instantiation, &plug_name)?;
85        graph.set_instantiation_argument(socket_instantiation, &socket_name, export)?;
86    }
87
88    // Export all exports from the socket (dependency) component.
89    for name in graph.types()[graph[dependency_id].ty()]
90        .exports
91        .keys()
92        .cloned()
93        .collect::<Vec<_>>()
94    {
95        let export = graph.alias_instance_export(socket_instantiation, &name)?;
96        graph.export(export, &name)?;
97    }
98
99    let bytes = graph.encode(Default::default())?;
100    Ok(bytes)
101}
102
103fn allow_list(inherits: InheritConfiguration) -> Vec<&'static str> {
104    let mut allow = vec![];
105
106    match inherits {
107        InheritConfiguration::All => {
108            for (_, capability_set) in CAPABILITY_SETS {
109                allow.extend_from_slice(capability_set);
110            }
111        }
112        InheritConfiguration::Some(inherits) => {
113            for config in inherits {
114                match config.as_str() {
115                    "ai_models" => allow.extend_from_slice(AI_MODELS),
116                    "allowed_outbound_hosts" => allow.extend_from_slice(ALLOWED_OUTBOUND_HOSTS),
117                    "environment" => allow.extend_from_slice(ENVIRONMENT),
118                    "files" => allow.extend_from_slice(FILES),
119                    "key_value_stores" => allow.extend_from_slice(KEY_VALUE_STORES),
120                    "sqlite_databases" => allow.extend_from_slice(SQLITE_DATABASES),
121                    "variables" => allow.extend_from_slice(VARIABLES),
122                    _ => {}
123                }
124            }
125        }
126        InheritConfiguration::None => {}
127    }
128
129    allow
130}