fix: ship the embedded tunnel in Linux builds only (closes #890) - #896
Merged
Conversation
mxcli embeds chisel to reverse-tunnel a locally-running app out to a tunnel-hub preview URL. Chisel is a dual-use tool that appears in threat intelligence as a post-exploitation pivoting component, and every platform linked it even though the tunnel only ever runs inside a Linux container. Windows Defender flags the Windows binary as Trojan:Script/Sabsik.EN.A!ml and enterprise EDR flags this class of payload harder still, which blocks mxcli on the managed corporate endpoints most Mendix developers use. Both chisel imports now sit behind a one-interface, Linux-only seam: tunnelConn / startTunnel cmd/mxcli/docker (chisel client) controlServer/ newControlServer cmd/mxcli/tunnelhub (chisel server) each with a _linux.go implementation and a !linux stub. The hub seam is at Start rather than construction so the portable front - registry, API, auth, Host routing - stays testable on every platform. Both commands remain registered and documented everywhere and fail with an actionable message naming the Linux-container constraint and a docs link. Windows and macOS binaries lose 32 packages, including the whole x/crypto/ssh stack, gorilla/websocket and armon/go-socks5: -13.5 MB (-14.67%). Linux is unchanged in behaviour and grows 8 KB. scripts/check-tunnel-deps.sh (CI + make check-tunnel-deps) fails the build if chisel or its tunnelling-specific dependencies reappear in a windows/darwin dependency graph. It matches the module list rather than the string "chisel", so transitive re-entry via x/crypto/ssh also trips it, and it asserts a positive control first so it cannot pass vacuously. Verified by re-adding the import and watching it fail on all four targets. A new CI matrix job runs the !linux tests on real Windows and macOS runners. Note this is a different problem from #185 (Wacatac.C!ml), which was a genuine generic Go-binary false positive. Here the capability really was in the binary, so code signing would not address it. Nothing was obfuscated, packed or renamed to evade detection - the fix is to stop shipping the capability where it is unused. Rationale recorded in ADR-0009. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new windows/macOS job ran the full docker + tunnelhub packages, which fails on Windows for reasons that predate this branch: three tests assert POSIX file modes (0600) that Windows does not implement (os.Chmod only toggles the read-only bit, so Stat reports 666), one asserts a world-readable file is refused, and one assumes Unix path separators. macOS passes all of them. Filed as #897. Scope the job with -run to the seam's own !linux tests. The full test binaries are still compiled for each platform, so a Windows/macOS build break is still caught; only the stub behaviour is executed. Assert that at least 4 seam tests actually ran, since -run passes vacuously when its pattern matches nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #890.
Restricts the embedded tunnel (chisel) to Linux builds, so the Windows and macOS release binaries no longer contain a dual-use tunnelling tool they can never use.
Why
Chisel tunnels SSH over WebSocket and appears in threat intelligence as a post-exploitation pivoting component. Every platform linked it even though the tunnel only ever runs inside a Linux container, so Defender flags the Windows binary as
Trojan:Script/Sabsik.EN.A!mland enterprise EDR flags this class of payload harder still — blocking mxcli on the managed corporate endpoints most Mendix developers work on.Not the same problem as #185 (
Wacatac.C!ml), which was a genuine generic Go-binary false positive. Here the capability really was in the binary, so code signing would not have addressed it — a signed binary containing chisel is still flagged behaviourally. Nothing was obfuscated, packed or renamed to evade detection; the fix is to stop shipping the capability where it is unused. Rationale is recorded in ADR-0009.The seam
Two chisel imports, one interface each,
_linux.goimplementation +!linuxstub:tunnelConn/startTunnelcmd/mxcli/dockertunnel_linux.gotunnel_other.gocontrolServer/newControlServercmd/mxcli/tunnelhubcontrol_linux.gocontrol_other.goThe hub seam is at
Start, not construction, on purpose — a Server that cannot bind a control server is still a Server whose routing can be exercised, so the portable front (registry, API, auth, Host routing) stays testable on every platform rather than only where the tunnel ships.Both commands stay registered and documented everywhere and fail with an actionable message:
Failure is raised during flag validation, before booting an app or touching cert caches / key stores.
Proof
Dependency graph — 32 packages leave windows/darwin, including the whole
x/crypto/sshstack,gorilla/websocket,armon/go-socks5,x/net/proxyand thejpillora/*support libraries:Binary inspection (windows/amd64, release flags):
go version -mlists no chisel/websocket/socks5/ssh module;stringsfinds 0 chisel literals (was 432). Linux still carries the module and 450 literals.go tool nmcannot prove this and was deliberately not used as the criterion: releaseldflagsare-s -w, which strips the symbol table, so nm reportsno symbolswhether or not chisel is linked. Verified — an unstripped Windows build has 405 chisel symbols while the stripped release-equivalent had none before this change. Used alone it is a false pass.Size (release flags, amd64, embed dir held identical on both sides):
(
cmd/mxcli/skills/is gitignored and regenerated bymake sync-skills; a naive before/after straddling a sync misattributes ~240 KB of doc churn to the code change.)Linux still works —
TestTunnelRoundTrip(real chisel client + server, end to end) andTestFront_ProxiesThroughTunnel(hub front proxying through a live tunnel) both pass.Regression guard
scripts/check-tunnel-deps.sh, wired intopush-test.ymlandmake check-tunnel-deps:chisel, so transitive re-entry viax/crypto/sshalso trips it.go listor a typo'd pattern cannot make every platform look clean.A new CI matrix job (
tunnel-seam-cross-platform, windows-latest + macos-latest) executes the!linuxtests for real — neither package needs the generated ANTLR parser, so it is fast.Breaking changes (narrow)
mxcli tunnel-hubcan no longer be hosted on Windows or macOS. Move the hub to a Linux host. Judged near-zero impact (it is a public-facing daemon) but it is a genuine capability removal, not just repackaging.--hub— run mxcli inside the devcontainer, which is where the warm loop already runs.tunnelhub.ServerOptions.ChiselAddr→ControlAddr(it now addresses a platform-agnostic control server). This rename also removed the last four chisel-derived string literals from the non-Linux binaries; that is naming accuracy, not concealment —control_linux.go,tunnel_linux.goandgo.modname chisel plainly, and it is only acceptable because the code is genuinely gone. Called out explicitly in ADR-0009.Validation
make build,make test(full suite),make lint(Go + TypeScript) — all pass.go build+go vet ./...clean for linux, windows and darwin (vet type-checks every test file).!linuxunit tests compile for both targets here but were not executed locally — this container has no Windows/macOS runner. That is exactly the gap the new CI matrix job closes; it will be the first real execution..mpris read or written differently.Docs
ADR-0009 (rationale + rejected alternatives, incl. why signing and shell-out don't work), a load-bearing rule in CLAUDE.md, a symptom row in
.claude/skills/fix-issue.md, CHANGELOG, README,docs-site/src/tools/run-local.md,docs-site/src/tutorial/claude-code-web.md, and the two syncedmendix/skills.🤖 Generated with Claude Code