Skip to content

Claude/init 1wgqn0 - #13

Merged
SuperInstance merged 2 commits into
masterfrom
claude/init-1wgqn0
Aug 18, 2026
Merged

Claude/init 1wgqn0#13
SuperInstance merged 2 commits into
masterfrom
claude/init-1wgqn0

Conversation

@SuperInstance

Copy link
Copy Markdown
Owner

Description

[What changed and why. Link to the issue if one exists.]

Testing Done

[How you verified the change works — test output, manual steps, screenshots]


  • Tests added/updated
  • Docs updated (if behavior changed)
  • No breaking changes (or changelog entry added)
  • CI/CD passes

Reviewer notes: [Optional — anything you want the reviewer to know upfront]

claude added 2 commits August 18, 2026 06:23
Both local servers refuse a request whose Host is not this machine. That is
the guard against DNS rebinding: comparing Origin against Host does not
replace it, because a page served from evil.example pointed at 127.0.0.1
sends both headers reading evil.example and they match perfectly.

The name was matched with `name.startswith("127.")`. 127.evil.example starts
with those characters, is registrable, and can be pointed anywhere -- so the
test meant to recognise the 127/8 block admitted the exact attack the guard
exists to stop. Parse the address instead of comparing the string.

The same lines read a bracketed IPv6 Host wrongly: the brackets separate the
address from the port, so stripping the port first turned `[::1]` -- what a
client sends when the port is the default -- into ":", and a loopback caller
was refused.

Both faults were in both servers, because the MCP copy was a copy of the web
one. Whether a name is loopback now lives only in runtime/localhost.py, and
test_localhost.py fails if a module starts deciding it again. bind_is_loopback
is separated from host_is_local in the same move: 0.0.0.0 is a fine thing to
be addressed as and a bad thing to be bound to, and the two callers wanted
opposite answers from what had been one list.

The refusal is pinned over a real socket on both servers, with and without an
Origin header -- the Host check has to run before Origin is read, since a
non-browser client omits Origin and an Origin-only guard then has nothing to
say. Every guard here fails the suite when removed, including the IPv4-mapped
normalisation, which is asserted directly because the interpreters this runs
on already fold it and could not observe its absence.

The sibling plainsong-mcp carries its own copy and needs the same fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
1.3.0 blamed the missing GitHub Releases on skip-existing and shipped a fix
for it. Reading the job logs rather than reasoning about them, all six release
runs died at the same step with the same error: invalid-publisher, no Trusted
Publisher configured on PyPI, environment MISSING. Every version up there had
been uploaded by hand. skip-existing is still worth having -- it is what makes
a re-run idempotent -- but it was never the blocker, and test and build passing
in all six runs is what made the failure easy to keep mis-reading.

docs/releasing.md is rewritten around that. It had been describing a project
that "has never been published", sending the reader to the pending-publisher
page -- which refuses a name that already exists rather than redirecting -- and
telling them to publish the sibling "the same way" when the sibling has no
release workflow at all. It now says which of PyPI's two pages applies when,
that the claims printed in the error are exactly what the publisher must match,
and that `environment: MISSING` means leave the field empty.

It also records a consequence that is not obvious: a re-run uses the workflow
file as it was at the tag. v1.0.1, v1.1.0 and v1.2.0 predate skip-existing, so
those three cannot be backfilled by re-running -- they authenticate and then
abort on files already on PyPI. From v1.3.0 on, a re-run completes.

1.4.0 rather than 1.3.1: plainsong.runtime.localhost is a new public module and
a Host header that was accepted before is refused now. No notation changes --
6,321 files compile to exactly the music they did in 1.0.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
@SuperInstance
SuperInstance merged commit 8471c32 into master Aug 18, 2026
16 checks passed
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.

2 participants