Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a1d9a9b
updated go deps and the min Go version in the README
ganigeorgiev Oct 12, 2024
e92d3cc
updated jsvm types
ganigeorgiev Oct 18, 2024
cbc88d7
backported some of the v0.23.0 form validators, fixes and tests
ganigeorgiev Oct 18, 2024
3786e08
added Instagram OAuth2 provider deprecation log
ganigeorgiev Oct 18, 2024
e18ea88
updated ghupdate to prevent downloading unnecessary v0.23+ since it c…
ganigeorgiev Oct 18, 2024
cffbe83
backported JSVM toString
ganigeorgiev Oct 18, 2024
3dd644c
updated changelog and min Go version
ganigeorgiev Oct 18, 2024
2ff2cd8
updated ui/dist
ganigeorgiev Oct 18, 2024
58a0b28
updated aws-sdk-go-v2 and moved the trimmed s3 driver in internal to …
ganigeorgiev Oct 18, 2024
c4b1e2d
updated changelog
ganigeorgiev Oct 18, 2024
0b30435
updated go.mod
ganigeorgiev Oct 18, 2024
cd14714
updated npm deps
ganigeorgiev Nov 3, 2024
caaac6b
added topic length validator to avoid relying solely on the body limi…
ganigeorgiev Nov 3, 2024
060a659
[#5789] updated the hooks watcher to account for the case when hooksD…
ganigeorgiev Nov 3, 2024
75d1c0b
updated changelog and jsvm types
ganigeorgiev Nov 3, 2024
08793ee
fixed error message typo
ganigeorgiev Nov 3, 2024
26ef0c6
registered a default http.Server.ErrorLog handler
ganigeorgiev Nov 3, 2024
9a9b667
[#5845] delete new uploaded record files in case of DB persist error
ganigeorgiev Nov 12, 2024
8144ad1
updated ui/dist
ganigeorgiev Nov 12, 2024
0d34c00
added randomized trottle for failed list filter requests
ganigeorgiev Nov 14, 2024
dbb0b6b
[#5861] refresh the old collections in the UI import page on successf…
ganigeorgiev Nov 14, 2024
671a6a3
added manual WAL checkpoints before creating the zip backup to minimi…
ganigeorgiev Nov 17, 2024
51c083d
[#5898] instead of unregister, unset the realtime client auth state o…
ganigeorgiev Nov 19, 2024
b83f924
updated changelog
ganigeorgiev Nov 19, 2024
eb78f59
(backported) renew the superuser file token cache when clicking on th…
ganigeorgiev Dec 18, 2024
0c966ab
(backported) upgraded default modernc.org driver to fix arm64 disk io…
ganigeorgiev Dec 18, 2024
fbb8fca
bumped golang.org/x/net to 0.33.0
ganigeorgiev Dec 19, 2024
ae36612
(backported) fixed fields extraction for view queries with milti-leve…
ganigeorgiev Jan 18, 2025
18dcc13
updated ui/dist and min GitHub Go release action version
ganigeorgiev Jan 18, 2025
7ec7ccc
set the current working directory as a default goja script path when …
ganigeorgiev Feb 18, 2025
f999037
updated golang-jwt
ganigeorgiev Mar 22, 2025
3f14a6e
fixed and normalized logs error serialization
ganigeorgiev Mar 23, 2025
2ca9b63
[#6657] allow OIDC email_verified to be int or boolean string
ganigeorgiev Mar 29, 2025
ea73207
added missing cast import
ganigeorgiev Mar 29, 2025
4c5d13f
bumped min go github action version
ganigeorgiev Aug 9, 2025
cbc7846
updated changelog
ganigeorgiev Aug 9, 2025
9014604
bumped min go action version
ganigeorgiev Oct 8, 2025
37601fe
bumped min go action version and added missing : char to the autocomp…
ganigeorgiev Dec 2, 2025
86cc345
bumped min go github action go 1.25.5
ganigeorgiev Dec 2, 2025
81ab302
try again with check-latest:true
ganigeorgiev Dec 2, 2025
2e4c9db
bumped actions/setup-go to v6
ganigeorgiev Dec 2, 2025
529cb8c
use go 1.25.5
ganigeorgiev Dec 2, 2025
fb120da
updated changelog
ganigeorgiev Dec 2, 2025
297d085
bumped min Go GitHub action to 1.25.6
ganigeorgiev Jan 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
node-version: 20.11.0

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security

Referencing the GitHub Action by a mutable major tag ('v6') can expose the workflow to supply-chain risks or unexpected behavior if the tag is retagged. Pinning to an immutable release tag (or commit SHA) ensures reproducibility and reduces risk.

Code Suggestion or Comments
uses: actions/setup-go@v6.0.0
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert yaml developer with deep knowledge of security, performance, and best practices.

### Context

File: .github/workflows/release.yaml
Lines: 22-22
Issue Type: security-medium
Severity: medium

Issue Description:
Referencing the GitHub Action by a mutable major tag ('v6') can expose the workflow to supply-chain risks or unexpected behavior if the tag is retagged. Pinning to an immutable release tag (or commit SHA) ensures reproducibility and reduces risk.

Current Code:
        uses: actions/setup-go@v6

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow yaml best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike

with:
go-version: '>=1.22.5'
go-version: '>=1.25.6'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functionality

Using a semver range ('>=1.25.6') for the Go toolchain is non-deterministic and may fail if no matching version exists. It can also silently upgrade to a newer Go version that breaks the build. Pin to the project-declared version via go.mod (preferred) or to a specific major.minor.x series.

Code Suggestion or Comments
with:
            go-version-file: 'go.mod'
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert yaml developer with deep knowledge of security, performance, and best practices.

### Context

File: .github/workflows/release.yaml
Lines: 24-24
Issue Type: functional-critical
Severity: critical

Issue Description:
Using a semver range ('>=1.25.6') for the Go toolchain is non-deterministic and may fail if no matching version exists. It can also silently upgrade to a newer Go version that breaks the build. Pin to the project-declared version via go.mod (preferred) or to a specific major.minor.x series.

Current Code:
            go-version: '>=1.25.6'

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow yaml best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike


# This step usually is not needed because the /ui/dist is pregenerated locally
# but its here to ensure that each release embeds the latest admin ui artifacts.
Expand Down
114 changes: 114 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,117 @@
## v0.22.38

- (_Backported from v0.36.0_) Bumped min Go GitHub action version to 1.25.6 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.25.6).


## v0.22.37

- (_Backported from v0.34.1_) - Added missing `:` char to the autocomplete regex ([#7353](https://github.com/pocketbase/pocketbase/pull/7353)).

- (_Backported from v0.34.1_) Bumped min Go GitHub action version to 1.25.5 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.25.5).
_The runner action was also updated to `actions/setup-go@v6` since the previous v5 Go source seems [no longer accessible](https://github.com/actions/setup-go/pull/665#issuecomment-3416693714)._


## v0.22.36

- (_Backported from v0.30.2_) Bumped min Go GitHub action version to 1.24.8 since it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.24.8+label%3ACherryPickApproved).


## v0.22.35

- (_Backported from v0.29.2_) Bumped min Go GitHub action version to 1.23.12 since it comes with some [minor fixes for the runtime and `database/sql` package](https://github.com/golang/go/issues?q=milestone%3AGo1.23.12+label%3ACherryPickApproved).


## v0.22.34

- (_Backported from v0.26.6_) Allow OIDC `email_verified` to be int or boolean string since some OIDC providers like AWS Cognito has non-standard userinfo response ([#6657](https://github.com/pocketbase/pocketbase/pull/6657)).


## v0.22.33

- (_Backported from v0.26.3_) Fixed and normalized logs error serialization across common types for more consistent logs error output ([#6631](https://github.com/pocketbase/pocketbase/issues/6631)).


## v0.22.32

- (_Backported from v0.26.2_) Updated `golang-jwt/jwt` dependency because it comes with a [minor security fix](https://github.com/golang-jwt/jwt/security/advisories/GHSA-mh63-6h87-95cp).


## v0.22.31

- (_Backported from v0.25.5_) Set the current working directory as a default goja script path when executing inline JS strings to allow `require(m)` traversing parent `node_modules` directories.


## v0.22.30

- (_Backported from v0.24.4_) Fixed fields extraction for view queries with nested comments ([#6309](https://github.com/pocketbase/pocketbase/discussions/6309)).

- Bumped GitHub action min Go version to 1.23.5 as it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.5).


## v0.22.29

- (_Backported from v0.23.11_) Upgraded `golang.org/x/net` to 0.33.0 to fix [CVE-2024-45338](https://www.cve.org/CVERecord?id=CVE-2024-45338).
_PocketBase uses the vulnerable functions primarily for the auto html->text mail generation, but most applications shouldn't be affected unless you are manually embedding unrestricted user provided value in your mail templates._


## v0.22.28

- (_Backported from v0.23.10_) Renew the superuser file token cache when clicking on the thumb preview or download link ([#6137](https://github.com/pocketbase/pocketbase/discussions/6137)).

- (_Backported from v0.23.10_) Upgraded `modernc.org/sqlite` to 1.34.3 to fix "disk io" error on arm64 systems.
_If you are extending PocketBase with Go and upgrading with `go get -u` make sure to manually set in your go.mod the `modernc.org/libc` indirect dependency to v1.55.3, aka. the exact same version the driver is using._


## v0.22.27

- Instead of unregistering the realtime client(s), just unset their auth state on delete of their related auth record so that the client(s) can receive the `delete` event ([#5898](https://github.com/pocketbase/pocketbase/issues/5898)).


## v0.22.26

- (_Backported from v0.23.0-rc_) Added manual WAL checkpoints before creating the zip backup to minimize copying unnecessary data.


## v0.22.25

- Refresh the old collections state in the Import UI after successful import submission ([#5861](https://github.com/pocketbase/pocketbase/issues/5861)).

- Added randomized throttle on failed filter list requests as a very rudimentary measure since some security researches raised concern regarding the possibity of eventual side-channel attacks.


## v0.22.24

- Delete new uploaded record files in case of record DB persist error ([#5845](https://github.com/pocketbase/pocketbase/issues/5845)).


## v0.22.23

- Updated the hooks watcher to account for the case when hooksDir is a symlink ([#5789](https://github.com/pocketbase/pocketbase/issues/5789)).

- _(Backported from v0.23.0-rc)_ Registered a default `http.Server.ErrorLog` handler to report general server connection errors as app Debug level logs (e.g. invalid TLS handshakes caused by bots trying to access your server via its IP or other similar errors).

- Other minor fixes (updated npm dev deps to fix the vulnerabilities warning, added more user friendly realtime topic length error, regenerated JSVM types, etc.)


## v0.22.22

- Added deprecation log in case Instagram OAuth2 is used (_related to [#5652](https://github.com/pocketbase/pocketbase/discussions/5652)_).

- Added `update` command warning to prevent unnecessary downloading PocketBase v0.23.0 since it will contain breaking changes.

- Added global JSVM `toString()` helper (_successor of `readerToString()`_) to stringify any value (bool, number, multi-byte array, io.Reader, etc.).
_`readerToString` is still available but it is marked as deprecated. You can also use `toString` as replacement for of `String.fromCharCode` to properly stringify multi-byte unicode characters like emojis._
```js
decodeURIComponent(escape(String.fromCharCode(...bytes))) -> toString(bytes)
```

- Updated `aws-sdk-go-v2` and removed deprecated `WithEndpointResolverWithOptions`.

- Backported some of the v0.23.0-rc form validators, fixes and tests.

- Bumped GitHub action min Go version and dependencies.


## v0.22.21

- Lock the logs database during backup to prevent `database disk image is malformed` errors in case there is a log write running in the background ([#5541](https://github.com/pocketbase/pocketbase/discussions/5541)).
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This document describes how to prepare a PR for a change in the main repository.

## Prerequisites

- Go 1.21+ (for making changes in the Go code)
- Go 1.22+ (for making changes in the Go code)
- Node 18+ (for making changes in the Admin UI)

If you haven't already, you can fork the main repository and clone your fork so that you can work locally:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ your own custom app specific business logic and still have a single portable exe

Here is a minimal example:

0. [Install Go 1.21+](https://go.dev/doc/install) (_if you haven't already_)
0. [Install Go 1.22+](https://go.dev/doc/install) (_if you haven't already_)

1. Create a new project directory with the following `main.go` file inside it:
```go
Expand Down
2 changes: 1 addition & 1 deletion apis/realtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func (api *realtimeApi) unregisterClientsByAuthModel(contextKey string, model mo
if clientModel != nil &&
clientModel.TableName() == model.TableName() &&
clientModel.GetId() == model.GetId() {
api.app.SubscriptionsBroker().Unregister(client.Id())
client.Unset(contextKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security

Replacing a full broker unregister with client.Unset(contextKey) may leave existing subscriptions active. If authorization is enforced only at subscribe time or relies on this context key, the client could continue receiving events for protected channels after the associated model is deleted, leading to privilege persistence/data leakage until the connection is reset or subscriptions are revalidated.

Code Suggestion or Comments
// Ensure client is fully unsubscribed to avoid privilege persistence on deleted auth model
			api.app.SubscriptionsBroker().Unregister(client.Id())
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert go developer with deep knowledge of security, performance, and best practices.

### Context

File: apis/realtime.go
Lines: 248-248
Issue Type: security-high
Severity: high

Issue Description:
Replacing a full broker unregister with client.Unset(contextKey) may leave existing subscriptions active. If authorization is enforced only at subscribe time or relies on this context key, the client could continue receiving events for protected channels after the associated model is deleted, leading to privilege persistence/data leakage until the connection is reset or subscriptions are revalidated.

Current Code:
			client.Unset(contextKey)

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow go best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike

}
}

Expand Down
119 changes: 98 additions & 21 deletions apis/realtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,22 +249,48 @@ func TestRealtimeAuthRecordDeleteEvent(t *testing.T) {

apis.InitApi(testApp)

authRecord, err := testApp.Dao().FindFirstRecordByData("users", "email", "test@example.com")
authRecord1, err := testApp.Dao().FindFirstRecordByData("users", "email", "test@example.com")
if err != nil {
t.Fatal(err)
}

client := subscriptions.NewDefaultClient()
client.Set(apis.ContextAuthRecordKey, authRecord)
testApp.SubscriptionsBroker().Register(client)
authRecord2, err := testApp.Dao().FindFirstRecordByData("users", "email", "test2@example.com")
if err != nil {
t.Fatal(err)
}

client1 := subscriptions.NewDefaultClient()
client1.Set(apis.ContextAuthRecordKey, authRecord1)
testApp.SubscriptionsBroker().Register(client1)

client2 := subscriptions.NewDefaultClient()
client2.Set(apis.ContextAuthRecordKey, authRecord1)
testApp.SubscriptionsBroker().Register(client2)

client3 := subscriptions.NewDefaultClient()
client3.Set(apis.ContextAuthRecordKey, authRecord2)
testApp.SubscriptionsBroker().Register(client3)

// "delete" authRecord1
e := new(core.ModelEvent)
e.Dao = testApp.Dao()
e.Model = authRecord
e.Model = authRecord1
testApp.OnModelAfterDelete().Trigger(e)

if len(testApp.SubscriptionsBroker().Clients()) != 0 {
t.Fatalf("Expected no subscription clients, found %d", len(testApp.SubscriptionsBroker().Clients()))
if total := len(testApp.SubscriptionsBroker().Clients()); total != 3 {
t.Fatalf("Expected %d subscription clients, found %d", 3, total)
}

if auth := client1.Get(apis.ContextAuthRecordKey); auth != nil {
t.Fatalf("[client1] Expected the auth state to be unset, found %#v", auth)
}

if auth := client2.Get(apis.ContextAuthRecordKey); auth != nil {
t.Fatalf("[client2] Expected the auth state to be unset, found %#v", auth)
}

if auth := client3.Get(apis.ContextAuthRecordKey); auth == nil || auth.(*models.Record).Id != authRecord2.Id {
t.Fatalf("[client3] Expected the auth state to be left unchanged, found %#v", auth)
}
}

Expand Down Expand Up @@ -307,22 +333,48 @@ func TestRealtimeAdminDeleteEvent(t *testing.T) {

apis.InitApi(testApp)

admin, err := testApp.Dao().FindAdminByEmail("test@example.com")
admin1, err := testApp.Dao().FindAdminByEmail("test@example.com")
if err != nil {
t.Fatal(err)
}

client := subscriptions.NewDefaultClient()
client.Set(apis.ContextAdminKey, admin)
testApp.SubscriptionsBroker().Register(client)
admin2, err := testApp.Dao().FindAdminByEmail("test2@example.com")
if err != nil {
t.Fatal(err)
}

client1 := subscriptions.NewDefaultClient()
client1.Set(apis.ContextAdminKey, admin1)
testApp.SubscriptionsBroker().Register(client1)

client2 := subscriptions.NewDefaultClient()
client2.Set(apis.ContextAdminKey, admin1)
testApp.SubscriptionsBroker().Register(client2)

client3 := subscriptions.NewDefaultClient()
client3.Set(apis.ContextAdminKey, admin2)
testApp.SubscriptionsBroker().Register(client3)

// "delete" admin1
e := new(core.ModelEvent)
e.Dao = testApp.Dao()
e.Model = admin
e.Model = admin1
testApp.OnModelAfterDelete().Trigger(e)

if len(testApp.SubscriptionsBroker().Clients()) != 0 {
t.Fatalf("Expected no subscription clients, found %d", len(testApp.SubscriptionsBroker().Clients()))
if total := len(testApp.SubscriptionsBroker().Clients()); total != 3 {
t.Fatalf("Expected %d subscription clients, found %d", 3, total)
}

if auth := client1.Get(apis.ContextAdminKey); auth != nil {
t.Fatalf("[client1] Expected the auth state to be unset, found %#v", auth)
}

if auth := client2.Get(apis.ContextAdminKey); auth != nil {
t.Fatalf("[client2] Expected the auth state to be unset, found %#v", auth)
}

if auth := client3.Get(apis.ContextAdminKey); auth == nil || auth.(*models.Admin).Id != admin2.Id {
t.Fatalf("[client3] Expected the auth state to be left unchanged, found %#v", auth)
}
}

Expand Down Expand Up @@ -394,16 +446,29 @@ func TestRealtimeCustomAuthModelDeleteEvent(t *testing.T) {

apis.InitApi(testApp)

authRecord, err := testApp.Dao().FindFirstRecordByData("users", "email", "test@example.com")
authRecord1, err := testApp.Dao().FindFirstRecordByData("users", "email", "test@example.com")
if err != nil {
t.Fatal(err)
}

client := subscriptions.NewDefaultClient()
client.Set(apis.ContextAuthRecordKey, authRecord)
testApp.SubscriptionsBroker().Register(client)
authRecord2, err := testApp.Dao().FindFirstRecordByData("users", "email", "test2@example.com")
if err != nil {
t.Fatal(err)
}

// refetch the authRecord as CustomUser
client1 := subscriptions.NewDefaultClient()
client1.Set(apis.ContextAuthRecordKey, authRecord1)
testApp.SubscriptionsBroker().Register(client1)

client2 := subscriptions.NewDefaultClient()
client2.Set(apis.ContextAuthRecordKey, authRecord1)
testApp.SubscriptionsBroker().Register(client2)

client3 := subscriptions.NewDefaultClient()
client3.Set(apis.ContextAuthRecordKey, authRecord2)
testApp.SubscriptionsBroker().Register(client3)

// refetch authRecord1 as CustomUser
customUser, err := findCustomUserByEmail(testApp.Dao(), "test@example.com")
if err != nil {
t.Fatal(err)
Expand All @@ -414,8 +479,20 @@ func TestRealtimeCustomAuthModelDeleteEvent(t *testing.T) {
t.Fatal(err)
}

if len(testApp.SubscriptionsBroker().Clients()) != 0 {
t.Fatalf("Expected no subscription clients, found %d", len(testApp.SubscriptionsBroker().Clients()))
if total := len(testApp.SubscriptionsBroker().Clients()); total != 3 {
t.Fatalf("Expected %d subscription clients, found %d", 3, total)
}

if auth := client1.Get(apis.ContextAuthRecordKey); auth != nil {
t.Fatalf("[client1] Expected the auth state to be unset, found %#v", auth)
}

if auth := client2.Get(apis.ContextAuthRecordKey); auth != nil {
t.Fatalf("[client2] Expected the auth state to be unset, found %#v", auth)
}

if auth := client3.Get(apis.ContextAuthRecordKey); auth == nil || auth.(*models.Record).Id != authRecord2.Id {
t.Fatalf("[client3] Expected the auth state to be left unchanged, found %#v", auth)
}
}

Expand Down
44 changes: 43 additions & 1 deletion apis/record_crud.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package apis

import (
cryptoRand "crypto/rand"
"fmt"
"log/slog"
"math/big"
"net/http"
"time"

"github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
Expand Down Expand Up @@ -71,7 +74,13 @@ func (api *recordApi) list(c echo.Context) error {

records := []*models.Record{}

result, err := searchProvider.ParseAndExec(c.QueryParams().Encode(), &records)
// note: in v0.23.0 this has been migrated as option check in the search.Provider
queryStr := c.QueryParams().Encode()
if len(queryStr) > 2048 {
return NewBadRequestError("query string is too large", nil)
}

result, err := searchProvider.ParseAndExec(queryStr, &records)
if err != nil {
return NewBadRequestError("", err)
}
Expand All @@ -91,10 +100,43 @@ func (api *recordApi) list(c echo.Context) error {
api.app.Logger().Debug("Failed to enrich list records", slog.String("error", err.Error()))
}

// note: in v0.23.0 this is combined with extra check for repeated attempts
//
// Add a randomized throttle in case of empty search filter attempts.
//
// This is just for extra precaution since security researches raised concern regarding the possibity of eventual
// timing attacks because the List API rule acts also as filter and executes in a single run with the client-side filters.
// This is by design and it is an accepted tradeoff between performance, usability and correctness.
//
// While technically the below doesn't fully guarantee protection against filter timing attacks, in practice combined with the network latency it makes them even less feasible.
// A properly configured rate limiter or individual fields Hidden checks are better suited if you are really concerned about eventual information disclosure by side-channel attacks.
//
// In all cases it doesn't really matter that much because it doesn't affect the builtin PocketBase security sensitive fields (e.g. password and tokenKey) since they
// are not client-side filterable and in the few places where they need to be compared against an external value, a constant time check is used.
if requestInfo.Admin == nil &&
(collection.ListRule != nil && *collection.ListRule != "") &&
(requestInfo.Query["filter"] != "") &&
len(e.Records) == 0 {
api.app.Logger().Debug("Randomized throttle because of failed filter search", "collectionId", collection.Id)
randomizedThrottle(100)
}
Comment on lines +116 to +122

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Robustness

randomizedThrottle is invoked within the request path and uses time.Sleep, which is not cancelable. If the client disconnects or the context is canceled, the handler will still sleep, delaying goroutine release and wasting resources.

Code Suggestion or Comments
if requestInfo.Admin == nil &&
			(collection.ListRule != nil && *collection.ListRule != "") &&
			(requestInfo.Query["filter"] != "") &&
			len(e.Records) == 0 {
			api.app.Logger().Debug("Randomized throttle because of failed filter search", "collectionId", collection.Id)
			randomizedThrottle(e.HttpContext.Request().Context(), 100)
		}
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert go developer with deep knowledge of security, performance, and best practices.

### Context

File: apis/record_crud.go
Lines: 116-122
Issue Type: robustness-medium
Severity: medium

Issue Description:
randomizedThrottle is invoked within the request path and uses time.Sleep, which is not cancelable. If the client disconnects or the context is canceled, the handler will still sleep, delaying goroutine release and wasting resources.

Current Code:
		if requestInfo.Admin == nil &&
			(collection.ListRule != nil && *collection.ListRule != "") &&
			(requestInfo.Query["filter"] != "") &&
			len(e.Records) == 0 {
			api.app.Logger().Debug("Randomized throttle because of failed filter search", "collectionId", collection.Id)
			randomizedThrottle(100)
		}

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow go best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike


return e.HttpContext.JSON(http.StatusOK, e.Result)
})
}

func randomizedThrottle(softMax int64) {
var timeout int64
randRange, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(softMax))
if err == nil {
timeout = randRange.Int64()
} else {
timeout = softMax
}

time.Sleep(time.Duration(timeout) * time.Millisecond)
}
Comment on lines +128 to +138

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Robustness

time.Sleep cannot be canceled, so this function will always block the handler goroutine for up to softMax milliseconds even if the request context is canceled. Additionally, a negative softMax could lead to unexpected behavior. Consider making the delay context-aware and validating the input.

Code Suggestion or Comments
func randomizedThrottle(ctx context.Context, softMax int64) {
	if softMax <= 0 {
		return
	}

	var timeout int64
	randRange, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(softMax))
	if err == nil {
		timeout = randRange.Int64()
	} else {
		timeout = softMax
	}

	timer := time.NewTimer(time.Duration(timeout) * time.Millisecond)
	defer timer.Stop()

	select {
	case <-ctx.Done():
		return
	case <-timer.C:
		return
	}
}
Prompt for AI assistance

Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:

You are an expert go developer with deep knowledge of security, performance, and best practices.

### Context

File: apis/record_crud.go
Lines: 128-138
Issue Type: robustness-medium
Severity: medium

Issue Description:
time.Sleep cannot be canceled, so this function will always block the handler goroutine for up to softMax milliseconds even if the request context is canceled. Additionally, a negative softMax could lead to unexpected behavior. Consider making the delay context-aware and validating the input.

Current Code:
func randomizedThrottle(softMax int64) {
	var timeout int64
	randRange, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(softMax))
	if err == nil {
		timeout = randRange.Int64()
	} else {
		timeout = softMax
	}

	time.Sleep(time.Duration(timeout) * time.Millisecond)
}

---

### Instructions

1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow go best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed

### Constraints

- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready

---


Like Dislike


func (api *recordApi) view(c echo.Context) error {
collection, _ := c.Get(ContextCollectionKey).(*models.Collection)
if collection == nil {
Expand Down
Loading