feat(http): a trusted caller on the tailnet #415

Merged
coilyco-ops merged 1 commit from feat/a-trusted-caller-on-the-tailnet into main 2026-08-13 12:29:33 +00:00
Member

closes #414

Identity half of #165, approved by Kai on 2026-08-12. 165 stays open for sessions and the principal assertion.

Safe by default

SIRENS_ECHO_HTTP_TOKEN supplies the token. Unset trusts nobody — exactly what this endpoint did before the token existed — so landing this changes nothing until a deployment decides otherwise.

Constant-time comparison: a check that leaks its own answer through timing looks like a control and is not one.

The trap it avoids, which is the reason this PR exists at all

X-Sirens-Caller already exists, is self-asserted, is used as a rate-limit key, and already flows into the turn's requester. It is a header named like an identity carrying a caller-supplied name.

The smallest possible version of "add identity" is to trust it. That step treats an unauthenticated string as a principal, and it would pass every test anyone would think to write, because the plumbing works and the value arrives where expected.

So the trusted input is a different input. A test asserts the self-asserted header grants no trust and still partitions the rate limit, so neither property can drift.

Why identity had to come before sessions

A session is a handle to retained conversation. An endpoint accepting a session id from an unauthenticated caller and returning that history discloses conversations to whoever guesses an id. history_count: 0 is not merely a missing feature — it is why this endpoint is currently safe to expose without authentication.

I was wrong twice on the way here, both recorded on 165

I declined this as "authentication on a network-facing endpoint" without checking what the endpoint faces. Echo is exposed through ingress-tailscale; /v1/turn has never been on the public internet. That does not make auth unimportant, it makes the threat model which tailnet peer — a bounded problem, and a different one from the one I talked myself out of.

What trust does today

Nothing beyond a span attribute. That is deliberate: the value is that the distinction exists and is recorded, and every consumer of it is a separate decision. The prompt still asserts an HTTP caller is not the principal — making that conditional means rebuilding the system prompt per request, which is a cost and a design question rather than a line of code.

ward exec gate green: build, policy-check, vet, test, test-skips, pre-commit.

closes #414 Identity half of https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/165, approved by Kai on 2026-08-12. **165 stays open** for sessions and the principal assertion. ## Safe by default `SIRENS_ECHO_HTTP_TOKEN` supplies the token. **Unset trusts nobody** — exactly what this endpoint did before the token existed — so landing this changes nothing until a deployment decides otherwise. Constant-time comparison: a check that leaks its own answer through timing looks like a control and is not one. ## The trap it avoids, which is the reason this PR exists at all `X-Sirens-Caller` already exists, is self-asserted, is used as a rate-limit key, and **already flows into the turn's requester**. It is a header named like an identity carrying a caller-supplied name. The smallest possible version of "add identity" is to trust it. That step treats an unauthenticated string as a principal, and it would pass every test anyone would think to write, because the plumbing works and the value arrives where expected. So the trusted input is a *different* input. A test asserts the self-asserted header grants no trust **and** still partitions the rate limit, so neither property can drift. ## Why identity had to come before sessions A session is a handle to retained conversation. An endpoint accepting a session id from an unauthenticated caller and returning that history discloses conversations to whoever guesses an id. `history_count: 0` is not merely a missing feature — it is why this endpoint is currently safe to expose without authentication. ## I was wrong twice on the way here, both recorded on 165 I declined this as "authentication on a network-facing endpoint" **without checking what the endpoint faces.** Echo is exposed through `ingress-tailscale`; `/v1/turn` has never been on the public internet. That does not make auth unimportant, it makes the threat model *which tailnet peer* — a bounded problem, and a different one from the one I talked myself out of. ## What trust does today Nothing beyond a span attribute. That is deliberate: the value is that the distinction exists and is recorded, and every consumer of it is a separate decision. The prompt still asserts an HTTP caller is not the principal — making that conditional means rebuilding the system prompt per request, which is a cost and a design question rather than a line of code. `ward exec gate` green: build, policy-check, vet, test, test-skips, pre-commit.
feat(http): a trusted caller on the tailnet
All checks were successful
ci / test (pull_request) Successful in 29s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
ci / image-build (pull_request) Successful in 18s
bc9c1f29ea
/v1/turn could not tell one caller from another. This authenticates one, and
does nothing else with the answer yet.

Safe by default: an unset token trusts nobody, which is exactly what this
endpoint did before the token existed. Landing it changes nothing until a
deployment decides otherwise.

The trusted input is deliberately not X-Sirens-Caller. That header exists, is
self-asserted, and is a rate-limit key, and it is the trap in this feature: it
is named like an identity, carries a caller-supplied name, and already flows
into the turn's requester. The smallest version of adding identity is to trust
it, and that step treats an unauthenticated string as a principal while passing
every test anyone would think to write.

Constant time comparison, because a check that leaks its answer through timing
looks like a control and is not one.

Sessions are not here and cannot come first. A session id accepted from an
unauthenticated caller discloses conversations to whoever guesses one, so
history_count being zero is the reason this endpoint is currently safe rather
than merely a missing feature.

Nothing reads the trust flag but telemetry. The prompt still asserts an HTTP
caller is not the principal, and making that conditional means rebuilding the
system prompt per request, which is its own issue.

closes #414

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
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-gaming/sirens-echo!415
No description provided.