Wasmtime 48 Denies Sockets by Default: What Breaks and Fixes
Table of Contents
I’m writing this while on the first leg of my flight from Frankfurt to Chicago, on my way to Salt Lake City for KubeCon NA 2026. Long flights are a good excuse to catch up on release notes, and one entry in the Wasmtime 48 release notes deserves a closer look because it will bite you the moment you upgrade an embedder.
What Changed in Wasmtime 48
Wasmtime 48.0.0 (released 2026-08-20) lists this under “Changed”: the wasmtime-wasi crate’s default configuration now denies creation of TCP/UDP sockets.
The pull request explains the motivation. Before, sockets could be created by default, but every address was denied. That worked in theory, but it was a brittle check, so the default is now to deny TCP/UDP entirely.
The Wasmtime CLI keeps the historical behavior for sockets. Passing -Sinherit-network enables TCP/UDP again, and an explicit -Stcp=n still shadows it. DNS is a separate switch, though. Name lookup stays off until you pass -Sallow-ip-name-lookup, so connecting to a hostname needs both flags.
The release notes do not include or link migration steps, so let’s go through them.
Reproduce the Failure
All we need is a guest that opens a TCP connection. Using std::net is enough on wasm32-wasip2:
use std::net::TcpStream;
fn main() {
match TcpStream::connect("spinframework.dev:443") {
Ok(_) => println!("connected"),
Err(e) => {
eprintln!("connect failed: {e}");
std::process::exit(1);
}
}
}
Build it for wasm32-wasip2 and run it with a host built on wasmtime-wasi 48 or newer. The connect call fails and the guest prints the error. Here is the catch: with a hostname, you get failed to lookup address information: Non-recoverable error, and it’s the same message with and without -Sinherit-network. The guest has to resolve the name before it can open a socket, and name lookup is denied by default. TCP itself is never even attempted. To see the TCP denial on its own, connect to an IP literal instead of a hostname.
The case that catches most people is an embedder that only called inherit_network() and relied on that to allow connections: that setup keeps compiling, but TCP creation is now denied.
Fix: The wasmtime CLI
With the Wasmtime CLI, restore the previous behavior using -Sinherit-network. If your guest connects to hostnames, also allow name lookup:
# Restore previous behavior
wasmtime run -Sinherit-network \
-Sallow-ip-name-lookup \
guest.wasm
If you want to inherit the network but still block TCP, combine the flags:
# Explicitly block TCP
wasmtime run -Sinherit-network \
-Sallow-ip-name-lookup \
-Stcp=n \
guest.wasm
Fix: Embedding wasmtime in an Rust App
For embedders, the sockets switches live on WasiCtxBuilder. In wasmtime-wasi 48.0.8, allow_tcp, allow_udp and allow_ip_name_lookup are all documented as disabled by default.
Note that inherit_network() alone is not the whole story here. In the 48.0.8 source, it only installs an address check that accepts every address. Whether TCP or UDP can be used at all is controlled by allow_tcp and allow_udp, and DNS by allow_ip_name_lookup. So the minimal opt-in for TCP to a hostname looks like this:
let mut builder = WasiCtx::builder();
builder
.inherit_stdio()
.allow_tcp(true)
.allow_ip_name_lookup(true)
.inherit_network();
The CLI covers TCP, UDP and the address check with -Sinherit-network, and name lookup with -Sallow-ip-name-lookup. When embedding Wasmtime in your application, you set each of them yourself. Drop allow_ip_name_lookup(true) if your guest only connects to IP addresses.
Least Privilege: Allow-Lists Instead of Blanket Access
inherit_network() is the blunt option. A guest can then connect to anything the host can reach. socket_addr_check lets you decide per address and per kind of use instead. Here is a complete embedder that runs a command component and only allows outbound TCP connections on port 443:
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Engine, Result, Store};
use wasmtime_wasi::p2::bindings::sync::Command;
use wasmtime_wasi::sockets::SocketAddrUse;
use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
struct State {
ctx: WasiCtx,
table: ResourceTable,
}
impl WasiView for State {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView {
ctx: &mut self.ctx,
table: &mut self.table,
}
}
}
fn main() -> Result<()> {
let path = std::env::args().nth(1).expect("path to component");
let engine = Engine::default();
let mut linker = Linker::<State>::new(&engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
let component = Component::from_file(&engine, &path)?;
let mut builder = WasiCtx::builder();
builder
.inherit_stdio()
.allow_tcp(true)
.allow_ip_name_lookup(true)
.socket_addr_check(|addr, kind| {
Box::pin(async move {
match kind {
SocketAddrUse::TcpBind => addr.port() == 0,
SocketAddrUse::TcpConnect => addr.port() == 443,
_ => false,
}
})
});
let mut store = Store::new(
&engine,
State {
ctx: builder.build(),
table: ResourceTable::new(),
},
);
let command = Command::instantiate(&mut store, &component, &linker)?;
command
.wasi_cli_run()
.call_run(&mut store)?
.map_err(|()| wasmtime::format_err!("guest failed"))
}
The check receives the SocketAddr and a SocketAddrUse. Returning false rejects the operation. Because the check runs for binds as well, a guest that only needs to connect gets exactly that and nothing else.
Where Wasmtime 48 Stops a Socket
How This Relates to Spin’s Outbound Networking
Spin
takes the same least-privilege approach at the application level. Outbound requests are denied unless a component lists the destination in allowed_outbound_hosts:
# Spin Application Manifest (spin.toml)
[component.example]
allowed_outbound_hosts = ["https://api.github.com"]
A request to a host or port that isn’t listed fails with an error.
Recap
Wasmtime 48 flipped wasmtime-wasi to deny TCP/UDP by default, and name lookup is denied too. On the CLI, -Sinherit-network brings back sockets, and -Sallow-ip-name-lookup brings back DNS. When embedding Wasmtime, call allow_tcp(true) (plus allow_udp(true) and allow_ip_name_lookup(true) if needed) and decide between inherit_network() and a socket_addr_check allow-list.