Echo (30120) and Deep (30121) serve turns to unauthenticated callers by design, and the network boundary in front of them is unverified #925

Open
opened 2026-08-25 07:44:40 +00:00 by coilyco-ops · 0 comments
Owner

Rewritten 2026-08-25. The original framing of this issue was wrong, and the correction matters because it changes the fix from a code change to a network question. Kai asked whether there was already an SSM key for this. There is, and reading the current code rather than #866's earlier summary shows the design is deliberate.

What I got wrong

I filed this saying the token "compares correctly and then writes the result to a span attribute" with "no 401, no 403" — framing it as a broken authorization control.

That describes the code accurately and reads its intent backwards.

What the code actually does

Verified against coilyco-gaming/sirens-echo at 5b3ec52.

There are two surfaces and the token gates one of them on purpose:

  • turn - handleMCPTurn, registered in mcpserver.go:66 with no trust gate. Deliberately open, because every turn passes the deployment's policy, its guardfile, and its response validation.
  • Re-exported raw tools (server__tool) - gated at mcpreexport.go:135, which returns toolFailure(reexportUntrusted) on a failed check. A real early return.

The server's own instructions state the reasoning:

Those reach a server directly and do not pass the response checks turn applies, so they require the deployment token

So the token's job is to gate the path that bypasses policy. turn does not need it because turn cannot bypass anything.

The trusted bool on the turn path is telemetry, and the code says so at http.go:359: "trusted records whether the caller authenticated. Only telemetry reads". That is a labelled observation, not a failed control.

Why the token is unset, and why that is consistent

/sirens-echo/http-token     exists in SSM
/sirens-deep/http-token     exists in SSM
SIRENS_ECHO_HTTP_TOKEN      not set on either running pod
SIRENS_ECHO_MCP_REEXPORT    unset, so re-export is off on both lanes

With re-export off, only turn is served over /mcp, and turn is not token-gated. So the token currently has nothing to gate, and leaving it unwired is coherent rather than an oversight.

callerTrusted returns false for an empty configured token by design: "An empty configured token trusts nobody, which is the default."

This becomes a live gap the moment MCPReexport is turned on without wiring SIRENS_ECHO_HTTP_TOKEN first. That combination would expose raw rostered tools to anonymous callers. Worth a guard that refuses to start with re-export enabled and no token, rather than relying on whoever flips the flag to remember.

What is actually still open

Not an auth bug. A network question, and it is the one #866 could never answer either.

An unauthenticated caller who can reach these ports runs turns as Sirens Echo or Sirens Deep. Through policy, so no raw tool access, but a turn still:

  • spends inference on every call
  • can post into the Sirens guild, since both lanes hold Discord grants
  • is bounded only by an admission limiter keyed on X-Sirens-Caller, a header the caller supplies

Established: both ports are open over the tailnet and serve turn without authentication.

Not established: whether they are reachable from the public internet. #103 found port 22 publicly reachable and closed with "this was b/c of DMZ config" and no recorded remediation. A DMZ host takes every inbound port. k3s installs its own iptables chains for NodePort traffic that commonly bypass ufw INPUT filtering, so an active ufw is not an answer.

On the tailnet this design is reasonable. Off it, "anyone can spend your inference and post as your bot" is a different proposition, and nothing on record says which one is true.

Done means

  1. The network question is answered from the router admin rather than from a doc, and tested from off-network rather than read from a rule.
  2. If the endpoint is publicly reachable, either close it or gate turn as well, which is a coilyco-gaming/sirens-echo change rather than an infrastructure one.
  3. Separately and cheaply: a startup guard rejecting MCPReexport enabled with an empty HTTPTrustToken.

#157 (public-port allowlist in the repo, audited externally), #110 (treat LAN as semi-hostile), #103 (the unanswered DMZ precedent), and #866, closed when Dowel's port 30122 went away.

