CONCEPTS
Package checks
Tether checks each synced package before a package manager runs. A package that fails a check waits in the Inbox. Tether runs the checks again on every sync, for new and held packages. For the threats and limits, see Supply-chain security.
Checks by package manager
| Check | Managers | Result |
|---|---|---|
| Trusted machine record | All | Holds a package that no trusted, signed record lists. See Machine trust |
| Name validation | All | Skips names that look like flags, URLs, paths, tarballs, or git+, github:, file: and link: specs |
| Release age (default 7 days) | npm, pnpm, bun, uv, gem | Installs only a release older than the limit. See Release age |
| OSV malware check | npm, pnpm, bun, uv, gem | Blocks a release with a MAL- advisory. Shows other advisories as warnings |
| Install scripts off | npm, pnpm, bun | Installs and upgrades with scripts off, except for packages in packages.allow_scripts |
| Trusted taps | Homebrew | Holds taps outside homebrew/* and packages.brew.trusted_taps, and their formulae and casks |
Homebrew gets no release-age limit and no OSV check, because Homebrew installs only the current release and OSV has no Homebrew data. For Homebrew, Tether relies on the tap allowlist.
Release age
packages.min_release_age_days sets the limit. The default is 7 days. 0 turns it off. To set another limit on one machine only, use ~/.tether/local.toml.
- npm 11.10 and later, pnpm 10.16 and later except the 12.0.0 prereleases, bun 1.3 and later, and uv enforce the limit themselves. Tether passes it to them
- gem, and older npm, pnpm and bun, cannot enforce it. For these, Tether checks the publish time in the registry itself. It installs the exact release it checked: the newest release older than the limit, or the version a trusted record lists when that version is old enough
- Tether holds such a package only when the release is too new ("too new") or the registry check fails ("age not checked"). A "too new" hold ends when the release is old enough
- When a trusted record names a release that is newer than the limit, and the manager rejects it, Tether holds the newest release older than the limit as "pinned too new"
- A running process, such as the daemon, asks the registry about a package once per day. Each CLI command asks again
- The daemon does not auto-upgrade a manager that cannot enforce the limit.
tether upgradeasks first, except for gem, whose upgrades install the release Tether checked. Dependencies that already fit stay as they are, but a gem's new dependencies install at their newest release, so the daemon does not upgrade gem
OSV checks
Tether finds the release that would install: the newest version that a trusted record lists. Without one, it is the newest stable release older than the release-age limit. It checks that release against OSV and installs it pinned. So the release that OSV checked is the release that installs.
Sometimes Tether cannot find that release, for example from a registry that needs a login. OSV then checks all releases, and only MAL- advisories count. When any release has one, the package waits in the Inbox as "malicious releases". Unlike a malicious release that would install, you can approve it, after a warning.
A network failure does not block a sync. The dashboard and tether packages approve ask before they install without an OSV result. Tether uses curl with a 10-second limit.
Upgrades get the same check. An upgrade target with a MAL- advisory waits as "malicious upgrade", and the installed version stays.
Install scripts
Add a package to packages.allow_scripts to run its own install scripts:
tether config set packages.allow_scripts '["esbuild"]' pnpm 10.4 and later, and npm 12 and later, run scripts for that package only. Older npm and pnpm would also run the scripts of its dependencies. On those versions, its scripts stay off, and Tether shows a warning once. gem and uv have no install-script switch.
Homebrew taps
Taps outside homebrew/* must be in packages.brew.trusted_taps. Other taps, and their formulae and casks, wait in the Inbox as "untrusted tap".
tether config set packages.brew.trusted_taps '["oven-sh/bun"]' - Tether finds the tap of a short name, such as
bun, before it installs it - An approved formula or cask installs by its qualified name, such as
homebrew/core/wget. Approving a formula from an untrusted tap does not trust the tap - Homebrew 7 keeps its own trust store. Before each Homebrew install, Tether copies its trusted taps, the taps approved in the Inbox, and the formulae and casks approved from an untrusted tap there, with
brew trust - Tether only adds entries to Homebrew's trust store. It never removes them. So Homebrew can still trust a tap that Tether does not trust
- Upgrades include only outdated packages from trusted taps. Tether updates trusted taps with
git pull --ff-only. It does not runbrew update. Run it yourself to update Homebrew
Other safeguards
- Every package manager runs in the empty directory
~/.tether/run, not in your current directory - Tether rejects names and versions that a manager reads as a local file, such as
.tgz,.gem,.whlor a Homebrew.rbfile gem installuses--remote, and--no-document, so RubyGems runs no documentation hook- To hold packages from trusted machines too, set
packages.auto_install_from_trusted = false