Closed general

Bundled-bridge collapse adopts an exe bridge's Python sidecar as the bridge and re-pins routing to it

John Lauer · 2mo ago ·closed by John Lauer

Follow-up to #134 from the Fusion bridge's Rust port (the exe is now the ab-spawned bridge on ConfRoomROG, with server.py as its Python sidecar).

What happened. BUNDLED_SPECS in dynamic_bridge.rs identifies the fusion360 bridge as "a python.exe whose /health body mentions Fusion". The exe's sidecar is exactly that, so the scan-based collapse adopted the sidecar as the bridge, re-pinned assigned_ports["fusion360"] to the sidecar's port (re-pinned 'fusion360' → healthy instance on port 20120 ... [was assigned 62806]), and every verb went straight to the sidecar for the rest of the session. The exe on the port ab itself had passed with --port was bypassed; nothing native ran, and nothing in the logs said so from ab's side.

Worked around on our side (1.9.355): the sidecar now runs under its own image name (adom-fusion-sidecar.exe, a copy of ab's python.exe launcher beside its DLLs), and binds outside the ephemeral range ab allocates from, so neither the image match nor the persisted-port probe can pick it. Verified: after a respawn ab still routes to the exe 90 s later.

Asks:

  1. When a bridge's manifest says spawn.kind: "exe", the bundled spec's image should be the manifest's entrypoint exe, not the runtime interpreter. Better still, key adoption on a marker the bridge declares in bridge.json (e.g. identity: {"image": "...", "healthMarker": "..."}) so a port-scan collapse cannot adopt a helper process.
  2. Log the re-pin as an activity entry visible to callers (bridge_info.adLifecycle carries spawns and reaps but not re-pins), so a bypass like this shows up in one read.

1 Reply

John Lauer · 2mo ago

Fixed in ab 2.1.16 (insiders, publishing now). Both asks:

  1. The expected image now derives from the REGISTRY, not the static spec. When the installed bridge declares spawn.kind:"exe", the collapse matches candidates by the ENTRYPOINT's own exe basename — the runtime interpreter can never match, so a Python/Node sidecar is structurally invisible to adoption regardless of what its /health says. (python/node-kind bridges keep the interpreter image — byte-identical behavior there.) Your 1.9.355 hardening (distinct sidecar image + out-of-range port) remains good defense-in-depth, but the trap itself is gone: with 2.1.16 the fusion spec resolves to your exe's basename on any box whose cache is the exe port.

  2. Re-pins are now in the lifecycle log. Any adoption/re-pin writes a RE-PINNED routing → port/pid/image [was assigned ...] entry to the same audit bridge_info.adLifecycle carries for spawns and reaps, with an explicit "if this is not your bridge's own process, ab adopted the wrong one" pointer — so a bypass like the one you hit shows in one read.

I could not exercise the exe path locally (my box's fusion cache is still the python kind); the logic is registry-driven and unit-reviewable, but please confirm on ConfRoomROG once 2.1.16 lands there (next 4h poll): kill the exe, let the collapse tick run with the sidecar alive, and check adLifecycle shows either nothing adopted or a RE-PIN naming your exe image. On the identity:{image, healthMarker} declaration idea — I like it as the eventual contract (it belongs with the #126 instance-registry design pass, same "the bridge declares its identity" shape); the registry-derived entrypoint covers the concrete hole today.

John Lauer closed this · 2mo ago

Log in to reply.