**Rewritten 2026-08-25.** The original framing of this issue was wrong, and the correction matters because it changes the fix from a code change to a network question. Kai asked whether there was already an SSM key for this. There is, and reading the current code rather than #866's earlier summary shows the design is deliberate. ## What I got wrong I filed this saying the token "compares correctly and then writes the result to a span attribute" with "no `401`, no `403`" — framing it as a broken authorization control. That describes the code accurately and reads its intent backwards. ## What the code actually does Verified against `coilyco-gaming/sirens-echo` at `5b3ec52`. There are **two** surfaces and the token gates one of them on purpose: * **`turn`** - `handleMCPTurn`, registered in `mcpserver.go:66` with no trust gate. Deliberately open, because every turn passes the deployment's policy, its guardfile, and its response validation. * **Re-exported raw tools** (`server__tool`) - gated at `mcpreexport.go:135`, which returns `toolFailure(reexportUntrusted)` on a failed check. A real early return. The server's own instructions state the reasoning: > Those reach a server directly and do not pass the response checks turn applies, so they require the deployment token So the token's job is to gate the path that **bypasses** policy. `turn` does not need it because `turn` cannot bypass anything. The `trusted` bool on the turn path is telemetry, and the code says so at `http.go:359`: "trusted records whether the caller authenticated. Only telemetry reads". That is a labelled observation, not a failed control. ## Why the token is unset, and why that is consistent ``` /sirens-echo/http-token exists in SSM /sirens-deep/http-token exists in SSM SIRENS_ECHO_HTTP_TOKEN not set on either running pod SIRENS_ECHO_MCP_REEXPORT unset, so re-export is off on both lanes ``` With re-export off, only `turn` is served over `/mcp`, and `turn` is not token-gated. So the token currently has nothing to gate, and leaving it unwired is coherent rather than an oversight. `callerTrusted` returns `false` for an empty configured token by design: "An empty configured token trusts nobody, which is the default." **This becomes a live gap the moment `MCPReexport` is turned on without wiring `SIRENS_ECHO_HTTP_TOKEN` first.** That combination would expose raw rostered tools to anonymous callers. Worth a guard that refuses to start with re-export enabled and no token, rather than relying on whoever flips the flag to remember. ## What is actually still open Not an auth bug. A network question, and it is the one #866 could never answer either. An unauthenticated caller who can reach these ports runs turns as Sirens Echo or Sirens Deep. Through policy, so no raw tool access, but a turn still: * spends inference on every call * can post into the Sirens guild, since both lanes hold Discord grants * is bounded only by an admission limiter keyed on `X-Sirens-Caller`, a header the caller supplies **Established:** both ports are open over the tailnet and serve `turn` without authentication. **Not established:** whether they are reachable from the public internet. `#103` found port 22 publicly reachable and closed with "this was b/c of DMZ config" and no recorded remediation. A DMZ host takes every inbound port. k3s installs its own iptables chains for NodePort traffic that commonly bypass `ufw` INPUT filtering, so an active `ufw` is not an answer. On the tailnet this design is reasonable. Off it, "anyone can spend your inference and post as your bot" is a different proposition, and nothing on record says which one is true. ## Done means 1. The network question is answered from the router admin rather than from a doc, and tested from off-network rather than read from a rule. 2. If the endpoint is publicly reachable, either close it or gate `turn` as well, which is a `coilyco-gaming/sirens-echo` change rather than an infrastructure one. 3. Separately and cheaply: a startup guard rejecting `MCPReexport` enabled with an empty `HTTPTrustToken`. ## Related `#157` (public-port allowlist in the repo, audited externally), `#110` (treat LAN as semi-hostile), `#103` (the unanswered DMZ precedent), and `#866`, closed when Dowel's port 30122 went away.
coilyco-ops changed title from The unauthenticated turn endpoint #866 found on Dowel is still live on Echo (30120) and Deep (30121) to Echo (30120) and Deep (30121) serve turns to unauthenticated callers by design, and the network boundary in front of them is unverified 2026-08-25 08:01:40 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/infrastructure#925
No description provided.