Skip to main content

packc/
component_host_stubs.rs

1#![forbid(unsafe_code)]
2
3use anyhow::Result;
4use wasmtime::component::Linker;
5use wasmtime_wasi::p2::add_to_linker_sync;
6use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
7use wasmtime_wasi_http::WasiHttpCtx;
8use wasmtime_wasi_http::p2::{WasiHttpCtxView, WasiHttpView, add_only_http_to_linker_sync};
9use wasmtime_wasi_tls::p2::{
10    LinkOptions as WasiTlsLinkOptions, add_to_linker as add_tls_to_linker,
11};
12use wasmtime_wasi_tls::{
13    UnsupportedProvider, WasiTlsCtx, WasiTlsCtxBuilder, WasiTlsCtxView, WasiTlsView,
14};
15
16pub struct DescribeHostState {
17    table: ResourceTable,
18    wasi: WasiCtx,
19    wasi_http: WasiHttpCtx,
20    wasi_tls: WasiTlsCtx,
21}
22
23impl Default for DescribeHostState {
24    fn default() -> Self {
25        // Describe-only paths should be offline-safe: no inherited env/args,
26        // no preopened directories, and no ambient stdio requirements.
27        let _ = rustls::crypto::ring::default_provider().install_default();
28        let mut wasi = WasiCtxBuilder::new();
29        Self {
30            table: ResourceTable::new(),
31            wasi: wasi.build(),
32            wasi_http: WasiHttpCtx::new(),
33            wasi_tls: WasiTlsCtxBuilder::new()
34                .provider(Box::new(UnsupportedProvider::default()))
35                .build(),
36        }
37    }
38}
39
40impl WasiView for DescribeHostState {
41    fn ctx(&mut self) -> WasiCtxView<'_> {
42        WasiCtxView {
43            table: &mut self.table,
44            ctx: &mut self.wasi,
45        }
46    }
47}
48
49impl WasiHttpView for DescribeHostState {
50    fn http(&mut self) -> WasiHttpCtxView<'_> {
51        WasiHttpCtxView {
52            ctx: &mut self.wasi_http,
53            table: &mut self.table,
54            hooks: Default::default(),
55        }
56    }
57}
58
59impl WasiTlsView for DescribeHostState {
60    fn tls(&mut self) -> WasiTlsCtxView<'_> {
61        WasiTlsCtxView {
62            ctx: &mut self.wasi_tls,
63            table: &mut self.table,
64        }
65    }
66}
67
68pub fn add_describe_host_imports(linker: &mut Linker<DescribeHostState>) -> Result<()> {
69    // Some WASI helper registrars may re-export overlapping interface names
70    // (for example `wasi:io/*`) across preview2, TLS, and HTTP worlds.
71    linker.allow_shadowing(true);
72
73    add_to_linker_sync(linker)
74        .map_err(|err| anyhow::anyhow!("register wasi preview2 describe host stubs: {err}"))?;
75
76    let mut tls_options = WasiTlsLinkOptions::default();
77    tls_options.tls(true);
78    add_tls_to_linker(linker, &tls_options)
79        .map_err(|err| anyhow::anyhow!("register wasi tls describe host stubs: {err}"))?;
80
81    add_only_http_to_linker_sync(linker)
82        .map_err(|err| anyhow::anyhow!("register wasi http describe host stubs: {err}"))?;
83
84    // NOTE: greentic host interfaces (state-store, secrets-store, http-client,
85    // interfaces-types, etc.) are NOT registered here. They are handled by
86    // `stub_remaining_imports` which uses `define_unknown_imports_as_traps` to
87    // provide trap stubs for any component imports not already in the linker.
88    // This avoids having to maintain an exhaustive list of every greentic
89    // interface version a component might import.
90    Ok(())
91}
92
93/// Stub any component imports not already registered in the linker as traps.
94///
95/// This must be called AFTER `add_describe_host_imports` so that real WASI
96/// implementations take priority. All remaining imports (greentic host
97/// interfaces like secrets-store, http-client, etc.) become traps — safe
98/// because the describe-only code path never invokes them.
99pub fn stub_remaining_imports(
100    linker: &mut Linker<DescribeHostState>,
101    component: &wasmtime::component::Component,
102) -> Result<()> {
103    linker
104        .define_unknown_imports_as_traps(component)
105        .map_err(|err| anyhow::anyhow!("stub remaining component imports: {err}"))
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn default_host_state_exposes_wasi_and_http_views() {
114        let mut state = DescribeHostState::default();
115
116        let ctx_view = state.ctx();
117        let _ = ctx_view.table;
118        let _ = ctx_view.ctx;
119
120        let http_view = state.http();
121        let _ = http_view.table;
122        let _ = http_view.ctx;
123    }
124
125    #[test]
126    fn describe_host_imports_register_without_error() {
127        let engine = wasmtime::Engine::default();
128        let mut linker = Linker::new(&engine);
129        add_describe_host_imports(&mut linker).expect("host imports should register");
130    }
131}