feat(install): install from install.socket.dev, optionally without github.com - #145
feat(install): install from install.socket.dev, optionally without github.com#145Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
Conversation
…thub.com
The documented one-liner becomes
curl -fsSL https://install.socket.dev/patch | sh
replacing a raw.githubusercontent.com URL that asked users to trust a
third-party CDN for a script they pipe into a shell, and that is the first
URL a locked-down egress policy blocks. install.socket.dev is a name Socket
controls, already inside the trust boundary a customer grants socket.dev.
What the host serves is a byte-for-byte copy of scripts/install.sh, with its
SHA-256 alongside at /patch.sha256 — the README tells people to diff it, so
that has to hold literally. The GitHub raw URL keeps working and serves the
same bytes, for anyone who would rather not depend on the Socket domain.
Archives can come from Socket too. New SOCKET_PATCH_BASE_URL points the
downloads at any releases base answering GitHub's two asset paths,
`<base>/latest/download/<file>` and `<base>/download/v<ver>/<file>`:
curl -fsSL https://install.socket.dev/patch \
| SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases sh
install.socket.dev relays exactly those paths from the GitHub release
(SocketDev/depscan#23840), which is why one template covers both origins and
the script needs no branching. A new socket-patch RELEASE needs no publish
for any of this: the origin resolves "latest" per request against the
upstream release, so nothing runs at release time.
The default origin stays GitHub here. Flipping it is one line, held until the
relay is verified in prod — a script defaulting to a host that does not answer
yet is a broken installer for everyone running it from a git checkout or the
raw URL, and CI's end-to-end install step would fail on main immediately.
Also adds SOCKET_PATCH_INSTALL_DIR, which wins over both existing defaults:
what unprivileged installs into a toolchain-managed prefix need, and what
makes the script testable without writing to a system path.
The trust model is unchanged and the docs are careful not to imply otherwise:
binaries still come from the GitHub release and are still verified against its
SHA256SUMS, and nothing is signed. Whichever origin serves the bytes, the
checksums come from that same origin. Hosting moved who serves the script,
nothing more.
Four gaps closed around the artifact users are told to pipe into a shell:
* install.sh was only shellchecked, never run. CI now installs with it end to
end and execs the result — twice, once with the default origin and once
through SOCKET_PATCH_BASE_URL, so the URL template is covered too.
* A third CI step installs through install.socket.dev and asserts the
installed version matches what that host reports as latest. It skips itself
with a notice until the host resolves, so it is inert until the relay ships
rather than red from merge.
* Nothing kept the URL consistent across the README, the script's own usage
comment, and the runbook; a grep guard fails if any of them drifts.
* Nothing checked the HOSTED copy. The new `installer-drift` workflow (weekly
+ dispatch) diffs the served bytes against scripts/install.sh, verifies the
published checksum, and shellchecks what is actually served. Deliberately
not part of CI: it tests a deployed artifact, so a red run means "bump the
submodule pin in depscan", not "this PR is broken". It also names the
specific failure this design is exposed to — a Cloudflare bot challenge,
which would otherwise feed an HTML interstitial to sh.
docs/installer-hosting.md is the runbook for the part that is not obvious from
this repository: the hosted copy is published out of depscan's vendored
submodule pin, so an installer change here goes live on a submodule bump plus
a deploy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9a01e26 to
8ede593
Compare
There was a problem hiding this comment.
Three things, all inline. The big one: pointing SOCKET_PATCH_BASE_URL at the host adds Socket's stack to the trust set rather than dropping GitHub, and SHA256SUMS rides the same origin as the archive. docs/installer-hosting.md says the trust model is unchanged, which holds for the default only.
One not inline, since the file is not in the diff: SOCKET_UPDATE_BASE_URL drops the https-only redirect rule in update/release.rs too.
| RELEASES_BASE="${SOCKET_PATCH_BASE_URL:-https://github.com/${REPO}/releases}" | ||
| while :; do | ||
| case "$RELEASES_BASE" in | ||
| */) RELEASES_BASE="${RELEASES_BASE%/}" ;; | ||
| *) break ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
SOCKET_PATCH_BASE_URL is never checked for https, so http:// is taken without a word. download() is a plain curl -fsSL too, no --proto-redir. Both legs matter here, since SHA256SUMS comes down the same way.
| RELEASES_BASE="${SOCKET_PATCH_BASE_URL:-https://github.com/${REPO}/releases}" | |
| while :; do | |
| case "$RELEASES_BASE" in | |
| */) RELEASES_BASE="${RELEASES_BASE%/}" ;; | |
| *) break ;; | |
| esac | |
| done | |
| RELEASES_BASE="${SOCKET_PATCH_BASE_URL:-https://github.com/${REPO}/releases}" | |
| # Refuse a non-HTTPS base: the archive and the SHA256SUMS that verifies it are | |
| # both fetched from here, so plaintext would forfeit both. | |
| case "$RELEASES_BASE" in | |
| https://*) ;; | |
| *) | |
| echo "SOCKET_PATCH_BASE_URL must be an https:// URL, got: $RELEASES_BASE" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| while :; do | |
| case "$RELEASES_BASE" in | |
| */) RELEASES_BASE="${RELEASES_BASE%/}" ;; | |
| *) break ;; | |
| esac | |
| done |
| if ! curl -sfI -m 20 https://install.socket.dev/patch/latest >/dev/null 2>&1; then | ||
| echo "::notice::install.socket.dev/patch/latest does not answer yet — skipping the Socket-origin install." | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
curl -sfI fails on a 403, so a challenge makes this print the skip notice and pass green. It turns itself off on the one thing it is here to catch. Only a resolve failure should skip, and that code wants a name rather than sitting bare.
| if ! curl -sfI -m 20 https://install.socket.dev/patch/latest >/dev/null 2>&1; then | |
| echo "::notice::install.socket.dev/patch/latest does not answer yet — skipping the Socket-origin install." | |
| exit 0 | |
| fi | |
| rc=0 | |
| code=$(curl -sI -m 20 -o /dev/null -w '%{http_code}' https://install.socket.dev/patch/latest) || rc=$? | |
| # curl exit 6 is "could not resolve host": the host is not up yet. | |
| if [ "$rc" -eq 6 ]; then | |
| echo "::notice::install.socket.dev does not resolve yet, skipping the Socket-origin install." | |
| exit 0 | |
| fi | |
| if [ "$rc" -ne 0 ]; then | |
| echo "::error::probing install.socket.dev failed (curl exit $rc)" >&2 | |
| exit 1 | |
| fi | |
| if [ "$code" != "200" ]; then | |
| echo "::error::install.socket.dev/patch/latest answered $code, not 200 (a challenge would feed HTML to sh)" >&2 | |
| exit 1 | |
| fi |
| for f in README.md scripts/install.sh docs/installer-hosting.md; do | ||
| if ! grep -qF 'https://install.socket.dev/patch' "$f"; then | ||
| echo "Error: $f no longer references https://install.socket.dev/patch" >&2 | ||
| exit 1 | ||
| fi | ||
| done |
There was a problem hiding this comment.
This fails unless the README already has the new URL, so the flip has to merge before DNS and the deploy exist. Anyone following main in that window gets a DNS error on the documented command. Could it skip while the host does not resolve, like the step above?
|
|
||
| ## What the trust model actually is | ||
|
|
||
| Unchanged by the hosting move, and worth being precise about: |
There was a problem hiding this comment.
True for the default origin, not for the one the README recommends. Pointing the base URL at the host adds Socket's stack to the trust set, and the checksums come from the same place as the archive.
| Unchanged by the hosting move, and worth being precise about: | |
| Unchanged for the default origin. Pointing `SOCKET_PATCH_BASE_URL` (or | |
| `SOCKET_UPDATE_BASE_URL`) at `install.socket.dev` does change it: GitHub stays in the | |
| trust set, because the relay pins nothing and re-fetches upstream per request, and | |
| Socket's serving stack joins it. The checksums are relayed from the same origin as the | |
| archive, so they are not an independent check of it. Worth being precise about: |
| The publish path lives in [depscan][depscan], which vendors this repository as | ||
| `submodules/socket-patch`: | ||
|
|
||
| 1. A change to `scripts/install.sh` merges **here**. | ||
| 2. depscan's `submodules/socket-patch` pin is bumped to that commit. | ||
| 3. depscan's prod deploy runs its **Publish install.socket.dev site** step, | ||
| which copies `submodules/socket-patch/scripts/install.sh` to | ||
| `gs://socket-install-prod/patch`, publishes its sha256 and the landing page, | ||
| then re-reads the object and fails the deploy if the bytes do not match. | ||
| 4. `install-server` (a `gcs-bucket-server` instance, `tanka/lib/depscan/install-server.libsonnet`) | ||
| serves that bucket at `install.socket.dev`. |
There was a problem hiding this comment.
This repo is public, and this spells out the whole publish path for a curl | sh endpoint: which repo, which identity, which bucket, and that a pin bump is the gate. The long version already lives in the internal doc. Suggest keeping the drift contract and dropping the rest.
| The publish path lives in [depscan][depscan], which vendors this repository as | |
| `submodules/socket-patch`: | |
| 1. A change to `scripts/install.sh` merges **here**. | |
| 2. depscan's `submodules/socket-patch` pin is bumped to that commit. | |
| 3. depscan's prod deploy runs its **Publish install.socket.dev site** step, | |
| which copies `submodules/socket-patch/scripts/install.sh` to | |
| `gs://socket-install-prod/patch`, publishes its sha256 and the landing page, | |
| then re-reads the object and fails the deploy if the bytes do not match. | |
| 4. `install-server` (a `gcs-bucket-server` instance, `tanka/lib/depscan/install-server.libsonnet`) | |
| serves that bucket at `install.socket.dev`. | |
| The hosted copy is published by Socket's deployment pipeline from a reviewed pin of this | |
| repository, so an installer change here goes live once that pin is bumped and deployed. | |
| The public repository needs no write credentials into Socket infrastructure, and the pin | |
| bump is a reviewed change, so nothing reaches a `curl | sh` endpoint without review on | |
| both sides. |
| On a network that blocks or distrusts `github.com`, set `SOCKET_PATCH_BASE_URL` so the | ||
| archives come from Socket too — `install.socket.dev` relays them from the GitHub release, | ||
| checksums included: |
There was a problem hiding this comment.
"checksums included" reads as reassurance, but it is the weak part: they come from the same origin as the archive, so they do not vouch for it on their own. One clause so a reader knows what they are trusting.
| On a network that blocks or distrusts `github.com`, set `SOCKET_PATCH_BASE_URL` so the | |
| archives come from Socket too — `install.socket.dev` relays them from the GitHub release, | |
| checksums included: | |
| On a network that blocks or distrusts `github.com`, set `SOCKET_PATCH_BASE_URL` so the | |
| archives come from Socket too. `install.socket.dev` relays them from the GitHub release, | |
| checksums included - which also means the checksums come from the relay rather than | |
| independently, so this trades GitHub's origin for Socket's rather than adding a check: |
| # curl -fsSL .../install.sh | SOCKET_PATCH_VERSION=3.0.0 sh | ||
| # curl -fsSL https://install.socket.dev/patch | SOCKET_PATCH_VERSION=3.0.0 sh | ||
| # | ||
| # Override where the archives come from with SOCKET_PATCH_BASE_URL — a releases |
There was a problem hiding this comment.
Style nit, take it or leave it: 22 em-dashes and one en-dash across the diff. Plain hyphens read the same, type easier, and grep. Nothing here enforces it, so entirely your call.
| # Override where the archives come from with SOCKET_PATCH_BASE_URL — a releases | |
| # Override where the archives come from with SOCKET_PATCH_BASE_URL - a releases |
The rest: install.sh:115, README.md:35,44, docs/installer-hosting.md:10,27,48,72,123,125 (plus the en-dash in "steps 2-3" at :111), .github/workflows/ci.yml:98,104,111,123,130,144, .github/workflows/installer-drift.yml:4,46,74, CHANGELOG.md:342,350,359.
Points the documented one-liner at a Socket-controlled domain, and lets the archives come
from Socket too:
curl -fsSL https://install.socket.dev/patch | shThe old URL was
raw.githubusercontent.com— a third-party CDN serving a script users pipeinto a shell, and the first URL a locked-down egress policy blocks.
Companion PR: SocketDev/depscan#23840, which serves the host. Nothing here breaks before
that lands: the drift workflow and the Socket-origin CI step are both inert until the
host resolves, and the README's alternative URL works today.
What the host serves
A byte-for-byte copy of
scripts/install.sh, with its SHA-256 at/patch.sha256. TheREADME tells people to diff it, so that has to hold literally. The GitHub raw URL keeps
working and serves the same bytes.
Installing without reaching github.com
New
SOCKET_PATCH_BASE_URLpoints the archive downloads at any releases base answeringGitHub's two asset paths:
curl -fsSL https://install.socket.dev/patch \ | SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases shinstall.socket.devrelays exactly those paths from the GitHub release, which is why onetemplate covers both origins and the script needs no branching.
A new release needs no publish for this. The origin resolves "latest" per request
against the upstream release, so cutting 3.4.0 makes it installable from Socket's host
immediately — nothing runs at release time.
socket-patch --updatecan use the same host today with no CLI changes, via theSOCKET_UPDATE_BASE_URLoverride it already has. One caveat documented rather than paperedover: a non-default value intentionally downgrades the downloaded binary's version
self-check from hard-fail to a warning, because that knob targets mirrors that may
repackage. Making Socket's host a first-class endpoint set that keeps the strict check is a
CLI change, not a hosting one — deliberately not here.
Also adds
SOCKET_PATCH_INSTALL_DIR, which wins over both existing defaults: whatunprivileged installs into a toolchain-managed prefix need, and what makes the script
testable without writing to a system path.
Trust model: unchanged, and the docs say so
Binaries still come from the GitHub release and are still verified against its
SHA256SUMS. Nothing is signed. Whichever origin serves the bytes, the checksums come fromthat same origin. Hosting moved who serves the script, nothing more.
The default origin is still GitHub, on purpose
Flipping it is one line, held until the relay is verified in prod. A script defaulting to a
host that does not answer yet is a broken installer for everyone running it from a git
checkout or the raw URL — and CI's end-to-end install step would fail on
mainimmediately. Sequence: this merges → depscan#23840 deploys → flip the default.
Four gaps closed around a
curl | shartifactinstall.shwas only shellchecked, never run. CI now installs with it end to endand execs the result — twice, once with the default origin and once through
SOCKET_PATCH_BASE_URL, so the URL template is covered too.install.socket.devand asserts the installed versionmatches what that host reports as latest. Skips itself with a notice until the host
resolves.
the runbook; a grep guard fails if any drifts.
installer-driftworkflow (weekly + dispatch)diffs the served bytes against
scripts/install.sh, verifies the published checksum, andshellchecks what is served. Not part of CI — it tests a deployed artifact, so a red run
means "bump the submodule pin in depscan", not "this PR is broken". It also names the
specific failure this design is exposed to: a Cloudflare bot challenge, which would
otherwise feed an HTML interstitial to
sh.docs/installer-hosting.mdis the runbook for the non-obvious part: the hosted copy ispublished from depscan's vendored submodule pin, so an installer change here goes live on a
submodule bump plus a deploy.
Verified
CI green on the pre-squash commits — all six
Shellsteps inlint-ecosystemspassed,including both end-to-end installs, and the Socket-origin step correctly emitted
install.socket.dev/patch/latest does not answer yet — skipping. Locally, against the realrelay: latest and pinned installs both succeeded and were checksum-verified, and
socket-patch --updateworked through the same host (--dry-runresolved latest;--update 3.2.0downloaded and swapped).The only failing check is
hosted-e2e/gem_bundler_hosted_redirect_and_known_install_defect— the known gem/Bundler
marshal data too shortdefect against rubygems.org, unrelated tothis change.
Sequencing note
depscan's submodule pin is 19 commits behind
main, andscripts/install.shdoesdiffer (
mainhas the SC2144detect_libcfix). So: merge this → bump the pin → deploy.installer-driftis red in that window by design, and its error message says which bump ismissing.
Not in scope
patch.ps1for native Windows — the hosting side already supports it; left out ratherthan shipped untested from a macOS box.