chore: flag suspicious OwnerID comparisons (#12184)

Resources in Forgejo can also be owned by predefined system users like Ghost or Forgejo Actions. Those have negative user IDs, for example, -2 in the case of Forgejo Actions. `OwnerID` checks oftentimes do not take these users into account, because their existence and how they work isn't well known. A [semgrep](https://semgrep.dev/) check is added that flags such suspicious `OwnerID` checks.

See https://codeberg.org/forgejo/forgejo/pulls/12144 for background.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12184
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
This commit is contained in:
Andreas Ahlenstorf 2026-04-19 04:24:09 +02:00 committed by Mathieu Fenniak
parent 99299a5685
commit 178a0a25f8
13 changed files with 59 additions and 9 deletions

View file

@ -74,7 +74,7 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.CommitSHA != "" {

View file

@ -85,7 +85,7 @@ func (opts FindRunOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.WorkflowID != "" {

View file

@ -152,6 +152,7 @@ func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
// LoadAttributes loads the attributes of the runner
func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
if r.OwnerID > 0 {
var user user_model.User
has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
@ -214,7 +215,7 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
}
cond = cond.And(c)
} else if opts.OwnerID > 0 { // OwnerID is ignored if RepoID is set
} else if opts.OwnerID != 0 { // OwnerID is ignored if RepoID is set
c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
if opts.WithVisible {
c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})

View file

@ -28,6 +28,7 @@ func (runners RunnerList) LoadOwners(ctx context.Context) error {
return err
}
for _, runner := range runners {
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
if runner.OwnerID > 0 && runner.Owner == nil {
runner.Owner = users[runner.OwnerID]
}

View file

@ -116,7 +116,7 @@ func (opts FindScheduleOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}

View file

@ -65,7 +65,7 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.CommitSHA != "" {

View file

@ -81,7 +81,7 @@ func (opts FindGPGKeyOptions) ToConds() builder.Cond {
cond = cond.And(builder.Eq{"primary_key_id": ""})
}
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.KeyID != "" {

View file

@ -190,7 +190,7 @@ type FindPublicKeyOptions struct {
func (opts FindPublicKeyOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.Fingerprint != "" {

View file

@ -136,6 +136,7 @@ func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { /
// Link returns the project's relative URL.
func (p *Project) Link(ctx context.Context) string {
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (system users are not stored in the database)
if p.OwnerID > 0 {
err := p.LoadOwner(ctx)
if err != nil {
@ -224,7 +225,7 @@ func (opts SearchOptions) ToConds() builder.Cond {
if opts.Type > 0 {
cond = cond.And(builder.Eq{"type": opts.Type})
}
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}

View file

@ -893,6 +893,7 @@ type CountRepositoryOptions struct {
func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64, error) {
sess := db.GetEngine(ctx).Where("id > 0")
// nosemgrep: forgejo-logic-suspicious-OwnerID-check (repositories cannot be owned by system users)
if opts.OwnerID > 0 {
sess.And("owner_id = ?", opts.OwnerID)
}