Skip to content

fix(lfs): reject unknown SSH LFS sub-verbs to prevent auth bypass - #38008

Merged
bircni merged 5 commits into
go-gitea:mainfrom
bircni:fix/lfs-ssh-subverb-auth-bypass
Jun 6, 2026
Merged

fix(lfs): reject unknown SSH LFS sub-verbs to prevent auth bypass#38008
bircni merged 5 commits into
go-gitea:mainfrom
bircni:fix/lfs-ssh-subverb-auth-bypass

Conversation

@bircni

@bircni bircni commented Jun 5, 2026

Copy link
Copy Markdown
Member

An authenticated SSH user could pass a malformed sub-verb (e.g. git-lfs-authenticate <repo> badverb) so getAccessMode falls through to AccessModeNone (0). The permission check in routers/private/serv.go then evaluates userMode < 0 which is always false, granting a valid LFS JWT for any private repository. The HTTP LFS handler only validates the Op claim on writes, so the token works for downloads.

Validate the sub-verb in runServ before calling getAccessMode and fail fast for anything other than upload/download.

@bircni
bircni requested review from TheFox0x7 and lunny June 5, 2026 21:56
@GiteaBot GiteaBot added lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Jun 5, 2026
An authenticated SSH user could pass a malformed sub-verb
(e.g. `git-lfs-authenticate <repo> badverb`) so getAccessMode falls
through to AccessModeNone (0). The permission check in
routers/private/serv.go then evaluates `userMode < 0` which is always
false, granting a valid LFS JWT for any private repository. The HTTP
LFS handler only validates the Op claim on writes, so the token works
for downloads.

Validate the sub-verb in runServ before calling getAccessMode and fail
fast for anything other than upload/download.
@bircni
bircni force-pushed the fix/lfs-ssh-subverb-auth-bypass branch from 5e548db to 2bc979d Compare June 5, 2026 21:58
@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Jun 5, 2026
@bircni bircni added the outdated/backport/v1.26 This PR should be backported to Gitea 1.26 label Jun 5, 2026
Comment thread cmd/serv.go Outdated
Address review feedback from @lunny: move the LFS sub-verb allow-list
next to IsAllowedVerbForServeLfs in modules/git/cmdverb.go and use it
from runServ.
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Jun 5, 2026

@wxiaoguang wxiaoguang 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 fix and test is not right.

It should not depends on the "panic" or "mode=none" behavior

@GiteaBot GiteaBot added lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged and removed lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. labels Jun 6, 2026
@wxiaoguang
wxiaoguang marked this pull request as draft June 6, 2026 08:47
@bircni

bircni commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

The fix and test is not right.

It should not depends on the "panic" or "mode=none" behavior

@wxiaoguang fixed

@bircni
bircni marked this pull request as ready for review June 6, 2026 13:08
@bircni
bircni requested a review from wxiaoguang June 6, 2026 13:08
@wxiaoguang
wxiaoguang dismissed their stale review June 6, 2026 13:13

dismiss

@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged labels Jun 6, 2026
@wxiaoguang

Copy link
Copy Markdown
Contributor

As a backport-able quick fix it's good enough.

For a complete fix, it needs to refactor the legacy code . People were just keeping adding more and more patches, the the logic became a mess, then lead to various security problems.

image

@bircni

bircni commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

As a backport-able quick fix it's good enough.

For a complete fix, it needs to refactor the legacy code . People were just keeping adding more and more patches, the the logic became a mess, then lead to various security problems.

I saw...

@puni9869

puni9869 commented Jun 6, 2026

Copy link
Copy Markdown
Member

As a quick fix we can go for it including backport solution by wxiaoguang . But complete rewrite need more changes and more time.

Approving it as of now until we are not going to rewrite it.

@bircni
bircni merged commit 4251339 into go-gitea:main Jun 6, 2026
27 checks passed
@GiteaBot GiteaBot added this to the 1.28.0 milestone Jun 6, 2026
@bircni
bircni deleted the fix/lfs-ssh-subverb-auth-bypass branch June 6, 2026 15:44
@puni9869

puni9869 commented Jun 6, 2026

Copy link
Copy Markdown
Member

@bircni Let me know if you are planning a backport of this.

@bircni

bircni commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

As a quick fix we can go for it including backport solution by wxiaoguang . But complete rewrite need more changes and more time.

Approving it as of now until we are not going to rewrite it.

maybe we should write an issue?
or @wxiaoguang already has it on his refactor list? I think he is the only one to do

@GiteaBot GiteaBot added the backport/done All backports for this PR have been created label Jun 6, 2026
@bircni

bircni commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

@bircni Let me know if you are planning a backport of this.

@puni9869 backport open

@puni9869

puni9869 commented Jun 6, 2026

Copy link
Copy Markdown
Member

maybe we should write an issue?

@bircni IMO we can explain the issue in more detail for re-write. It took me significant time to access the problem, actually.

@puni9869

puni9869 commented Jun 6, 2026

Copy link
Copy Markdown
Member

@bircni Let me know if you are planning a backport of this.

@puni9869 backport open

Any open PR? Will be approving.
Please send.

@bircni

bircni commented Jun 6, 2026

Copy link
Copy Markdown
Member Author

@bircni Let me know if you are planning a backport of this.

@puni9869 backport open

Any open PR? Will be approving. Please send.

you already got requested...
#38015

@puni9869

puni9869 commented Jun 6, 2026

Copy link
Copy Markdown
Member

My approval is done. Good to merge the backport as well.

