-
Notifications
You must be signed in to change notification settings - Fork 0
Pre‐Release Manual Testing
A pre-release checklist for validating an eunox-mcp release candidate on macOS,
Windows, and Linux. Enforcement behavior is identical across platforms — what
differs is the delivery surface (install channel, archive format, signature/checksum
verification, first-launch OS guard, and file paths), so the shared steps are written
once and per-OS variants are called out inline.
.goreleaser.yml builds every OS for both amd64 and arm64 — test each arch on
matching hardware if you ship both.
| macOS | Windows | Linux | |
|---|---|---|---|
| Install channel | Homebrew cask (stable tags only) | winget (stable tags only) |
.deb / .rpm / tarball |
| Archive | .tar.gz |
.zip (eunox-mcp.exe) |
.tar.gz |
| Checksum tool | shasum -a 256 |
Get-FileHash |
sha256sum -c (native) |
| First-launch guard | Gatekeeper quarantine | Mark-of-the-Web / SmartScreen | none |
Audit dir (~/.eunox) |
~/.eunox |
%USERPROFILE%\.eunox |
~/.eunox |
| Key-mode check |
stat -f '%Sp' → -rw-------
|
icacls (ACL, not POSIX) |
stat -c '%a' → 600
|
The released binaries for macOS and Windows are unsigned (per
.goreleaser.yml), so users hit Gatekeeper / SmartScreen on first launch; Linux has no equivalent. The first-launch step below documents the unblock for each.
# macOS (Homebrew):
brew install go jq cosign gh# Windows (winget); commands below are PowerShell:
winget install --id sigstore.cosign; winget install --id GitHub.cli; winget install --id jqlang.jq# Linux (apt/dnf); install cosign + gh per their upstream instructions:
sudo apt-get install -y jq # or: sudo dnf install -y jqgo (>= 1.25) is only needed for the source build in Step 1; node is for the npx
filesystem-server smoke test in Step 5.
# macOS / Linux:
cd eunox
make check-fmt && make check-license && make check-notice
make test # go test -race -count=1 ./...
make build # -> bin/eunox-mcp
./bin/eunox-mcp version # a source build prints "dev" -- expected here# Windows (no make; use the raw go commands):
cd eunox
go test -race -count=1 ./...
go build -o bin\eunox-mcp.exe .\cmd\eunox-mcp
.\bin\eunox-mcp.exe version # prints "dev" -- expected for a local buildThe
devstring is correct only for a local build. On the released binary the version must be the real tag.
Download the candidate's artifacts + signature chain (replace <tag>, e.g. v0.1.0-rc1).
Swap the OS pattern as needed (darwin / windows / linux):
mkdir -p ~/eunox-rel && cd ~/eunox-rel # PowerShell: mkdir ~\eunox-rel; cd ~\eunox-rel
gh release download <tag> --repo eunolabs/eunox \
--pattern 'eunox-mcp_*_<os>_*' \
--pattern 'checksums.txt' --pattern 'checksums.txt.pem' --pattern 'checksums.txt.sig' \
--pattern '*<os>*.sbom.json'
# Linux native packages, additionally:
# --pattern 'eunox-mcp*.deb' --pattern 'eunox-mcp*.rpm'2a. Verify the Sigstore signature on checksums.txt (same on every OS; PowerShell
uses backtick line-continuations instead of \):
cosign verify-blob \
--certificate-identity-regexp "^https://github\.com/eunolabs/eunox/\.github/workflows/release\.yml@refs/tags/" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
checksums.txt
# expect: "Verified OK"2b. Verify the downloaded artifact against the proven checksum file — the one
command that differs per OS (hashes in checksums.txt are lowercase):
# Linux (the form documented in SECURITY.md):
sha256sum -c checksums.txt --ignore-missing # every downloaded file prints ": OK"# macOS (no sha256sum; compare manually):
shasum -a 256 eunox-mcp_*_darwin_arm64.tar.gz
grep darwin_arm64 checksums.txt # the two hashes must match# Windows (no sha256sum):
(Get-FileHash .\eunox-mcp_*_windows_amd64.zip -Algorithm SHA256).Hash.ToLower()
Select-String -Path checksums.txt -Pattern 'windows_amd64' # the two hashes must match2c. Unpack, clear the first-launch guard, and confirm the real version — the other per-OS step:
# macOS -- Gatekeeper quarantine. Reproduce a real download, then clear it:
tar xzf eunox-mcp_*_darwin_arm64.tar.gz
xattr -w com.apple.quarantine "0081;00000000;Safari;" ./eunox-mcp
./eunox-mcp version # EXPECT: blocked ("cannot be opened")
xattr -d com.apple.quarantine ./eunox-mcp
./eunox-mcp version # EXPECT: the real <tag>, not "dev"# Windows -- Mark-of-the-Web / Defender SmartScreen:
Expand-Archive .\eunox-mcp_*_windows_amd64.zip -DestinationPath .\win
Get-Item .\win\eunox-mcp.exe -Stream Zone.Identifier # shows MotW on downloaded zips
.\win\eunox-mcp.exe version # EXPECT: SmartScreen may warn / block
Unblock-File .\win\eunox-mcp.exe
.\win\eunox-mcp.exe version # EXPECT: the real <tag>, not "dev"# Linux -- no quarantine; just confirm the executable bit survived and it runs:
tar xzf eunox-mcp_*_linux_amd64.tar.gz
./eunox-mcp version # EXPECT: the real <tag>, not "dev"Record the exact wording of any Gatekeeper / SmartScreen dialog — it's what users hit
first, and what release notes (and the Homebrew caveats) should pre-empt.
2d. SBOM present & parseable (grype sbom:<file> / trivy sbom <file> optional):
jq -e '.spdxVersion' eunox-mcp_*_<os>_*.sbom.json >/dev/null && echo "SBOM ok"
# PowerShell: Get-Content .\eunox-mcp_*_windows_*.sbom.json | ConvertFrom-Json | Select spdxVersionCasks and winget publish only on stable tags (skip_upload: auto skips rc/pre-release).
# macOS:
brew install eunolabs/tap/eunox-mcp
which eunox-mcp # /opt/homebrew/bin (arm64) or /usr/local/bin (Intel)
eunox-mcp version # real tag; note whether the cask clears the quarantine# Windows (open a fresh shell afterward so PATH refreshes):
winget install eunolabs.eunox-mcp
eunox-mcp version # real tag; note whether install clears SmartScreen# Linux -- confirm binary at /usr/bin and docs at /usr/share/doc/eunox-mcp/:
sudo dpkg -i eunox-mcp_*_linux_amd64.deb # or: sudo rpm -i eunox-mcp-*_linux_x86_64.rpm
which eunox-mcp; ls /usr/share/doc/eunox-mcp/ # LICENSE + README.mdSubcommands: proxy validate init suggest kill audit-verify stats doctor version.
eunox-mcp --help
eunox-mcp doctor # redacted support bundle; nothing uploaded
eunox-mcp validate demo/manifest.yaml # from a checkout; expect "OK" (Windows: demo\manifest.yaml)doctor prints os/arch: <goos>/<goarch> — confirm it matches the build you installed,
and that it redacts secrets.
Point an MCP host at the proxy. Host-config locations differ:
-
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json -
Windows:
%APPDATA%\Claude\claude_desktop_config.json(escape backslashes in JSON paths) -
Linux: Claude Desktop isn't available; use a Linux MCP host (VS Code + Copilot agent
mode, Cursor, Cline, Roo) — same
command/argsshape.
{
"mcpServers": {
"fs-wiretap": {
"command": "eunox-mcp",
"args": ["proxy", "--audit", "--",
"npx", "-y", "@modelcontextprotocol/server-filesystem", "<path>"]
}
}
}Drive some tool calls, then inspect the tape and the audit dir:
eunox-mcp stats # per-tool allow/deny histogram
# macOS / Linux:
ls -l ~/.eunox/
stat -f '%Sp' ~/.eunox/audit.key # macOS -> -rw-------
stat -c '%a' ~/.eunox/audit.key # Linux -> 600# Windows: ~ resolves to %USERPROFILE%
Get-ChildItem $env:USERPROFILE\.eunox
icacls $env:USERPROFILE\.eunox\audit.key # 0600 is POSIX-only; confirm via ACL insteadConfirm: wiretap forwards everything, tools/call records carry full args, and
.../list calls are not recorded. (No host handy? Drive it over HTTP via Step 7 or
the demo scripts in Step 10 — no host required.)
eunox-mcp validate demo/manifest.yaml
eunox-mcp suggest --output /tmp/draft.yaml # turn the wiretap tape into a draft manifestVerify the four documented outcomes: allowed read_file /reports/*; denied write_file
(AUTHORIZATION_FAILED, not in manifest); denied off-path read (CONDITION_FAILED);
denied non-SELECT query_db (CONDITION_FAILED). Denials must return a structured
JSON-RPC error and the upstream is never called.
eunox-mcp proxy --config demo/gateway.yaml & # transport: http (Windows: run in its own terminal)
curl -s localhost:3000/healthz | jq # status, sessions, auditDropped...
curl -s localhost:3000/metrics | grep eunox_ # Prometheus counters
eunox-mcp kill all --port 3000 # loopback /control/killWindows: PowerShell aliases
curltoInvoke-WebRequest. Usecurl.exe, orInvoke-RestMethod http://localhost:3000/healthzand(Invoke-WebRequest http://localhost:3000/metrics).Content -split "\n" | Select-String eunox_`.
Confirm /healthz, /metrics, /control/kill are loopback-only. Confirm a route
with neither policy: nor enforcement: audit makes the gateway fail closed at
startup (route named in the error).
eunox-mcp audit-verify # expect: "Checked N record(s): N valid, 0 invalid, 0 skipped."Tamper test: edit one byte of a value in the audit log (~/.eunox/audit.jsonl, or
%USERPROFILE%\.eunox\audit.jsonl on Windows), re-run audit-verify, confirm it flags
that record (and the rest of the chain) invalid; then restore/delete. Also confirm
--require-audit makes the proxy exit non-zero at startup when --audit-log points at
an unwritable path (no silent blind run).
With a manifest + live upstream, confirm startup drift warnings on stderr
(fm1 glob over-permission, fm2 dead reference, ...). Confirm --strict-drift turns
over-permission / dead-ref / serverVersion findings into a fatal startup abort,
and that a descriptionHash mismatch aborts with or without the flag.
eunox-mcp validate <m> --live --upstream-url <url> reports the same out of band.
The Docker demo is the fastest independent end-to-end cross-check (Unix shell + Linux containers — on Windows run it from WSL2):
make -C demo up
make -C demo allow # ALLOWED read_file /reports/q3.pdf
make -C demo deny # DENIED write_file (AUTHORIZATION_FAILED)
make -C demo deny-path # DENIED /etc/shadow (CONDITION_FAILED)
make -C demo deny-op # DENIED query_db DELETE (CONDITION_FAILED)
make -C demo audit
make -C demo gateway-up && make -C demo ci-test-gateway
make -C demo down && make -C demo gateway-downOn Linux, if the demo audit log throws a permission error, run
sudo chmod 777 demo/audit && sudo chown -R 0:0 demo/audit, thenmake -C demo up(noted indemo/README.md).
Verify the released image signature (bound to the manifest digest):
cosign verify \
--certificate-identity-regexp "^https://github\.com/eunolabs/eunox/\.github/workflows/release\.yml@refs/tags/" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/eunolabs/eunox-mcp:<tag># macOS / Linux:
rm -rf ~/.eunox ~/eunox-rel
brew uninstall eunox-mcp # macOS, if installed via cask
sudo dpkg -r eunox-mcp # Linux deb (or: sudo rpm -e eunox-mcp)# Windows:
Remove-Item -Recurse -Force $env:USERPROFILE\.eunox, ~\eunox-rel
winget uninstall eunolabs.eunox-mcpRemove the test entry from your MCP host config.
-
First-launch guard on the unsigned binary — macOS Gatekeeper (
xattr -d com.apple.quarantine) and Windows Mark-of-the-Web / SmartScreen (Unblock-File). Linux has none. -
Checksum command differs —
sha256sum -c(Linux) vsshasum -a 256(macOS) vsGet-FileHash(Windows). SECURITY.md only documents the Linux form. -
Both
amd64andarm64per OS — verify and run each on matching hardware. -
Version string is the real tag, never
dev, on the released binary. -
Audit-key permissions —
0600is POSIX (staton macOS/Linux); on Windows it doesn't map to ACLs, so checkicacls/ Properties instead. -
~/ audit dir —~/.eunoxon macOS/Linux,%USERPROFILE%\.eunoxon Windows. -
Loopback-only endpoints —
/healthz,/metrics,/control/killmust refuse a non-loopback bind on every OS.