Skip to content

Add channels.msteams.webhook.host to bind the bot webhook to loopback only #62766

@tmote

Description

@tmote

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

  1. Operator clarity: a one-line config change is easier to audit than a UFW rule that exists for reasons buried in another file.
  2. 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.
  3. Container deployments: many container runtimes don't ship UFW; an operator using OpenClaw in a container has no equivalent network-layer knob.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions