Skip to content

v1.1.2 — a WebSocket that trusted the wrong thing

Choose a tag to compare

@TomasLucasUTN TomasLucasUTN released this 26 Jul 18:37
· 114 commits to main since this release

godot-mcp-bridge 1.1.2 — a WebSocket that trusted the wrong thing

The bridge binds 127.0.0.1. That stops a remote host. It does not stop the
browser on the same machine, and it was never supposed to be relied on for that.

Any page you visited could drive your editor

A WebSocket handshake is not subject to the same-origin policy. Binding to
loopback keeps a remote attacker out, but a page open in your browser — any
page, while the editor happened to be running — could connect to
ws://127.0.0.1:6505 exactly as the addon does, and from there call any tool.
That includes writing files into the project and game_eval.

Browsers always send an Origin header on a WebSocket handshake and cannot be
told not to. Godot's WebSocketPeer never sends one. So the fix is one check:
refuse any handshake carrying Origin. Always on, no configuration, and it does
not touch the addon's own connection — Godot was never sending the header that
now gets it rejected.

This does not need active exploitation in the wild to be worth a same-day patch.
It needed one page with a script tag.

A shared secret, if you want a second lock

Origin rejection closes the browser vector completely, but not every local
process: something else running as you on the same machine can still open a
plain WebSocket and claim to be Godot. GODOT_MCP_SECRET closes that too — set
it on the server, set the same value in the editor (same env var, or the
godot_mcp/network/secret project setting), and the handshake is checked in
constant time. Unset on either side, and nothing changes: this is additive, not
a new requirement.

SECURITY.md lists what actually guards the bridge now, in order, instead of
just saying "runs on localhost."

Along the way: coverage found three real bugs

Extending the GDScript suite to 29 more mutating tools (52 of 99 covered → 81)
was meant to be mechanical. It wasn't:

  • remove_state_machine_transition never worked. It called find_transition(),
    which does not exist on AnimationNodeStateMachine. The call aborted the
    handler mid-edit and returned an empty dictionary — no ok, no error, no
    message, undo action left open. Fixed by locating the transition index
    directly instead of a method that was never there.
  • Painting a tilemap cell with no TileSet assigned reported plain success.
    The cell is stored; nothing can render it, because the source ID resolves
    against a TileSet that doesn't exist yet. Refusing would break the legitimate
    case of assigning the TileSet afterwards, so both tilemap_set_cell and
    tilemap_fill_rect now succeed and say so in a warning field.
  • Tilemap coordinates silently dropped [x, y] arrays and painted at (0, 0).
    Every other coordinate-taking tool here — add_node's position,
    setup_collision's size — accepts an array, so trying one here was a
    reasonable guess that used to fail quietly. Arrays are now accepted alongside
    {x, y}.

None of these three would have been caught by hand. That's the whole argument
for writing the coverage in the first place.

A live activity feed instead of a poll

get_editor_activity only tells you something if you ask, and the digest
riding along on tool responses only arrives when a tool happens to be called —
between calls is exactly when the developer is doing something worth knowing
about. godot-mcp://editor/activity is now a subscribable MCP resource:
subscribe once and the server pushes notifications/resources/updated when the
developer touches something (the agent's own edits are filtered out, and a
burst — dragging a node — coalesces into one notification instead of a dozen).
Nothing is polled while nobody is subscribed.

Also

  • CI now runs the GDScript and live-editor suites against two Godot
    versions
    — the advertised minimum (4.5) and the latest stable (4.7) — instead
    of one. Both engine breaks this project has had were found by hand, late; a
    version matrix turns the next one into a red build instead.
  • The MCP registry entry now publishes itself on every GitHub release, via
    GitHub OIDC (no secret to leak). It had been sitting at 1.0.0 through two
    releases because nobody re-ran the manual step.

Upgrade notes

Same tool surface as 1.1.1 — nothing removed, no schemas changed. The security
fix lives in the npm package (the bridge), not the addon, so update the npm
package
(npm install -g godot-mcp-bridge@latest, or just let npx pick up
the new version next run) — a reinstalled addon alone does not carry this fix.

If 1.1.1 or earlier is what's actually running today, updating is the same
priority as any other same-day security patch: do it before the next session
where the editor is open in the background while you're also browsing.