Skip to content

service: HTTP channels bind loopback only — no public exposure path #1136

Description

@edwin-zvs

Service HTTP listeners bind Ipv4Addr::LOCALHOST unconditionally (crates/daemon/src/service_supervisor.rs). There is no way to expose a service beyond the machine running the daemon, so an HTTP service can only be reached by something already on that host — or by a tunnel the operator sets up entirely outside Construct.

The daemon already has tunnel machinery (crates/daemon/src/tunnel/) for the owner surface. The service module does not reference it at all.

The Slack channel (#1127) sidesteps this by dialing out over Socket Mode, which is why a Slack service works today with no exposure story. That makes this less urgent than it was, but it leaves the generic HTTP channel — described in the design as "the compatibility floor: anything that can POST authenticated JSON can integrate before a dedicated channel exists" — reachable only from localhost, which is precisely when a compatibility floor is least useful.

The hard part is not binding a different address; it is that exposing a service publicly changes its threat model. Worth settling as its own decision record:

  • The design's Consequences require that the service route class never widen to the owner control surface, which keeps its interactive login boundary. A shared exposure path must preserve that separation.
  • Bearer auth is currently the only caller check. Public exposure likely wants request signing per channel kind (Slack already has its own scheme), and interacts with budgets (service: per-service budgets (rate, concurrency, tokens) are not enforced #1132) as the only defense against an open endpoint being hammered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions