Skip to content

fix(security): fail-closed CLI checksum + https/browserstack.com redirect allowlist [DEVA11Y-473] - #46

Draft
Crash0v3rrid3 wants to merge 2 commits into
mainfrom
fix/DEVA11Y-473-fail-closed-checksum
Draft

Crash0v3rrid3 wants to merge 2 commits into
mainfrom
fix/DEVA11Y-473-fail-closed-checksum

Conversation

@Crash0v3rrid3

@Crash0v3rrid3 Crash0v3rrid3 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The Security retest of DEVA11Y-473 at 806f98cb reopened the ticket for two reasons:

  1. Checksum check lets downloads through when it can't check them: verifyArchiveChecksum aborted only on a mismatch. If the .sha256 sidecar was missing, unreachable or malformed, the archive was extracted and executed unverified.
  2. Redirects not validated: followRedirects() / download() accepted any redirect target, so an http:// (or off-domain) hop would be followed. URLSession.shared also auto-follows redirects, so the bad hop happened before our code saw the URL.

What

Commit 1: f882c50 redirect allowlist (independent; can be cherry-picked and shipped on its own)

  • New downloadSession with a URLSessionTaskDelegate that vetoes any redirect hop that isn't https on browserstack.com / *.browserstack.com.
  • followRedirects, followWithGet and download() use it, and they re-check the vetoed Location and the final response.url as a backstop.
  • The initial URL is still governed by parseOverride() (HTTPS only), so an HTTPS override to a mirror still works as long as it doesn't redirect off-domain.

Commit 2: 2e23e29 fail-closed checksum

  • Every branch of verifyArchiveChecksum that used to log a warning and return now throws before extraction: sidecar can't be derived, non-2xx, fetch error, not a 64-char hex digest. The sidecar fetch also goes through the guarded session.
  • README updated.

⚠️ Merge gate: do not merge until SDK assets publishes sidecars

As of 2026-10-16, sdk-assets.browserstack.com/binary-{macos-arm64,macos-x64,linux-x64,linux-arm64,alpine-x64,win-x64}-1.62.0.zip.sha256 all return 403. With commit 2 merged, every plugin run would fail until those files exist. The spm-smoke-test job will go red for exactly this reason.

Testing (local, macOS arm64, real network)

Scenario Result
Default path (api.browserstack.com → sdk-assets), after commit 1 only ✅ downloads and runs the CLI
Override → GitHub archive (redirects to codeload.github.com) ✅ Refusing to follow redirect … only https:// URLs on browserstack.com are allowed.
Override → httpbin redirect to http://sdk-assets.browserstack.com/… ✅ rejected (scheme downgrade)
Default path, after commit 2 (sidecar 403) ✅ No BrowserStack CLI checksum available … (HTTP 403); refusing to use the downloaded binary.
Override → URL whose .sha256 returns a 200 JSON body ✅ Malformed BrowserStack CLI checksum …
Override → raw.githubusercontent.com/…/scripts/bash/cli.sh (valid published sidecar) ✅ checksum passes; the run reaches extraction (bsdtar then fails, as expected, because the file isn't an archive)

The mismatch path is unchanged. The plugin isn't unit-testable (private functions inside a command-plugin target), so these were exercised end-to-end via swift package … scan.

Out of scope / follow-ups

  • The shell launchers (scripts/{bash,zsh,fish}/cli.sh → verify_binary_integrity) have the same fail-open behaviour. That code is tracked under DEVA11Y-474 and should get the same treatment once the sidecars exist.
  • On Linux Foundation the redirect delegate may not be consulted for async calls. The final-URL backstop still rejects the result before it's used, but an intermediate hop could still be contacted.

🤖 Generated with Claude Code

Crash0v3rrid3 and others added 2 commits October 16, 2026 15:54
…VA11Y-473]

followRedirects()/followWithGet() returned any Location target, and
URLSession.shared auto-followed every hop, so an https -> http (or
off-domain) redirect would fetch the CLI archive in cleartext or from an
arbitrary host before our code saw the URL.

Route the HEAD/GET resolution and the archive download through a session
whose delegate vetoes any hop that is not https on browserstack.com or a
subdomain, and re-check the vetoed Location / final response URL after
each request as a backstop. The initial URL is still governed by
parseOverride(), so an HTTPS override to a mirror keeps working as long
as it does not redirect off-domain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…[DEVA11Y-473]

verifyArchiveChecksum aborted only on a digest mismatch. A sidecar that
could not be derived, returned non-2xx, failed to fetch, or was not a
64-char hex digest let the archive be extracted and executed unverified,
so blocking or corrupting the .sha256 bypassed the check entirely
(Security retest of DEVA11Y-473 @806f98cb).

Every one of those paths now throws before extraction. The sidecar fetch
also goes through the redirect-guarded session. README updated to
describe the plugin's fail-closed behaviour.

MERGE GATE: sdk-assets.browserstack.com does not publish <asset>.sha256
yet (403 for all six platform binaries as of 2026-10-16), so this commit
makes every plugin run fail until SDK assets ships the sidecars.

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

coderabbitai Bot commented Oct 16, 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: 5e7e0140-7d41-4da9-8524-be2e576f9066

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.

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.

1 participant