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::{
10    LinkOptions as WasiTlsLinkOptions, WasiTls, WasiTlsCtx, WasiTlsCtxBuilder,
11};
12
13pub struct DescribeHostState {
14    table: ResourceTable,
15    wasi: WasiCtx,
16    wasi_http: WasiHttpCtx,
17    wasi_tls: WasiTlsCtx,
18}
19
20impl Default for DescribeHostState {
21    fn default() -> Self {
22        // Describe-only paths should be offline-safe: no inherited env/args,
23        // no preopened directories, and no ambient stdio requirements.
24        let mut wasi = WasiCtxBuilder::new();
25        Self {
26            table: ResourceTable::new(),
27            wasi: wasi.build(),
28            wasi_http: WasiHttpCtx::new(),
29            wasi_tls: WasiTlsCtxBuilder::new().build(),
30        }
31    }
32}
33
34impl WasiView for DescribeHostState {
35    fn ctx(&mut self) -> WasiCtxView<'_> {
36        WasiCtxView {
37            table: &mut self.table,
38            ctx: &mut self.wasi,
39        }
40    }
41}
42
43impl WasiHttpView for DescribeHostState {
44    fn http(&mut self) -> WasiHttpCtxView<'_> {
45        WasiHttpCtxView {
46            ctx: &mut self.wasi_http,
47            table: &mut self.table,
48            hooks: Default::default(),
49        }
50    }
51}
52
53pub fn add_describe_host_imports(linker: &mut Linker<DescribeHostState>) -> Result<()> {
54    // Some WASI helper registrars may re-export overlapping interface names
55    // (for example `wasi:io/*`) across preview2, TLS, and HTTP worlds.
56    linker.allow_shadowing(true);
57
58    add_to_linker_sync(linker)
59        .map_err(|err| anyhow::anyhow!("register wasi preview2 describe host stubs: {err}"))?;
60
61    let mut tls_options = WasiTlsLinkOptions::default();
62    tls_options.tls(true);
63    wasmtime_wasi_tls::add_to_linker(linker, &mut tls_options, |host: &mut DescribeHostState| {
64        WasiTls::new(&host.wasi_tls, &mut host.table)
65    })
66    .map_err(|err| anyhow::anyhow!("register wasi tls describe host stubs: {err}"))?;
67
68    add_only_http_to_linker_sync(linker)
69        .map_err(|err| anyhow::anyhow!("register wasi http describe host stubs: {err}"))?;
70
71    // NOTE: greentic host interfaces (state-store, secrets-store, http-client,
72    // interfaces-types, etc.) are NOT registered here. They are handled by
73    // `stub_remaining_imports` which uses `define_unknown_imports_as_traps` to
74    // provide trap stubs for any component imports not already in the linker.
75    // This avoids having to maintain an exhaustive list of every greentic
76    // interface version a component might import.
77    Ok(())
78}
79
80/// Stub any component imports not already registered in the linker as traps.
81///
82/// This must be called AFTER `add_describe_host_imports` so that real WASI
83/// implementations take priority. All remaining imports (greentic host
84/// interfaces like secrets-store, http-client, etc.) become traps — safe
85/// because the describe-only code path never invokes them.
86pub fn stub_remaining_imports(
87    linker: &mut Linker<DescribeHostState>,
88    component: &wasmtime::component::Component,
89) -> Result<()> {
90    linker
91        .define_unknown_imports_as_traps(component)
92        .map_err(|err| anyhow::anyhow!("stub remaining component imports: {err}"))
93}