Skip to content

feat(client)!: check the Host header on each listener, add --allowed-host - #603

Open
mark1russell7 wants to merge 3 commits into
mainfrom
fix/host-check-each-listener
Open

mark1russell7 wants to merge 3 commits into
mainfrom
fix/host-check-each-listener

Conversation

@mark1russell7

@mark1russell7 mark1russell7 commented Nov 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

The browser checks of the procedure server examine the Host header on each listener. Before, they examined it only on a loopback address. A server on a LAN address (0.0.0.0) accepted each host name.

A request must name one of these hosts in its Host header:

  • A loopback name: localhost, 127.0.0.1 or [::1].
  • An IP address, for example 192.168.1.20 or [fe80::1]. With Docker port maps and NAT, the socket address and the Host differ. Each IP address passes, so these setups do not change.
  • A name of browser.allowedHosts of the host policy, with any letter case. "*" turns the check off.

A request with another name or without a Host header gets FORBIDDEN (403) with the reason host-header-not-allowed. The message names allowedHosts and --allowed-host, so an operator finds the change.

  • The server command gets --allowed-host <names>. Repeat the option, or separate the names with commas. A name has no port and no scheme. * turns the check off. @procedures/server exports parseAllowedHosts.
  • mark --server gets the same option. A server on a LAN address without the option shows a hint.
  • Docs: the MCP face, WIRE-PROTOCOL.md, and the README files of the server and of mark.

Checks

  • browser-guard.test.ts has 4 new tests:
    • A name on a LAN listener (IPv4, IPv4 in IPv6, IPv6), and a request without Host.
    • The loopback names and the IP addresses on 5 listeners.
    • Names that look like IP addresses: 1.2.3.4.evil.example, 256.1.1.1, 0x7f.0.0.1, [evil.example].
    • allowedHosts with any letter case, on each listener.
  • With the old guard, the 4 tests fail.
  • Server: the parse and the errors of --allowed-host, and a started server with and without the option (200 and 403 by Host).
  • mark: the parse of the repeated option, the option after a command (exit 1), and the host options of the server mode.
  • Local checks:
    • vitest related for the guard: 615 passed.
    • The tests of the server config and start (16 passed), of mark args and server mode (35 passed), and of the MCP guard (20 passed).
    • The API report, the conventions check and ste-lint.

Version

MINOR of the client group. On a LAN listener, a request that names a host outside the list gets 403. The first line of the changeset names the break and the default hosts, and the changeset has the migration steps. Merge this change alone, after the release of the previous break. It has no deprecation release before it, because that release keeps the old behavior for one more cycle.

🤖 Generated with Claude Code

mark1russell7 and others added 3 commits November 15, 2026 11:17
…host

The browser checks examine the Host header on each listener, also on a LAN
address. A request names a loopback name, an IP address or a name of
browser.allowedHosts. The server command and mark --server get the option
--allowed-host. @procedures/server exports parseAllowedHosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first line of the changeset names the break and the default hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant