Engineering preview No published release

Get the bits

There is no installer to fetch. Below is what the release format will be when one exists, what the verification actually checks today, and how to build and verify from a checkout in the meantime.

Downloads

Every file is listed with its size and SHA-256 so you can check it. A row is a link only when the file is inside this site.

Machine-readable manifest (JSON)

What the labels mean

Latest
The newest published version of that file. An older version carries its number instead.
Available
The file is on this site and its hash below is the one the deploy measured.
Not published yet
There is no file yet, so there is no link. Nothing here is a placeholder download.
Unsigned
Not code-signed or notarized. Check the SHA-256 before you open it.

Version · File · Size · SHA-256

Nothing is published yet

This is the honest state, not a broken page. Two independent facts say so:

Signed feed asset
HTTP 404 host hbozok-US/tether-updates, asset releases/latest/download/stable.json, measured . The nine-byte Not Found body is not a signed envelope.
Git tags in this repository
2 Both are archive/* housekeeping tags that preserve abandoned worker branches, not releases. Zero v<version> release tags exist, so no release exists to point an asset URL at.
Version declared in-tree
0.1.0 from pyproject.toml. Nothing has shipped it, so it is not a release you can fetch.

Because no release URL resolves, this page contains no download link at all. A button that 404s would be worse than none. When a release is published, the asset names and verification below are what it will use — those come from tools/tether_publish.py and src/tether_update.py, not from a plan.

One thing does work today: a local, hash-pinned macOS install

tools/tether_install.py and tools/install.sh --archive now accept a macOS CLI archive you already have on disk, verify its sha256 against a pin you supply or a sibling .sha256 file, and land it — no signed feed involved, because none exists for macOS yet. This is not a public release: the binary is unsigned and unnotarized, and you still have to build the archive yourself with tools/tether_build_cli.py --platform macos-arm64. See the macOS steps below.

Build from checkout for your platform

These are the archives you build yourself. The commands below are the real ones in tools/tether_build_cli.py and tools/install.sh / install.ps1. The Tether source repository is private, so there is no public clone URL to publish here. If you have been given checkout access, run them from that checkout.

macOS

Platform key macos-arm64 or macos-x86_64. The Mac Status app archive is a .zip; a macOS CLI archive is a .tar.gz. Signing is a local-unsigned lane only until Developer ID.

  1. Build the macOS CLI archive. macOS is not in the build tool's default platform set, so it has to be named explicitly. python3 tools/tether_build_cli.py --version 0.1.0 --build 1 --platform macos-arm64 --out-dir dist/cli
  2. Check the sha256 sidecar the build wrote beside the archive. checks the exact archive step 1 wrote (cd dist/cli && shasum -a 256 -c tether-0.1.0-macos-arm64.tar.gz.sha256)
  3. Install from that local archive. The installer verifies the pin before it unpacks. refuses if step 2's check did not pass sh tools/install.sh --yes --archive dist/cli/tether-0.1.0-macos-arm64.tar.gz
  4. Put the installed commands on this shell's PATH. The installer never edits shell startup files. makes the launcher from step 3 discoverable in this shell export PATH="$HOME/.local/opt/tether:$PATH"
  5. Create a one-time invite for a Mac you own using the installed CLI. needs the PATH export and binary step 3 just installed tether fabric pair create PEER_ID --display-name NAME --json --invite

Windows

Platform key windows-x86_64, archive suffix .zip. The archive ships a tether.exe trampoline and tether.cmd. Peer execution on Windows is not yet — the service and tray planner exists, execute stays blocked.

  1. Build the Windows archive from the checkout. python3 tools/tether_build_cli.py --version 0.1.0 --build 1 --platform windows-x86_64 --out-dir dist/cli
  2. Compare the file hash against the sidecar pin by hand. checks the exact archive step 1 wrote Get-FileHash -Algorithm SHA256 dist\cli\tether-0.1.0-windows-x86_64.zip
  3. Run the installer from disk. It refuses a streamed invocation. reruns the check step 2 did by hand powershell -File tools\install.ps1 --yes --archive=dist\cli\tether-0.1.0-windows-x86_64.zip
  4. Put the installed commands on this PowerShell's PATH. The installer never edits the registry or shell profile. makes the launcher from step 3 discoverable in this session $env:Path = "$env:LOCALAPPDATA\Tether\tether;$env:Path"
  5. Expect a refusal on peer execution. Do not read the archive as parity. runs against the install step 3 landed after the PATH step tether doctor

Run it once on Windows and paste the result

No Windows machine is reachable from the build host, so this archive has not been run there. On the Windows machine, from the unpacked archive, run this one command and paste what it prints.

tether_windows_user_smoke.cmd

Linux

Platform key linux-x86_64 or linux-arm64, archive suffix .tar.gz. A linux-arm64 node has been brought up from a sealed archive in a VM; authorized real-host peer acceptance is still open.

  1. Build the Linux archive from the checkout. A donor must use a bundled runtime, so pass --runtime bundled when this archive will be paired as a machine (a controller-only install can leave it off). this is the archive tether pair ships to the node; without a bundled runtime the node would need host Python and the install refuses python3 tools/tether_build_cli.py --version 0.1.0 --build 1 --platform linux-x86_64 --runtime bundled --runtime-fetch --out-dir dist/cli
  2. Verify the GNU-format sidecar pin. checks the exact archive step 1 wrote (cd dist/cli && sha256sum -c tether-0.1.0-linux-x86_64.tar.gz.sha256)
  3. Install from the local archive with the fail-closed wrapper. refuses if step 2's check did not pass sh tools/install.sh --yes --archive dist/cli/tether-0.1.0-linux-x86_64.tar.gz
  4. Put the installed commands on this shell's PATH. The installer never edits shell startup files. makes the launcher from step 3 discoverable in this shell export PATH="$HOME/.local/opt/tether:$PATH"
  5. The default (non-bundled) format's launcher needs python3 on PATH; the --runtime bundled archive from step 1 does not. what step 3's installed launcher requires at runtime python3 --version
  6. Add one machine you can already ssh to, then see it in the pool. This is the first-run step; without it every command stays local. needs step 4's PATH export and ssh access to the donor tether init HOST && tether --status

All three platforms are shown above. With JavaScript enabled, this page detects your platform and shows one, with a manual switch for the others.

What verification actually checks

These are the checks implemented in src/tether_update.py, src/tether_update_feed.py, and tools/tether_publish.py today — not a roadmap. They are not eight independent rules: they are one gate chain, run in this order across a real check-then-apply, and a refusal at any step stops the chain before anything after it runs.

  1. https on every hop, including redirects

    The feed alias redirects to a CDN host. Both the CLI and the publish verifier refuse a redirect that is not https rather than following it. The very first fetch is gated on this before a single byte is trusted.

  2. Envelope size cap before parsing

    The CLI refuses an envelope larger than 262144 bytes unread; the publisher refuses to emit one larger than 131072 bytes, mirroring the Mac app's limit. Nothing is handed to a parser until this holds.

  3. Ed25519 signature against a pinned key

    The feed is a tether.update-feed-envelope.v1 envelope carrying a tether.update-feed.v1 payload, algorithm ed25519. The client trusts one compiled-in publisher key and checks the envelope's key_id against it. A build made against a staging key says so in every verdict and is refused at publish time. Everything below reads a payload that has already passed this.

  4. Expiry, and no silent downgrade

    A feed whose expires_at has passed is refused before its contents are trusted for anything else. A feed older than the local install is reported as ahead of feed, never applied as a downgrade. Same version with a newer signed build is a real update.

  5. Wrong-platform assets are never substituted

    Only once the feed above is trusted does the client look up its own <os>-<arch> key, falling back only to that OS's universal entry. If neither exists, no asset is offered — the Mac app zip is never handed to another OS.

  6. Size and sha256 of every signed locator

    The archive selected above is streamed and hashed as it downloads: each assets[<os>-<arch>] archive must match the signed size and sha256 or the bytes are discarded, not installed.

  7. Attestation subject must be this archive

    Once the archive above matches, its .intoto.json sidecar is bound to that same archive name and sha256. A present sidecar that describes different bytes is a refusal, not a warning. CLI archives for Linux and Windows require one.

  8. Independent builders must agree

    Last: release preflight requires a builders-agree second-builder verdict per published platform, and the consumer verifies the quorum document against the exact archive it just verified above before calling the install applied.

Asset names a release will use

Taken from the staging code, so you can recognise a real release when one appears. <version> is dotted digits and the tag is v<version>.

  • tether-<version>-linux-x86_64.tar.gz
  • tether-<version>-linux-arm64.tar.gz
  • tether-<version>-windows-x86_64.zip
  • TetherStatus-<version>.zip — the Mac app
  • <archive>.sha256 — GNU first-field pin per archive
  • SHA256SUMS — one file covering every CLI archive
  • <archive>.intoto.json — SLSA-shaped attestation
  • second-builder-quorum.json — reproduction record
  • stable.json / beta.json — the signed feed

Publisher key the client pins: 1b1b424c81261672805bb2e8bbd49cddc536409b191645a7e00241f27fb1549a This is a public verification key. It is printed in full because a truncated key cannot be used to verify anything.