@lunny lunny modified the milestones: 1.28.0, 1.27.0 Jun 6, 2026
eleboucher pushed a commit to eleboucher/apoci that referenced this pull request Jun 21, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [code.gitea.io/gitea](https://github.com/go-gitea/gitea) | `v1.26.2` → `v1.26.3` | ![age](https://developer.mend.io/api/mc/badges/age/go/code.gitea.io%2fgitea/v1.26.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/code.gitea.io%2fgitea/v1.26.2/v1.26.3?slim=true) |

---

### Release Notes

<details>
<summary>go-gitea/gitea (code.gitea.io/gitea)</summary>

### [`v1.26.3`](https://github.com/go-gitea/gitea/releases/tag/v1.26.3)

[Compare Source](go-gitea/gitea@v1.26.2...v1.26.3)

- BREAKING

  - fix(actions)!: require merged PR to bypass fork PR approval gate ([#&#8203;38010](go-gitea/gitea#38010)) ([#&#8203;38041](go-gitea/gitea#38041))

- SECURITY
  - fix(hostmatcher): patch incorrect private list ([#&#8203;38170](go-gitea/gitea#38170)) ([#&#8203;38173](go-gitea/gitea#38173))
  - fix: Various security fixes ([#&#8203;38103](go-gitea/gitea#38103)) ([#&#8203;38151](go-gitea/gitea#38151))
  - fix: Various sec fixes ([#&#8203;38108](go-gitea/gitea#38108)) ([#&#8203;38147](go-gitea/gitea#38147))
  - fix: allow git clone of private repos with anonymous code access ([#&#8203;38074](go-gitea/gitea#38074)) ([#&#8203;38146](go-gitea/gitea#38146))
  - fix(auth): ignore stale OIDC external login links to organizations ([#&#8203;37875](go-gitea/gitea#37875)) ([#&#8203;38141](go-gitea/gitea#38141))
  - fix(hostmatcher): block reserved IP ranges from external/private filters ([#&#8203;38039](go-gitea/gitea#38039)) ([#&#8203;38059](go-gitea/gitea#38059))
  - fix(lfs): require Code-unit access for cross-repo LFS object reuse ([#&#8203;38006](go-gitea/gitea#38006)) ([#&#8203;38050](go-gitea/gitea#38050))
  - fix(lfs): reject unknown SSH LFS sub-verbs to prevent auth bypass ([#&#8203;38008](go-gitea/gitea#38008)) ([#&#8203;38015](go-gitea/gitea#38015))
  - fix: bound CODEOWNERS regex match time ([#&#8203;38011](go-gitea/gitea#38011)) ([#&#8203;38025](go-gitea/gitea#38025))
  - fix: bound debian ParseControlFile to a single control stanza ([#&#8203;38044](go-gitea/gitea#38044)) ([#&#8203;38055](go-gitea/gitea#38055))
  - fix(deps): update module golang.org/x/net to v0.55.0 \[security] ([#&#8203;37813](go-gitea/gitea#37813)) ([#&#8203;37829](go-gitea/gitea#37829))

- API
  - feat(api): add Link header in ListForks ([#&#8203;38052](go-gitea/gitea#38052)) ([#&#8203;38063](go-gitea/gitea#38063))

- BUGFIXES
  - fix: Fix the panic when ssh remote lfs endpoint parsing failure ([#&#8203;38026](go-gitea/gitea#38026)) ([#&#8203;38158](go-gitea/gitea#38158))
  - fix(api): nil pointer panic when filtering tracked times by a non-existent user ([#&#8203;38112](go-gitea/gitea#38112)) ([#&#8203;38115](go-gitea/gitea#38115))
  - fix: keep literal "false" value displayed in workflow\_dispatch choice dropdowns ([#&#8203;38080](go-gitea/gitea#38080)) ([#&#8203;38096](go-gitea/gitea#38096))
  - fix: parse HEAD ref ([#&#8203;38119](go-gitea/gitea#38119))
  - fix: git cmd ([#&#8203;38084](go-gitea/gitea#38084)) ([#&#8203;38087](go-gitea/gitea#38087))
  - fix(releases): generate notes for initial tag ([#&#8203;37697](go-gitea/gitea#37697)) ([#&#8203;37986](go-gitea/gitea#37986))
  - fix(actions): return 404 when job log blob is missing ([#&#8203;38003](go-gitea/gitea#38003)) ([#&#8203;38004](go-gitea/gitea#38004))
  - fix(actions): exclude `workflow_call` from workflow trigger detection ([#&#8203;37894](go-gitea/gitea#37894)) ([#&#8203;37899](go-gitea/gitea#37899))
  - fix(actions): keep action run title clickable when commit subject is a URL ([#&#8203;37867](go-gitea/gitea#37867)) ([#&#8203;37898](go-gitea/gitea#37898))
  - fix(actions): reject workflow\_dispatch for workflows without that trigger ([#&#8203;37660](go-gitea/gitea#37660)) ([#&#8203;37895](go-gitea/gitea#37895))
  - fix(actions): ack re-sent `UpdateLog` finalize idempotently ([#&#8203;37885](go-gitea/gitea#37885)) ([#&#8203;37892](go-gitea/gitea#37892))
  - fix: http content file render ([#&#8203;37850](go-gitea/gitea#37850)) ([#&#8203;37856](go-gitea/gitea#37856))
  - fix(issues): clear stale ReviewTypeRequest when submitting pending review ([#&#8203;37809](go-gitea/gitea#37809)) ([#&#8203;37815](go-gitea/gitea#37815))
  - fix: Fix issue target branch selection for non-collaborators ([#&#8203;36916](go-gitea/gitea#36916)) ([#&#8203;38164](go-gitea/gitea#38164))

- BUILD
  - fix(deps): update `@playwright/test` to 1.60.0 ([#&#8203;38144](go-gitea/gitea#38144))
  - ci: add `tools/ci-tools.ts` for the PR labeler workflow ([#&#8203;37831](go-gitea/gitea#37831))
  - fix(build): swagger css import ([#&#8203;37801](go-gitea/gitea#37801)) ([#&#8203;37803](go-gitea/gitea#37803))

Instances on **[Gitea Cloud](https://cloud.gitea.com)** will be automatically upgraded to this version during the specified maintenance window.

</details>

---

### Configuration

📅 **Schedule**: 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 PR becomes conflicted, or you tick the rebase/retry checkbox.

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

---

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

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/apoci/pulls/89
eleboucher pushed a commit to eleboucher/apoci that referenced this pull request Jun 22, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [code.gitea.io/gitea](https://github.com/go-gitea/gitea) | `v1.26.2` → `v1.26.4` | ![age](https://developer.mend.io/api/mc/badges/age/go/code.gitea.io%2fgitea/v1.26.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/code.gitea.io%2fgitea/v1.26.2/v1.26.4?slim=true) |

---

### Release Notes

<details>
<summary>go-gitea/gitea (code.gitea.io/gitea)</summary>

### [`v1.26.4`](https://github.com/go-gitea/gitea/releases/tag/v1.26.4)

[Compare Source](go-gitea/gitea@v1.26.3...v1.26.4)

- SECURITY
  - fix(auth): do not auto-reactivate disabled users on OAuth2 callback ([#&#8203;38009](go-gitea/gitea#38009)) ([#&#8203;38183](go-gitea/gitea#38183))

- BUGFIXES
  - fix: walk git log context error handling ([#&#8203;38182](go-gitea/gitea#38182)) ([#&#8203;38185](go-gitea/gitea#38185))

Instances on **[Gitea Cloud](https://cloud.gitea.com)** will be automatically upgraded to this version during the specified maintenance window.

### [`v1.26.3`](https://github.com/go-gitea/gitea/releases/tag/v1.26.3)

[Compare Source](go-gitea/gitea@v1.26.2...v1.26.3)

> \[!WARNING]
> **Please upgrade to 1.26.4 directly.** A regression in this release can cause **"context deadline exceeded"** errors when opening any repository's code pages ([#&#8203;38177](go-gitea/gitea#38177)). Please hold off on upgrading until a fix is released.

- BREAKING

  - fix(actions)!: require merged PR to bypass fork PR approval gate ([#&#8203;38010](go-gitea/gitea#38010)) ([#&#8203;38041](go-gitea/gitea#38041))

- SECURITY
  - fix(hostmatcher): patch incorrect private list ([#&#8203;38170](go-gitea/gitea#38170)) ([#&#8203;38173](go-gitea/gitea#38173))
  - fix: Various security fixes ([#&#8203;38103](go-gitea/gitea#38103)) ([#&#8203;38151](go-gitea/gitea#38151))
  - fix: Various sec fixes ([#&#8203;38108](go-gitea/gitea#38108)) ([#&#8203;38147](go-gitea/gitea#38147))
  - fix: allow git clone of private repos with anonymous code access ([#&#8203;38074](go-gitea/gitea#38074)) ([#&#8203;38146](go-gitea/gitea#38146))
  - fix(auth): ignore stale OIDC external login links to organizations ([#&#8203;37875](go-gitea/gitea#37875)) ([#&#8203;38141](go-gitea/gitea#38141))
  - fix(hostmatcher): block reserved IP ranges from external/private filters ([#&#8203;38039](go-gitea/gitea#38039)) ([#&#8203;38059](go-gitea/gitea#38059))
  - fix(lfs): require Code-unit access for cross-repo LFS object reuse ([#&#8203;38006](go-gitea/gitea#38006)) ([#&#8203;38050](go-gitea/gitea#38050))
  - fix(lfs): reject unknown SSH LFS sub-verbs to prevent auth bypass ([#&#8203;38008](go-gitea/gitea#38008)) ([#&#8203;38015](go-gitea/gitea#38015))
  - fix: bound CODEOWNERS regex match time ([#&#8203;38011](go-gitea/gitea#38011)) ([#&#8203;38025](go-gitea/gitea#38025))
  - fix: bound debian ParseControlFile to a single control stanza ([#&#8203;38044](go-gitea/gitea#38044)) ([#&#8203;38055](go-gitea/gitea#38055))
  - fix(deps): update module golang.org/x/net to v0.55.0 \[security] ([#&#8203;37813](go-gitea/gitea#37813)) ([#&#8203;37829](go-gitea/gitea#37829))

- API
  - feat(api): add Link header in ListForks ([#&#8203;38052](go-gitea/gitea#38052)) ([#&#8203;38063](go-gitea/gitea#38063))

- BUGFIXES
  - fix: Fix the panic when ssh remote lfs endpoint parsing failure ([#&#8203;38026](go-gitea/gitea#38026)) ([#&#8203;38158](go-gitea/gitea#38158))
  - fix(api): nil pointer panic when filtering tracked times by a non-existent user ([#&#8203;38112](go-gitea/gitea#38112)) ([#&#8203;38115](go-gitea/gitea#38115))
  - fix: keep literal "false" value displayed in workflow\_dispatch choice dropdowns ([#&#8203;38080](go-gitea/gitea#38080)) ([#&#8203;38096](go-gitea/gitea#38096))
  - fix: parse HEAD ref ([#&#8203;38119](go-gitea/gitea#38119))
  - fix: git cmd ([#&#8203;38084](go-gitea/gitea#38084)) ([#&#8203;38087](go-gitea/gitea#38087))
  - fix(releases): generate notes for initial tag ([#&#8203;37697](go-gitea/gitea#37697)) ([#&#8203;37986](go-gitea/gitea#37986))
  - fix(actions): return 404 when job log blob is missing ([#&#8203;38003](go-gitea/gitea#38003)) ([#&#8203;38004](go-gitea/gitea#38004))
  - fix(actions): exclude `workflow_call` from workflow trigger detection ([#&#8203;37894](go-gitea/gitea#37894)) ([#&#8203;37899](go-gitea/gitea#37899))
  - fix(actions): keep action run title clickable when commit subject is a URL ([#&#8203;37867](go-gitea/gitea#37867)) ([#&#8203;37898](go-gitea/gitea#37898))
  - fix(actions): reject workflow\_dispatch for workflows without that trigger ([#&#8203;37660](go-gitea/gitea#37660)) ([#&#8203;37895](go-gitea/gitea#37895))
  - fix(actions): ack re-sent `UpdateLog` finalize idempotently ([#&#8203;37885](go-gitea/gitea#37885)) ([#&#8203;37892](go-gitea/gitea#37892))
  - fix: http content file render ([#&#8203;37850](go-gitea/gitea#37850)) ([#&#8203;37856](go-gitea/gitea#37856))
  - fix(issues): clear stale ReviewTypeRequest when submitting pending review ([#&#8203;37809](go-gitea/gitea#37809)) ([#&#8203;37815](go-gitea/gitea#37815))
  - fix: Fix issue target branch selection for non-collaborators ([#&#8203;36916](go-gitea/gitea#36916)) ([#&#8203;38164](go-gitea/gitea#38164))

- BUILD
  - fix(deps): update `@playwright/test` to 1.60.0 ([#&#8203;38144](go-gitea/gitea#38144))
  - ci: add `tools/ci-tools.ts` for the PR labeler workflow ([#&#8203;37831](go-gitea/gitea#37831))
  - fix(build): swagger css import ([#&#8203;37801](go-gitea/gitea#37801)) ([#&#8203;37803](go-gitea/gitea#37803))

Instances on **[Gitea Cloud](https://cloud.gitea.com)** will be automatically upgraded to this version during the specified maintenance window.

</details>

---

### Configuration

📅 **Schedule**: 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 PR becomes conflicted, or you tick the rebase/retry checkbox.

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

---

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

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/apoci/pulls/95
zeekay pushed a commit to hanzoai/forge that referenced this pull request Jul 26, 2026
…-gitea#38008)

An authenticated SSH user could pass a malformed sub-verb (e.g.
`git-lfs-authenticate <repo> badverb`) so getAccessMode falls through to
AccessModeNone (0). The permission check in routers/private/serv.go then
evaluates `userMode < 0` which is always false, granting a valid LFS JWT
for any private repository. The HTTP LFS handler only validates the Op
claim on writes, so the token works for downloads.

Validate the sub-verb in runServ before calling getAccessMode and fail
fast for anything other than upload/download.
@go-gitea go-gitea locked as resolved and limited conversation to collaborators Sep 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

backport/done All backports for this PR have been created lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. outdated/backport/v1.26 This PR should be backported to Gitea 1.26 type/bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants