Skip to content

Allow downloading release assets without authentication#13723

Merged
BagToad merged 3 commits into
trunkfrom
bagtoad/disable-auth-check-release-clone
Jun 26, 2026
Merged

Allow downloading release assets without authentication#13723
BagToad merged 3 commits into
trunkfrom
bagtoad/disable-auth-check-release-clone

Conversation

@BagToad

@BagToad BagToad commented Jun 24, 2026

Copy link
Copy Markdown
Member

Fixes #2680

Description

gh release download now works without authentication against public repositories, matching gh extension install. A token is still used when one is present.

Key Points

This change has three parts, from simplest to subtlest:

  • Why drop the auth gate - the login requirement is unnecessary for public downloads.
  • Fixing the by-tag race to be success oriented - an anonymous draft-lookup 403 no longer masks a release the REST lookup found.
  • Honoring the gate under repo override - the cobra plumbing fix that makes the dropped gate apply to download.

Why drop the auth gate

Release endpoints for public repositories are readable anonymously over REST, so the login gate is unnecessary. Removing it is the same one-line cmdutil.DisableAuthCheck change that #13176 made for gh extension install.

The same logic here applies as with gh extension install:

What this says is that extension installation is going to happen unauthenticated whether we make it easier or not. We might as well make it easier and keep people installing through our core commands.

In gh release download - people are going to just curl the endpoint directly, and that degrades their experience since we provide a lot of abstractions over the API that make gh much more than a curl/gh api call.

But I'll broaden the conversation - we got feedback that folks are using gh release download more in CI and they want to secure their CI by not providing gh a token unless it's necessary. If you're downloading a public asset, that's not necessary and we force you to use a larger than necessary token scope.

Fixing the by-tag race to be success oriented

The by-tag path creates problems that make this deviate a bit from gh ext install - that's one reason why this diff is a bit bigger. FetchRelease races a published-release REST lookup against a draft-release GraphQL lookup. GraphQL rejects anonymous requests, so the draft lookup returns a 403. The old selector returned the first result unless it was exactly ErrReleaseNotFound, so that 403 could win the race and mask a release the REST lookup found.

The selector now prefers a found release and only errors when both lookups fail. We used to return whichever result arrived first, treating only a not-found error as a reason to wait for the second:

// Before - a non-not-found error from either lookup wins
res := <-results
if errors.Is(res.error, ErrReleaseNotFound) {
    res = <-results
    cancel()
} else {
    cancel()
    <-results // drain the channel
}
return res.release, res.error

Now we take a success from either lookup, and only surface an error when both fail:

// Now - a found release wins; an error needs both lookups to fail
first := <-results
if first.error == nil {
    cancel()
    <-results // drain the channel
    return first.release, nil
}

second := <-results
cancel()
if second.error == nil {
    return second.release, nil
}
if errors.Is(second.error, ErrReleaseNotFound) {
    return nil, second.error
}
return nil, first.error

This also stops a transient draft-lookup failure from masking a real release for authenticated users.

Honoring the gate under repo override

Removing the gate did not take effect on its own. release enables the -R/--repo override, and that installs a PersistentPreRunE on the release command. cobra runs only the nearest such hook walking up from the command you invoked, so the override hook shadows the root auth gate. The override re-runs the nearest ancestor hook to make up for that, but it handed the ancestor to the hook as the command, so the auth gate judged release instead of download and never saw download's DisableAuthCheck.

The fix keeps the invoked command and passes that leaf up, the node cobra would have judged if the repo override didn't muck with it:

// Before - climbs by reassigning cmd, so the gate is judged against the ancestor
for cmd.HasParent() {
    cmd = cmd.Parent()
    if cmd.PersistentPreRunE != nil {
        return cmd.PersistentPreRunE(cmd, args)
    }
}

// Now - keep the invoked leaf and pass it up
for p := overrideCmd.Parent(); p != nil; p = p.Parent() {
    if p.PersistentPreRunE != nil {
        return p.PersistentPreRunE(cmd, args)
    }
}

This mirrors cobra's own execute loop, which walks the parents but always hands them the leaf command. cobra's EnableTraverseRunHooks global would run the whole chain for us and maybe is a better long term fix, but it is process-wide and would change pre-run behavior for every command, so I'm not touching it here.

I guarded this with an integration test, since the bug only surfaces with the pieces wired together. Test_EnableRepoOverride_authCheckIntegration builds a root gate, a repo-override parent, and a leaf, then asserts the gate judged the leaf: opting out with DisableAuthCheck skips the check, otherwise the gate still runs. It lives in cmdutil beside the helper, since the coupling is the helper's, not any one command's.

Notes for reviewers

Three atomic commits:

  • fix(release): don't let a failed draft lookup mask a found release changes the selector in FetchRelease and adds a regression case to the existing Test_downloadRun table.
  • feat(release): allow download without authentication removes the gate in the download command.
  • fix(cmdutil): honor DisableAuthCheck under repo-override parents fixes the repo-override helper so the gate removal actually takes effect, and adds an integration test that pins the coupling between repo override and the root auth gate.

Additional Context

Copilot AI review requested due to automatic review settings June 24, 2026 23:39
@BagToad
BagToad requested review from a team as code owners June 24, 2026 23:39
@BagToad
BagToad requested a review from babakks June 24, 2026 23:39

Copilot AI 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.

Pull request overview

This pull request updates gh release download to work without requiring authentication for public repositories, and adjusts release lookup behavior to avoid a failed draft (GraphQL) lookup masking a successful published (REST) lookup.

Changes:

  • Bypass the pre-execution login gate for gh release download.
  • Update shared.FetchRelease selection logic to prefer a successful lookup result over an error from the other concurrent lookup.
  • Add a regression test ensuring an unauthorized draft lookup does not prevent downloading a published release by tag.
Show a summary per file
File Description
pkg/cmd/release/shared/fetch.go Changes concurrent published/draft selection logic for by-tag release lookup.
pkg/cmd/release/download/download.go Disables the auth check gate so downloads can proceed unauthenticated.
pkg/cmd/release/download/download_test.go Adds coverage for the “draft lookup unauthorized” regression scenario.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment thread pkg/cmd/release/shared/fetch.go
@BagToad

BagToad commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Switching to draft because I still want to do this, but it turns out there's a bug with repo-override.

The auth check lives in the root command's PersistentPreRunE, and it decides whether to require login by inspecting the command that was run. That is how a subcommand opts out, which is what this PR does for download. release enables repo-override, which replaces that root hook and then re-runs the auth check, but against the wrong command. So download and its opt-out are never seen, and login is still required.

I'll pick the fix back up shortly.

@BagToad
BagToad marked this pull request as draft June 25, 2026 05:37

@andyfeller andyfeller 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.

The only suggestion I'd like to offer is making this abundantly clear in the command description with very huge warning about the potential impact of using unauthenticated release assets with a link to GH docs page + section on unauthentiated rate limits

@BagToad
BagToad marked this pull request as ready for review June 25, 2026 21:34

@babakks babakks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM as we also reviewed this PR in sync.

Comment on lines +233 to +236
"assets": [
{ "name": "linux.tgz", "size": 56,
"url": "https://api.github.com/assets/5678" }
],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: let's tidy this part.

Comment on lines +12 to +27
// executeParentHook re-runs the nearest ancestor's persistent pre-run hook,
// which the hook installed by EnableRepoOverride would otherwise shadow. By
// default cobra runs only the nearest PersistentPreRunE found walking up from
// the invoked command, so without this the nearest ancestor hook, such as the
// root auth gate, would never run for a repo-override command.
//
// That ancestor hook receives the invoked leaf cmd, not the ancestor, matching
// how cobra passes the leaf to every persistent hook:
// https://github.com/spf13/cobra/blob/v1.10.2/command.go#L984-L986
//
// cobra's EnableTraverseRunHooks global is the native equivalent and runs the
// whole root-to-leaf chain for us, but it is global. Enabling it would change
// pre-run behavior for every command: double-running the parents that issue
// develop and EnableRepoOverride re-run by hand, and un-suppressing the root
// gate that agent-task and skills intentionally shadow.
func executeParentHook(overrideCmd, cmd *cobra.Command, args []string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great catch here! 🙏

@babakks

babakks commented Jun 25, 2026

Copy link
Copy Markdown
Member

The only suggestion I'd like to offer is making this abundantly clear in the command description with very huge warning about the potential impact of using unauthenticated release assets with a link to GH docs page + section on unauthentiated rate limits

@BagToad and I talked about this, and my take is to just wait for some feedback regarding docs.

BagToad and others added 3 commits June 26, 2026 15:00
FetchRelease races a published-release REST lookup against a draft-release
GraphQL lookup, and returned the first result unless it was ErrReleaseNotFound.
A failing draft lookup, such as a 403 when unauthenticated, could mask a release
the published lookup found. Prefer a found release and only error when both fail.

Co-authored-by: Copilot <[email protected]>
Downloading assets from a public repository's release works unauthenticated
over REST, so drop the login gate. A token is still used when present.

Co-authored-by: Copilot <[email protected]>
EnableRepoOverride's hook shadows the root auth gate, then re-runs the nearest ancestor hook to restore it. That re-run passed the ancestor as the command, so the gate judged the wrong node and ignored a leaf's DisableAuthCheck. Pass the invoked leaf instead, as cobra does for every persistent hook.

Co-authored-by: Copilot <[email protected]>
@BagToad
BagToad force-pushed the bagtoad/disable-auth-check-release-clone branch from ab568b6 to 7b681a4 Compare June 26, 2026 21:25
@BagToad
BagToad enabled auto-merge June 26, 2026 21:29
@BagToad
BagToad merged commit 71fb4f5 into trunk Jun 26, 2026
18 checks passed
@BagToad
BagToad deleted the bagtoad/disable-auth-check-release-clone branch June 26, 2026 21:35
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Jul 9, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [cli/cli](https://github.com/cli/cli) | minor | `v2.94.0` → `v2.96.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>cli/cli (cli/cli)</summary>

### [`v2.96.0`](https://github.com/cli/cli/releases/tag/v2.96.0): GitHub CLI 2.96.0

[Compare Source](cli/cli@v2.95.0...v2.96.0)

#### Security

A security vulnerability has been identified, and fixed, that could allow command execution on a user's computer when connecting to a malicious Codespace via `gh codespace jupyter`.

Users of `gh codespace jupyter` are advised to update gh to version v2.96.0 as soon as possible.

For more information see: <GHSA-8cg3-r6g9-fpg2>

#### Download release assets without authentication

`gh release download` now works against public repositories without authentication, matching `gh extension install`. A token is still used when one is present:

```shell

# Download assets from a public repository, no login required
gh release download v2.96.0 --repo cli/cli
```

#### What's Changed

##### ✨ Features

- Allow `gh release download` without authentication on public repositories by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13723](cli/cli#13723)
- Detect additional third-party coding agents by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13722](cli/cli#13722)
- Support `antigravity-cli` and `antigravity2.0` in `gh skill` by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13784](cli/cli#13784)

##### 🐛 Fixes

- fix: show checks summary when all checks were cancelled by [@&#8203;s3onghyun](https://github.com/s3onghyun) in [#&#8203;13679](cli/cli#13679)
- fix(skills): install universal agent to `~/.agents/skills` by [@&#8203;toller892](https://github.com/toller892) in [#&#8203;13681](cli/cli#13681)
- fix(skills): honor `--dir` without agent prompt by [@&#8203;happysnaker](https://github.com/happysnaker) in [#&#8203;13766](cli/cli#13766)
- Fix concurrent map writes in codespace port forwarding by [@&#8203;williammartin](https://github.com/williammartin) in [#&#8203;13313](cli/cli#13313)
- Use `int64` for GitHub database IDs by [@&#8203;williammartin](https://github.com/williammartin) in [#&#8203;13403](cli/cli#13403)

##### 📚 Docs & Chores

- Pin reusable triage workflows to a commit SHA by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13705](cli/cli#13705)
- Add security disclosure guidance to `AGENTS.md` by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13720](cli/cli#13720)
- Clarify `--clone` boolean flag behaviour in `gh repo fork` help by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13786](cli/cli#13786)
- Fix flaky `TestHuhPrompterMultiSelectWithSearchPersistence` on slow architectures by [@&#8203;pdostal](https://github.com/pdostal) in [#&#8203;13675](cli/cli#13675)
- docs(search): add examples for multiple qualifiers by [@&#8203;happysnaker](https://github.com/happysnaker) in [#&#8203;13756](cli/cli#13756)
- docs: fix broken anchor link in release-process-deep-dive by [@&#8203;patrickwehbe](https://github.com/patrickwehbe) in [#&#8203;13688](cli/cli#13688)
- docs: fix broken install command and link/grammar errors by [@&#8203;patrickwehbe](https://github.com/patrickwehbe) in [#&#8203;13690](cli/cli#13690)
- docs: fix duplicated word in primer README by [@&#8203;s3onghyun](https://github.com/s3onghyun) in [#&#8203;13677](cli/cli#13677)

##### :dependabot: Dependencies

- chore(deps): bump github.com/microsoft/dev-tunnels from 0.1.19 to 0.1.27 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13708](cli/cli#13708)
- chore(deps): bump actions/checkout from 6.0.3 to 7.0.0 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13703](cli/cli#13703)
- chore(deps): bump github.com/google/go-containerregistry from 0.21.6 to 0.21.7 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13702](cli/cli#13702)
- chore(deps): bump actions/setup-go from 6.4.0 to 6.5.0 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13740](cli/cli#13740)
- chore(deps): bump actions/attest from 4.1.0 to 4.1.1 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13754](cli/cli#13754)
- chore(deps): bump goreleaser/goreleaser-action from 7.2.2 to 7.2.3 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13759](cli/cli#13759)
- chore(deps): bump golangci/golangci-lint-action from 9.2.1 to 9.3.0 by [@&#8203;dependabot](https://github.com/dependabot) in [#&#8203;13779](cli/cli#13779)

#### New Contributors

- [@&#8203;patrickwehbe](https://github.com/patrickwehbe) made their first contribution in [#&#8203;13688](cli/cli#13688)
- [@&#8203;s3onghyun](https://github.com/s3onghyun) made their first contribution in [#&#8203;13679](cli/cli#13679)
- [@&#8203;toller892](https://github.com/toller892) made their first contribution in [#&#8203;13681](cli/cli#13681)
- [@&#8203;happysnaker](https://github.com/happysnaker) made their first contribution in [#&#8203;13756](cli/cli#13756)

**Full Changelog**: <cli/cli@v2.95.0...v2.96.0>

### [`v2.95.0`](https://github.com/cli/cli/releases/tag/v2.95.0): GitHub CLI 2.95.0

[Compare Source](cli/cli@v2.94.0...v2.95.0)

#### Read repository files and directories with `gh repo read-file` and `gh repo read-dir`

Two new preview commands read repository contents without cloning:

```shell

# Read a single file to stdout
gh repo read-file README.md --repo cli/cli

# Read from a specific branch, tag, or commit
gh repo read-file go.mod --ref v2.94.0 --repo cli/cli

# Write a file to disk (use --clobber to overwrite)
gh repo read-file README.md --output ./README.md --repo cli/cli

# List the entries in a directory
gh repo read-dir script --repo cli/cli
```

Both commands default to the repository's default branch, accept `--ref` to target any branch, tag, or commit, and support `--json`, `--jq`, and `--template` for scripting. This makes it easy for agents and automation to inspect a repo without a full checkout.

> \[!NOTE]
> `gh repo read-file` and `gh repo read-dir` are in preview and subject to change without notice.

#### What's Changed

##### ✨ Features

- feat: add `repo read-file` and `repo read-dir` by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;13580](cli/cli#13580)
- feat(skills): list available skills when install runs non-interactively by [@&#8203;SamMorrowDrums](https://github.com/SamMorrowDrums) in [#&#8203;13548](cli/cli#13548)
- Support custom CLAUDE\_CONFIG\_DIR in install by [@&#8203;tommaso-moro](https://github.com/tommaso-moro) in [#&#8203;13523](cli/cli#13523)

##### 🐛 Fixes

- fix(skills): stage updates in a temp dir and swap in-place by [@&#8203;SamMorrowDrums](https://github.com/SamMorrowDrums) in [#&#8203;13449](cli/cli#13449)

##### 📚 Docs & Chores

- Make filtering by bot authors more discoverable by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;13642](cli/cli#13642)
- docs(discussion): polish help docs by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;13632](cli/cli#13632)
- Bump Go in devcontainer by [@&#8203;spenserblack](https://github.com/spenserblack) in [#&#8203;13674](cli/cli#13674)

##### :dependabot: Dependencies

- chore(deps): bump golang.org/x/text from 0.37.0 to 0.38.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13640](cli/cli#13640)
- chore(deps): bump charm.land/lipgloss/v2 from 2.0.3 to 2.0.4 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13663](cli/cli#13663)
- chore(deps): bump golang.org/x/term from 0.43.0 to 0.44.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13661](cli/cli#13661)
- chore(deps): bump github/codeql-action from 4.36.1 to 4.36.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13619](cli/cli#13619)
- chore(deps): bump github.com/sigstore/sigstore-go from 1.1.4 to 1.2.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13662](cli/cli#13662)
- chore(deps): bump golang.org/x/crypto from 0.52.0 to 0.53.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;13641](cli/cli#13641)

**Full Changelog**: <cli/cli@v2.94.0...v2.95.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMjcuMSIsInVwZGF0ZWRJblZlciI6IjQzLjIzMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
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.

Allow certain requests to be unauthenticated.

4 participants