SUPPLY-CHAIN SECURITY

Sync your tools.
Check them first.

Tether installs your global packages on every machine. So a bad release or a changed manifest could reach all of them. Tether checks each synced package before a package manager runs.

Checked before
they install.

Release age
7 days by default
Install scripts
Off for npm, pnpm, bun
Known malware
OSV MAL- advisories
Homebrew taps
Allowlist
Machine records
Signed, trusted locally

THE THREAT

One bad release.
Every machine.

A sync tool spreads what you install. That is its job. It also spreads a bad package to each machine that syncs.

Compromised releases.

An attacker who takes over a maintainer account can publish a malicious version to npm, PyPI or RubyGems.

Registries often remove such versions after someone reports them. A machine that installs before then gets the bad version.

Worm-style packages.

Some malicious packages run code at install time. They steal tokens from the machine and use them to publish more infected packages.

Install scripts are a common way for this code to run.

Stolen repo tokens.

Anyone with push access to your sync repo can add a line to a manifest.

Without checks, each of your machines would install that package on its next sync.

THE DEFENCES

In the manifest.
Not yet approved.

Tether runs these checks on every sync. It runs them again for each held package. A package that fails a check waits in the Inbox on this machine.

FROM MANIFEST TO INSTALL
  1. Manifest changeA package arrives from another machine
  2. Run the checksTrusted record · Name · Tap · Release age · OSV
  3. Install or holdInstall it, or hold it in the Inbox
Tether checks held packages again on each sync. A package installs when no check holds it.

A release-age limit.

npm, pnpm, bun and uv skip releases newer than 7 days. Set packages.min_release_age_days to change the limit. 0 turns it off.

Some managers cannot enforce the limit: gem, npm before 11.10, pnpm before 10.16 and bun before 1.3. Their synced packages wait for approval.

Install scripts off.

npm, pnpm and bun install and upgrade with install scripts turned off.

Add a package to packages.allow_scripts when it needs its scripts. Older npm and pnpm cannot limit scripts to one package, so its scripts stay off there.

Malware checks with OSV.

Tether checks npm, pnpm, bun, uv and gem packages against OSV before it installs or upgrades them. A MAL- advisory blocks that release.

Tether finds the release that would install, checks it, and installs exactly that release.

Names, not commands.

Tether skips manifest names that look like flags, URLs, paths, tarballs or git+ specs.

Every package manager runs in an empty directory, ~/.tether/run. A local file or project config cannot change what it installs.

Taps you chose.

Homebrew taps outside homebrew/* must be in packages.brew.trusted_taps. Other taps, and their formulae and casks, wait in the Inbox.

Tether finds the tap of a short name, such as bun, before it installs it. Upgrades name only packages from trusted taps.

Exact versions.

Each signed machine record lists the version it installed of each npm, pnpm, bun, uv and gem package. Manifests list only names.

A sync installs the newest version that a trusted machine lists. A manifest cannot pick an older version.

THE TRUST MODEL

Signed records.
Your trust list.

Each machine has its own SSH signing key. Every sync signs the machine record that lists its packages and versions.

Your trust list stays on this machine and never syncs. A package installs on its own only when a trusted, signed record lists that exact version. A line in a manifest grants no trust.

Trust is transitive. When you trust a machine, you also trust the packages it installed. A package you rejected on this machine stays blocked.

Terminal
tether machines show
tether machines trust <id> --fingerprint SHA256:...
ON EACH SYNC
  1. Sign the recordmachines/<id>.json · ed25519 key
  2. Verify locallyMatch the key in ~/.tether/trusted_keys
  3. Install listed packagesHold all other packages in the Inbox
Tether never moves trust to another machine id on its own.

RECORDS THAT CHANGE

A push is not
a signature.

Someone with push access can edit or replace machine records. Tether checks each record before it counts.

Old records do not count.

Each record has a generation that increases with every save. Tether keeps the newest generation it accepted from each key.

An older signed record, such as one from git history, grants no trust.

Changed keys stop.

When a trusted machine signs with a different key, Tether warns you. It does not trust that record until you approve the new key.

Tether ignores a record that fails its signature, and shows a warning.

Your record, from your copy.

This machine builds its record from ~/.tether/machine.json, not from the repo.

A change that someone pushes to the repo copy does not get your signature.

YOU DECIDE

One Inbox.
Every held item.

Held packages, taps and machine keys wait in the Inbox, ~/.tether/inbox.json. The Inbox never syncs. Review it in the terminal or on the dashboard Security tab.

What the Inbox holds.

  • Packages that no trusted machine record lists
  • Packages from untrusted Homebrew taps
  • Packages whose manager cannot enforce the release-age limit
  • Releases with a MAL- advisory, which you cannot approve
  • A release for this OS, when the version that another OS pins fails here
  • New machine keys and changed machine keys

How you decide.

An approval covers one version, one Homebrew tap, or one machine key. A rejected item is not offered again.

Tether refuses a decision when the item changed after you saw it. It then shows you the new item.

Terminal
tether packages inbox
tether packages approve <id> --expect <version>
tether packages reject <id> --expect <version>

KNOW THE LIMITS

What the checks
cannot promise.

These checks reduce the risk. They do not remove it.

Gaps in the checks.

OSV lists known malware only. A new malicious release can pass until someone reports it.

When OSV cannot be reached, a sync still installs. The dashboard and tether packages approve ask first.

Homebrew has no release-age limit and no OSV data. gem and uv have no install-script switch, so Tether cannot turn their build steps off.

Machines that still run Tether 1.x do not run these checks.

Trust follows the machine.

When you trust a machine, you trust the packages its user installs on it.

Anyone with a machine’s ~/.tether/signing_key can sign records as that machine. Protect it like an SSH key.

Compare fingerprints on both machines before you trust a key. Commit signatures are an audit trail only. Record signatures decide trust.

VERIFY THE DETAILS

The code is open.

Read the implementation and its tests.

SET IT UP

Trust your machines.
Review the Inbox.

After the upgrade, trust each machine once. Then approve or reject what waits.

Read the upgrade guide