Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Inbox refuses all reverse-proxy requests: x-forwarded-for treated as tailnet exposure (breaks local proxies)

Open Beginner friendly
#1,867 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, typescript
Domain
backend, security

Research direction

Start by locating the inbox request handler that defines SERVE_HEADERS and calls localOnly(), then read how the Host-header guard and tailnetPublished state are used. Reproduce the reported 403 with an x-forwarded-for header on a loopback request and compare it with a request without that header. Done when local proxy requests work while the tailnet protection still behaves as intended.

Written by the indexing model from the issue text.

Description

What happened

Since the 0.28.9+ security hardening ("hostname check on every server" / tailnet detection), the Inbox's localOnly check treats any request carrying an x-forwarded-for header as tailnet exposure and refuses it:

var SERVE_HEADERS = ["tailscale-headers-info", "tailscale-user-login", "x-forwarded-for"];
// ...
if (SERVE_HEADERS.some((name) => req.headers.has(name))) {
  tailscale.noteServedRequest();
  return Promise.resolve(localOnly());
}

Response: {"error":"This answers only on this computer, not over the tailnet.","code":"local_only"} (403).

Why this is a problem

Every reverse proxy adds x-forwarded-for — it's standard proxy behavior, not a Tailscale signature. A purely local proxy chain (loopback → local HTTPS front-end → loopback inbox) gets 403'd even though the request never left the machine. This breaks:

  • local HTTPS front-ends with named domains (e.g. portless .localhost aliases — my case: inbox.localhost → 127.0.0.1:<port>)
  • nginx/caddy local setups
  • any dev server proxying to the inbox

Verified by sending x-forwarded-for: 1.2.3.4 on an otherwise plain loopback request — 403 local_only; without the header, 200.

Suggested fix

The two tailscale-* headers are genuinely Tailscale-serve-specific and are the right signal. x-forwarded-for alone is not — it only becomes meaningful when tailscale serve is the thing adding it. Options:

  1. Drop x-forwarded-for from SERVE_HEADERS (the Host-header guard already protects loopback-only endpoints).
  2. Only treat x-forwarded-for as tailnet-exposure when tailscale is actually publishing (the inbox already tracks tailnetPublished state).
  3. Check that x-forwarded-for's value is a non-loopback address (tailscale serve forwards the real client IP; a local proxy forwards 127.0.0.1).

Workaround in the meantime

A tiny local proxy that strips x-forwarded-for (and the other forwarded headers) in front of the inbox.

Versions: plannotator 0.28.16 (darwin-arm64), inbox started via plannotator inbox --background.

Dominant language
TypeScript
Stars
8.7k
Forks
649
Avg merge
5h 5m
Merged PRs (30d)
325

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from backnotprop/plannotator

All issues in backnotprop/plannotator

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.