Skip to content

Commit 8df5bd7

Browse files
authored
Use multi-method audit log search exclusions (#200)
## Summary - Updates the Go SDK dependency to v0.78.0 with list-typed audit-log exclusions. - Sends the default GET exclusion and an explicit `--exclude-method` together through the API. - Removes client-side exclusion filtering and its pagination workaround. ## Dependency Requires the server-side multi-exclusion support from kernel/kernel#2706, which is merged. ## Example ```console $ kernel audit-logs search --limit 10 Timestamp | Method | Status | Path | User | Duration (ms) | Client IP 2026-07-13 19:00:54 UTC | POST | 200 | /browser/kernel/configure | <redacted-email> | 8755 | <redacted-ip> 2026-07-13 19:00:53 UTC | POST | 200 | /browsers/<browser-id>/playwright/execute | <redacted-email> | 2223 | <redacted-ip> 2026-07-13 19:00:53 UTC | POST | 200 | /browser/kernel/playwright/execute | <redacted-email> | 2038 | <redacted-ip> 2026-07-13 19:00:51 UTC | DELETE | 204 | /browsers/<browser-id> | <redacted-email> | 22 | <redacted-ip> 2026-07-13 19:00:51 UTC | POST | 200 | /browsers/<browser-id>/playwright/execute | <redacted-email> | 3580 | <redacted-ip> 2026-07-13 19:00:51 UTC | POST | 200 | /browser/kernel/playwright/execute | <redacted-email> | 3471 | <redacted-ip> 2026-07-13 19:00:51 UTC | PUT | 201 | /browser/kernel/fs/write_file | <redacted-email> | 4 | <redacted-ip> 2026-07-13 19:00:51 UTC | POST | 200 | /browser/kernel/process/exec | <redacted-email> | 7 | <redacted-ip> 2026-07-13 19:00:51 UTC | POST | 200 | /browser/kernel/process/exec | <redacted-email> | 9246 | <redacted-ip> 2026-07-13 19:00:51 UTC | POST | 200 | /browser/kernel/process/exec | <redacted-email> | 4752 | <redacted-ip> INFO: Showing first 10 results; increase --limit or narrow the search window ``` GET requests are excluded by default. Pass `--include-get` to include them. ## Test plan - [x] `go test ./...` - [x] `go test -race ./cmd -run TestAuditLogsSearch` - [x] `go vet ./...` - [x] Verify multiple exclusions serialize as `exclude_method=GET,post` - [x] Production smoke tests with limits 10 and 100 - [x] Production smoke tests for default GET exclusion, `--include-get`, `--method`, and lowercase `--exclude-method` - [x] Production smoke tests for service, auth strategy, search, repeatable user ID, time window, table, and JSON output - [x] GitHub CI after the v0.78.0 update --------- Co-authored-by: yummybomb <19238148+yummybomb@users.noreply.github.com>
1 parent d49a910 commit 8df5bd7

4 files changed

Lines changed: 31 additions & 72 deletions

File tree

cmd/audit_logs.go

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,9 @@ func (c AuditLogsCmd) Search(ctx context.Context, in AuditLogsSearchInput) error
7272
if in.Method != "" {
7373
params.Method = kernel.String(in.Method)
7474
}
75-
// The API accepts a single exclude_method. When the default GET exclusion
76-
// stacks with a user-provided one, GET goes server-side (it drops the most
77-
// rows) and the user's method is filtered client-side.
78-
excludeGetByDefault := in.Method == "" && !in.IncludeGet
79-
var clientExclude string
80-
serverExclude := in.ExcludeMethod
81-
if excludeGetByDefault && !strings.EqualFold(in.ExcludeMethod, "GET") {
82-
clientExclude = in.ExcludeMethod
83-
serverExclude = "GET"
84-
}
85-
if serverExclude != "" {
86-
params.ExcludeMethod = kernel.String(serverExclude)
75+
excludeMethods := auditLogExcludeMethods(in.Method, in.ExcludeMethod, in.IncludeGet)
76+
if len(excludeMethods) > 0 {
77+
params.ExcludeMethod = excludeMethods
8778
}
8879
if in.Service != "" {
8980
params.Service = kernel.String(in.Service)
@@ -100,13 +91,9 @@ func (c AuditLogsCmd) Search(ctx context.Context, in AuditLogsSearchInput) error
10091
hasMore := false
10192
pager := c.auditLogs.ListAutoPaging(ctx, params)
10293
for pager.Next() {
103-
entry := pager.Current()
104-
if clientExclude != "" && strings.EqualFold(entry.Method, clientExclude) {
105-
continue
106-
}
107-
entries = append(entries, entry)
94+
entries = append(entries, pager.Current())
10895
if len(entries) >= in.Limit {
109-
hasMore = peekForMore(pager, clientExclude)
96+
hasMore = pager.Next()
11097
break
11198
}
11299
}
@@ -143,20 +130,17 @@ func (c AuditLogsCmd) Search(ctx context.Context, in AuditLogsSearchInput) error
143130
return nil
144131
}
145132

146-
// peekForMore reports whether entries matching the client-side exclusion
147-
// remain past the limit. The scan is capped at one page so a long run of
148-
// excluded entries can't trigger unbounded fetching; hitting the cap assumes
149-
// more may match.
150-
func peekForMore(pager *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry], clientExclude string) bool {
151-
for peeked := 0; peeked < auditLogsMaxPageSize; peeked++ {
152-
if !pager.Next() {
153-
return false
154-
}
155-
if clientExclude == "" || !strings.EqualFold(pager.Current().Method, clientExclude) {
156-
return true
133+
func auditLogExcludeMethods(method, excludeMethod string, includeGet bool) []string {
134+
if method != "" || includeGet {
135+
if excludeMethod == "" {
136+
return nil
157137
}
138+
return []string{excludeMethod}
139+
}
140+
if excludeMethod == "" || strings.EqualFold(excludeMethod, "GET") {
141+
return []string{"GET"}
158142
}
159-
return true
143+
return []string{"GET", excludeMethod}
160144
}
161145

162146
func parseAuditLogTime(value string) (time.Time, error) {

cmd/audit_logs_test.go

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestAuditLogsSearchBuildsParamsAndPrintsTable(t *testing.T) {
5252
assert.Equal(t, time.Date(2026, 7, 2, 15, 0, 0, 0, time.UTC), query.End)
5353
assert.Equal(t, "browsers", query.Search.Value)
5454
assert.Equal(t, "POST", query.Method.Value)
55-
assert.Equal(t, "OPTIONS", query.ExcludeMethod.Value)
55+
assert.Equal(t, []string{"OPTIONS"}, query.ExcludeMethod)
5656
assert.Equal(t, "api", query.Service.Value)
5757
assert.Equal(t, "api_key", query.AuthStrategy.Value)
5858
assert.Equal(t, []string{"user_123", "user_456"}, query.SearchUserID)
@@ -160,7 +160,7 @@ func TestAuditLogsSearchExcludesGetByDefault(t *testing.T) {
160160
capturePtermOutput(t)
161161
fake := &FakeAuditLogsService{
162162
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
163-
assert.Equal(t, "GET", query.ExcludeMethod.Value)
163+
assert.Equal(t, []string{"GET"}, query.ExcludeMethod)
164164
return auditLogPager()
165165
},
166166
}
@@ -170,11 +170,15 @@ func TestAuditLogsSearchExcludesGetByDefault(t *testing.T) {
170170
require.NoError(t, err)
171171
}
172172

173+
func TestAuditLogExcludeMethodsDeduplicatesDefaultGetCaseInsensitively(t *testing.T) {
174+
assert.Equal(t, []string{"GET"}, auditLogExcludeMethods("", "get", false))
175+
}
176+
173177
func TestAuditLogsSearchIncludeGetDisablesDefaultExclusion(t *testing.T) {
174178
capturePtermOutput(t)
175179
fake := &FakeAuditLogsService{
176180
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
177-
assert.False(t, query.ExcludeMethod.Valid())
181+
assert.Empty(t, query.ExcludeMethod)
178182
return auditLogPager()
179183
},
180184
}
@@ -189,7 +193,7 @@ func TestAuditLogsSearchMethodDisablesDefaultExclusion(t *testing.T) {
189193
fake := &FakeAuditLogsService{
190194
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
191195
assert.Equal(t, "GET", query.Method.Value)
192-
assert.False(t, query.ExcludeMethod.Valid())
196+
assert.Empty(t, query.ExcludeMethod)
193197
return auditLogPager()
194198
},
195199
}
@@ -203,8 +207,11 @@ func TestAuditLogsSearchExcludeMethodStacksWithDefaultGetExclusion(t *testing.T)
203207
buf := capturePtermOutput(t)
204208
fake := &FakeAuditLogsService{
205209
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
206-
assert.Equal(t, "GET", query.ExcludeMethod.Value)
207-
return auditLogPager(sampleAuditLogEntry("POST", "/posted"), sampleAuditLogEntry("DELETE", "/deleted"))
210+
assert.Equal(t, []string{"GET", "post"}, query.ExcludeMethod)
211+
values, err := query.URLQuery()
212+
require.NoError(t, err)
213+
assert.Equal(t, "GET,post", values.Get("exclude_method"))
214+
return auditLogPager(sampleAuditLogEntry("DELETE", "/deleted"))
208215
},
209216
}
210217
c := AuditLogsCmd{auditLogs: fake}
@@ -214,14 +221,13 @@ func TestAuditLogsSearchExcludeMethodStacksWithDefaultGetExclusion(t *testing.T)
214221

215222
out := buf.String()
216223
assert.Contains(t, out, "/deleted")
217-
assert.NotContains(t, out, "/posted")
218224
}
219225

220226
func TestAuditLogsSearchIncludeGetWithExcludeMethodSendsUserExclusion(t *testing.T) {
221227
capturePtermOutput(t)
222228
fake := &FakeAuditLogsService{
223229
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
224-
assert.Equal(t, "POST", query.ExcludeMethod.Value)
230+
assert.Equal(t, []string{"POST"}, query.ExcludeMethod)
225231
return auditLogPager()
226232
},
227233
}
@@ -255,37 +261,6 @@ func TestAuditLogsSearchLimitTruncatesResults(t *testing.T) {
255261
assert.Contains(t, out, "Showing first 2 results")
256262
}
257263

258-
func TestAuditLogsSearchNoTruncationNoticeWhenOnlyExcludedEntriesRemain(t *testing.T) {
259-
buf := capturePtermOutput(t)
260-
fake := &FakeAuditLogsService{
261-
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
262-
return auditLogPager(sampleAuditLogEntry("DELETE", "/deleted"), sampleAuditLogEntry("POST", "/posted"), sampleAuditLogEntry("POST", "/posted-too"))
263-
},
264-
}
265-
c := AuditLogsCmd{auditLogs: fake}
266-
267-
err := c.Search(context.Background(), AuditLogsSearchInput{ExcludeMethod: "POST", Limit: 1})
268-
require.NoError(t, err)
269-
270-
out := buf.String()
271-
assert.Contains(t, out, "/deleted")
272-
assert.NotContains(t, out, "Showing first")
273-
}
274-
275-
func TestAuditLogsSearchTruncationNoticeWhenMatchRemainsPastExcludedEntries(t *testing.T) {
276-
buf := capturePtermOutput(t)
277-
fake := &FakeAuditLogsService{
278-
ListAutoPagingFunc: func(ctx context.Context, query kernel.AuditLogListParams, opts ...option.RequestOption) *pagination.PageTokenPaginationAutoPager[kernel.AuditLogEntry] {
279-
return auditLogPager(sampleAuditLogEntry("DELETE", "/first"), sampleAuditLogEntry("POST", "/posted"), sampleAuditLogEntry("DELETE", "/second"))
280-
},
281-
}
282-
c := AuditLogsCmd{auditLogs: fake}
283-
284-
err := c.Search(context.Background(), AuditLogsSearchInput{ExcludeMethod: "POST", Limit: 1})
285-
require.NoError(t, err)
286-
assert.Contains(t, buf.String(), "Showing first 1 results")
287-
}
288-
289264
func TestAuditLogsSearchPrintsEmptyMessage(t *testing.T) {
290265
buf := capturePtermOutput(t)
291266
c := AuditLogsCmd{auditLogs: &FakeAuditLogsService{}}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1
1010
github.com/golang-jwt/jwt/v5 v5.2.2
1111
github.com/joho/godotenv v1.5.1
12-
github.com/kernel/kernel-go-sdk v0.75.0
12+
github.com/kernel/kernel-go-sdk v0.78.0
1313
github.com/klauspost/compress v1.18.5
1414
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1515
github.com/pterm/pterm v0.12.80

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
6464
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6565
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
6666
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
67-
github.com/kernel/kernel-go-sdk v0.75.0 h1:UTlBLOVGQIFa0Vcn9DrTbhR44IQRrcZuhaKcMTwHvms=
68-
github.com/kernel/kernel-go-sdk v0.75.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
67+
github.com/kernel/kernel-go-sdk v0.78.0 h1:ZUmQ7Pg9A0dz8SVosFScYPETg/E4e6y7JtNB30OLPZE=
68+
github.com/kernel/kernel-go-sdk v0.78.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
6969
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
7070
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
7171
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

0 commit comments

Comments
 (0)