Skip to content

fix(security): keep launcher CLI downloads on https and browserstack.com [DEVA11Y-473] - #49

Merged
Crash0v3rrid3 merged 2 commits into
mainfrom
fix/DEVA11Y-473-launcher-https-redirect
Oct 22, 2026
Merged

Crash0v3rrid3 merged 2 commits into
mainfrom
fix/DEVA11Y-473-launcher-https-redirect

Conversation

@Crash0v3rrid3

@Crash0v3rrid3 Crash0v3rrid3 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Security's follow-up on DEVA11Y-473, after #46 merged. The launchers (scripts/*/cli.sh) download the CLI with curl -L, and nothing checks where the redirect lands. A redirect can go to any host, including plain http://. On macOS the signature check (#47) covers that. On Linux nothing verifies the binary, and the risk Security accepted on DEVA11Y-473 assumes HTTPS from BrowserStack end to end. Security asked for:

  1. --proto =https --proto-redir =https on that curl call
  2. refusing a final URL that isn't on browserstack.com, using the url_effective the launchers already capture

What

Launchers (the change is identical in bash, zsh and fish):

  • The download curl gets --proto '=https' --proto-redir '=https'. curl refuses any non-https request or redirect hop and exits 1, and the launcher reports BrowserStack CLI download was redirected to a non-HTTPS URL; refusing it.
  • New is_allowed_download_url: the final URL must be https:// with host browserstack.com or a subdomain of it. The check is case-insensitive and ignores a port or a trailing dot. It reads the host after stripping userinfo, so https://browserstack.com@evil.example/ is refused. Look-alikes such as browserstack.com.evil.example and evilbrowserstack.com are refused too.
  • download_binary runs that check right after curl succeeds, before the size check or any extraction. If the check fails, the zip is deleted and the launcher exits 2, as a failed signature check does. On a 304, url_effective is still the redirect target, so a cached zip is held to the same rule.
  • Self-update .sha256 sidecars are regenerated.

Tests/CI: tests/launcher-signature/run_tests.sh url lifts is_allowed_download_url verbatim from each launcher and runs 6 allowed and 12 hostile URLs through it. It needs no network. The new job, Launcher download URL allowlist, runs it on ubuntu-latest. It isn't a required check.

Extraction-guard suite (DEVA11Y-484): the suite serves its fixtures over plain http on 127.0.0.1 through _shim/curl, so the new checks refused every fixture. For a swapped URL, the shim now widens --proto/--proto-redir to =http,https and reports the production redirect target as url_effective. download_binary itself is still unmodified. The harness loads is_allowed_download_url and greps for both checks; I confirmed by mutation that removing them turns the suite red. New cases 10 and 11 turn each relaxation off: the launcher must then refuse the http download (exit 1) and the off-domain download (exit 2), leaving nothing on disk. The suite now passes 99/99.

README: the launcher paragraph now states the download rules for the launchers directly, rather than "the same check".

Out of scope: self-update downloads the launcher from raw.githubusercontent.com (a different origin, covered by its own checksum sidecar), so I didn't change it.

Testing (macOS arm64, curl 8.7.1, real network)

Today the live endpoint redirects once, to https://sdk-assets.browserstack.com/binary-<os>-<arch>-1.63.0.zip, for macOS and Linux on both arm64 and x64, so real downloads pass the host check.

Test Result
run_tests.sh url ✅ 54/54 (18 cases × bash/zsh/fish)
run_tests.sh matrix (existing signature suite, with the reworked extract) ✅ 21/21
bash -n on all launchers; shasum -c on the sidecars ✅
download_binary end to end, live endpoint, empty cache ✅ rc=0, verified and published
Same, cached zip (304) ✅ rc=0
Endpoint swapped for an https→http redirect ✅ curl: (1) Protocol "http" disabled (in redirect), rc=1, nothing left on disk
Endpoint swapped for an https redirect to example.com ✅ download came from https://example.com/, not https on browserstack.com, rc=2, zip deleted

The end-to-end runs lifted download_binary and its helpers verbatim from scripts/bash/cli.sh and used a scratch cache directory. For the two redirect cases, only the endpoint URL was replaced, with an httpbin redirector.

🤖 Generated with Claude Code

…com [DEVA11Y-473]

The launchers downloaded the CLI with `curl -L` and never checked where the
redirect landed, so it could go to any host, over plain http. On Linux nothing
verifies the binary's signature, and the risk Security accepted on DEVA11Y-473
assumes HTTPS from BrowserStack end to end.

- Add `--proto =https --proto-redir =https` to the download curl call.
- Refuse an archive whose final URL (url_effective, already captured) is not
  https on browserstack.com or a subdomain: delete the zip and exit 2, as a
  failed signature check does, before anything is extracted.
- Add a `url` mode to tests/launcher-signature/run_tests.sh and a CI job for it.
- Reword the README paragraph so it matches the launcher code.
- Regenerate the launchers' self-update .sha256 sidecars.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Crash0v3rrid3
Crash0v3rrid3 requested a review from a team as a code owner October 22, 2026 06:19
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited)
  • Review profile: ASSERTIVE
  • Plan: Enterprise
  • Run ID: 9adca200-9c25-449d-81ff-e0866ddba284

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

… checks [DEVA11Y-473]

The DEVA11Y-484 suite serves its fixtures from plain http on 127.0.0.1 through a
curl shim. The launchers' new `--proto '=https'` refused every fixture, and the
host check would refuse the 127.0.0.1 url_effective, so 39 cases failed in
"extraction-guard / shell launchers".

- For a swapped URL, the shim widens --proto/--proto-redir to '=http,https' and
  reports the production redirect target as url_effective. download_binary
  itself is still unmodified.
- The harness loads is_allowed_download_url, and its extraction step greps for
  both transport checks. Removing either one fails the suite (verified by
  mutation).
- Cases 10 and 11 turn each shim relaxation off (SHIM_KEEP_PROTO,
  SHIM_REAL_URL_EFFECTIVE). The launcher must then refuse the plain-http
  download (exit 1) and the off-domain download (exit 2), leaving nothing on
  disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@nmnishant-browserstack nmnishant-browserstack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The https-only flags and the url_effective host check are identical across bash, zsh and fish, and they run before extraction and the signature check. The refused cases (userinfo, port, path, query and fragment tricks) all hold. The sidecars verify, and the url and extraction-guard suites pass locally (54/54, 99/99).

Non-blocking:

  • This conflicts with #50 in spm-smoke-test.yml (both add a job at the same spot), so whichever merges second needs a rebase.
  • curl's exit-1 branch always prints "redirected to a non-HTTPS URL", but curl exits 1 for any unsupported protocol.

@Crash0v3rrid3
Crash0v3rrid3 merged commit cd88cc0 into main Oct 22, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants