Feature request: channels.msteams.webhook.host to bind the bot webhook to loopback only
Environment
- OpenClaw
2026.4.5 (3e72c03)
- Ubuntu 24.04.4 LTS
- Bot Framework webhook (msteams provider) listening on
0.0.0.0:3978 (current default)
Background
Our deployment terminates the Bot Framework webhook behind Cloudflare Tunnel (cloudflared), which connects from the local machine to Cloudflare's edge over QUIC and proxies inbound HTTP requests back to 127.0.0.1:3978. Because cloudflared always uses loopback to reach the origin, there is no need for the msteams webhook to bind to any non-loopback interface.
Our threat model wants the webhook reachable only via the cloudflared path, so any unintended exposure (e.g. from another tailnet device on the host's tailscale0 interface) is prevented at the network layer.
What's missing
channels.msteams has no webhook.host (or equivalent) config field. The only knobs we found are:
{
"channels": {
"msteams": {
"webhook": {
"port": 3978,
"path": "/api/messages"
}
}
}
}
port and path are honoured. There is no documented way to set the listen address.
Workaround we shipped
Defense-in-depth at the network layer with UFW:
sudo ufw insert 1 deny in on tailscale0 to any port 3978
sudo ufw insert 4 deny in on tailscale0 to any port 3978 # v6 rule
This blocks tailnet-side access to :3978 while leaving loopback (cloudflared → 127.0.0.1) unaffected. It works, but it's a poor substitute for a clean app-layer bind.
Why an app-layer fix is preferable
- Operator clarity: a one-line config change is easier to audit than a UFW rule that exists for reasons buried in another file.
- Drift resistance: UFW state can drift independently of OpenClaw config (e.g., a
ufw reset would silently remove the lockdown). A bound socket cannot drift.
- Container deployments: many container runtimes don't ship UFW; an operator using OpenClaw in a container has no equivalent network-layer knob.
- Documentation alignment: the OpenClaw msteams docs describe the webhook as serving Bot Framework Service traffic, which is always inbound from a public ingress. There's no design reason for the default to be a wildcard bind.
Proposed change
Add channels.msteams.webhook.host (string, optional, default "0.0.0.0" for backwards compat). Pass it through to the underlying HTTP server's listen call.
{
"channels": {
"msteams": {
"webhook": {
"host": "127.0.0.1",
"port": 3978,
"path": "/api/messages"
}
}
}
}
Optionally, change the default to 127.0.0.1 for new installs. The vast majority of OpenClaw msteams deployments will be running behind a tunnel or reverse proxy on the same host. Wildcard bind is rarely the right answer for a webhook endpoint.
Cross-channel consistency
Other OpenClaw channels with HTTP-server components (gateway WS on 18789, browser control on 18791, signal-cli HTTP daemons on 8080/8081) already bind to 127.0.0.1 by default. The msteams webhook is an outlier. A webhook.host knob — or just changing the default — would bring it in line.
Where this came up
Synap deployment, 2026-04-07. Closing the audit pass on our security model required guaranteeing the bot webhook is unreachable from the tailnet (not just from the public internet, since the tailnet is already a "trusted" but not "infinitely trusted" network in our threat model). We landed on the UFW workaround but tracked the app-layer bind as a follow-up to file upstream.
Feature request:
channels.msteams.webhook.hostto bind the bot webhook to loopback onlyEnvironment
2026.4.5 (3e72c03)0.0.0.0:3978(current default)Background
Our deployment terminates the Bot Framework webhook behind Cloudflare Tunnel (
cloudflared), which connects from the local machine to Cloudflare's edge over QUIC and proxies inbound HTTP requests back to127.0.0.1:3978. Becausecloudflaredalways uses loopback to reach the origin, there is no need for the msteams webhook to bind to any non-loopback interface.Our threat model wants the webhook reachable only via the cloudflared path, so any unintended exposure (e.g. from another tailnet device on the host's tailscale0 interface) is prevented at the network layer.
What's missing
channels.msteamshas nowebhook.host(or equivalent) config field. The only knobs we found are:{ "channels": { "msteams": { "webhook": { "port": 3978, "path": "/api/messages" } } } }portandpathare honoured. There is no documented way to set the listen address.Workaround we shipped
Defense-in-depth at the network layer with UFW:
This blocks tailnet-side access to
:3978while leaving loopback (cloudflared → 127.0.0.1) unaffected. It works, but it's a poor substitute for a clean app-layer bind.Why an app-layer fix is preferable
ufw resetwould silently remove the lockdown). A bound socket cannot drift.Proposed change
Add
channels.msteams.webhook.host(string, optional, default"0.0.0.0"for backwards compat). Pass it through to the underlying HTTP server'slistencall.{ "channels": { "msteams": { "webhook": { "host": "127.0.0.1", "port": 3978, "path": "/api/messages" } } } }Optionally, change the default to
127.0.0.1for new installs. The vast majority of OpenClaw msteams deployments will be running behind a tunnel or reverse proxy on the same host. Wildcard bind is rarely the right answer for a webhook endpoint.Cross-channel consistency
Other OpenClaw channels with HTTP-server components (gateway WS on 18789, browser control on 18791, signal-cli HTTP daemons on 8080/8081) already bind to
127.0.0.1by default. The msteams webhook is an outlier. Awebhook.hostknob — or just changing the default — would bring it in line.Where this came up
Synap deployment, 2026-04-07. Closing the audit pass on our security model required guaranteeing the bot webhook is unreachable from the tailnet (not just from the public internet, since the tailnet is already a "trusted" but not "infinitely trusted" network in our threat model). We landed on the UFW workaround but tracked the app-layer bind as a follow-up to file upstream.