From 7a2e9e09956bd88c64e7f610febc680a46e381ef Mon Sep 17 00:00:00 2001 From: Yash Datta Date: Tue, 31 Mar 2026 13:18:13 +0800 Subject: [PATCH 1/3] feat: Add identity filters --- internal/handler/agent.go | 15 ++++++++------- internal/handler/identity.go | 2 +- internal/service/agent.go | 4 ++-- internal/service/identity.go | 4 ++-- internal/store/postgres/identity.go | 16 +++++++++++++++- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/internal/handler/agent.go b/internal/handler/agent.go index b94dac6..6bf9187 100644 --- a/internal/handler/agent.go +++ b/internal/handler/agent.go @@ -72,13 +72,14 @@ type GetAgentOutput struct { } type ListAgentsInput struct { - AgentType string `query:"agent_type" doc:"Filter by agent type"` + AgentType string `query:"agent_type" doc:"Filter by agent type"` IdentityType []string `query:"identity_type" doc:"Filter by identity type. Comma-separated for multiple (e.g. agent,application)."` - Label string `query:"label" doc:"Filter by label (key:value, e.g. product:guardrails)"` - TrustLevel string `query:"trust_level" doc:"Filter by trust level"` - IsActive string `query:"is_active" doc:"Filter by active status"` - Limit int `query:"limit" default:"20" doc:"Items per page (max 100)"` - Offset int `query:"offset" default:"0" doc:"Offset for pagination"` + Label string `query:"label" doc:"Filter by label (key:value, e.g. product:guardrails)"` + TrustLevel string `query:"trust_level" doc:"Filter by trust level"` + IsActive string `query:"is_active" doc:"Filter by active status"` + Search string `query:"search" doc:"Search by name or external_id"` + Limit int `query:"limit" default:"20" doc:"Items per page (max 100)"` + Offset int `query:"offset" default:"0" doc:"Offset for pagination"` } type ListAgentsOutput struct { @@ -245,7 +246,7 @@ func (a *API) listAgentsOp(ctx context.Context, input *ListAgentsInput) (*ListAg return nil, huma.Error401Unauthorized("missing tenant context") } - resp, err := a.agentSvc.ListAgents(ctx, tenant.AccountID, tenant.ProjectID, input.IdentityType, input.Label, input.Limit, input.Offset) + resp, err := a.agentSvc.ListAgents(ctx, tenant.AccountID, tenant.ProjectID, input.IdentityType, input.Label, input.TrustLevel, input.IsActive, input.Search, input.Limit, input.Offset) if err != nil { return nil, mapErr(err) } diff --git a/internal/handler/identity.go b/internal/handler/identity.go index 070bcb4..e093ca6 100644 --- a/internal/handler/identity.go +++ b/internal/handler/identity.go @@ -209,7 +209,7 @@ func (a *API) listIdentitiesOp(ctx context.Context, _ *struct{}) (*IdentityListO return nil, huma.Error401Unauthorized("missing tenant context") } - identities, err := a.identitySvc.ListIdentities(ctx, tenant.AccountID, tenant.ProjectID, nil, "") + identities, err := a.identitySvc.ListIdentities(ctx, tenant.AccountID, tenant.ProjectID, nil, "", "", "", "") if err != nil { log.Error().Err(err).Msg("failed to list identities") return nil, huma.Error500InternalServerError("failed to list identities") diff --git a/internal/service/agent.go b/internal/service/agent.go index 9b28e1e..ebe2fdb 100644 --- a/internal/service/agent.go +++ b/internal/service/agent.go @@ -173,7 +173,7 @@ func (s *AgentService) GetAgent(ctx context.Context, id, accountID, projectID st } // ListAgents lists agents for a tenant, optionally filtered by identity_type(s) and label. -func (s *AgentService) ListAgents(ctx context.Context, accountID, projectID string, identityTypes []string, label string, limit, offset int) (*AgentListResponse, error) { +func (s *AgentService) ListAgents(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string, limit, offset int) (*AgentListResponse, error) { if limit <= 0 || limit > 100 { limit = 20 } @@ -181,7 +181,7 @@ func (s *AgentService) ListAgents(ctx context.Context, accountID, projectID stri offset = 0 } - identities, err := s.identitySvc.ListIdentities(ctx, accountID, projectID, identityTypes, label) + identities, err := s.identitySvc.ListIdentities(ctx, accountID, projectID, identityTypes, label, trustLevel, isActive, search) if err != nil { return nil, err } diff --git a/internal/service/identity.go b/internal/service/identity.go index 8dff315..cd98bc2 100644 --- a/internal/service/identity.go +++ b/internal/service/identity.go @@ -171,8 +171,8 @@ func (s *IdentityService) GetIdentityByExternalID(ctx context.Context, externalI } // ListIdentities returns identities for a tenant, optionally filtered by identity_type(s) and label. -func (s *IdentityService) ListIdentities(ctx context.Context, accountID, projectID string, identityTypes []string, label string) ([]*domain.Identity, error) { - return s.repo.List(ctx, accountID, projectID, identityTypes, label) +func (s *IdentityService) ListIdentities(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string) ([]*domain.Identity, error) { + return s.repo.List(ctx, accountID, projectID, identityTypes, label, trustLevel, isActive, search) } // UpdateIdentityRequest holds parameters for identity updates. diff --git a/internal/store/postgres/identity.go b/internal/store/postgres/identity.go index 3172723..babd7e4 100644 --- a/internal/store/postgres/identity.go +++ b/internal/store/postgres/identity.go @@ -75,7 +75,7 @@ func (r *IdentityRepository) GetByWIMSEURI(ctx context.Context, wimseURI, accoun // List returns identities for a tenant, optionally filtered by identity_type(s) and label. // The label parameter accepts "key:value" format (e.g. "product:guardrails", "team:platform") // and filters using JSONB containment: labels @> {"key": "value"}. -func (r *IdentityRepository) List(ctx context.Context, accountID, projectID string, identityTypes []string, label string) ([]*domain.Identity, error) { +func (r *IdentityRepository) List(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string) ([]*domain.Identity, error) { var identities []*domain.Identity q := r.db.NewSelect().Model(&identities). Where("account_id = ?", accountID). @@ -95,6 +95,20 @@ func (r *IdentityRepository) List(ctx context.Context, accountID, projectID stri labelJSON, _ := json.Marshal(map[string]string{parts[0]: parts[1]}) q = q.Where("labels @> ?::jsonb", string(labelJSON)) } + if trustLevel != "" { + q = q.Where("trust_level = ?", trustLevel) + } + if isActive != "" { + if isActive == "true" { + q = q.Where("status = 'active'") + } else if isActive == "false" { + q = q.Where("status != 'active'") + } + } + if search != "" { + searchPattern := "%" + search + "%" + q = q.Where("(name ILIKE ? OR external_id ILIKE ?)", searchPattern, searchPattern) + } if err := q.Scan(ctx); err != nil { return nil, fmt.Errorf("failed to list identities: %w", err) From e8ddddd0d1fc71af4a1a4f3f00927bd93ebffc17 Mon Sep 17 00:00:00 2001 From: Yash Datta Date: Tue, 31 Mar 2026 15:58:59 +0800 Subject: [PATCH 2/3] fix: Add integration tests --- internal/handler/identity.go | 29 +++- internal/service/agent.go | 14 +- internal/service/identity.go | 4 +- internal/store/postgres/identity.go | 20 ++- tests/integration/identity_test.go | 255 ++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+), 22 deletions(-) diff --git a/internal/handler/identity.go b/internal/handler/identity.go index e093ca6..6fb21b4 100644 --- a/internal/handler/identity.go +++ b/internal/handler/identity.go @@ -43,10 +43,22 @@ type IdentityIDInput struct { ID string `path:"id" doc:"Identity UUID"` } +type ListIdentitiesInput struct { + IdentityType []string `query:"identity_type" doc:"Filter by identity type. Comma-separated for multiple (e.g. agent,application)."` + Label string `query:"label" doc:"Filter by label (key:value, e.g. product:guardrails)"` + TrustLevel string `query:"trust_level" doc:"Filter by trust level"` + IsActive string `query:"is_active" doc:"Filter by active status"` + Search string `query:"search" doc:"Search by name or external_id"` + Limit int `query:"limit" default:"20" doc:"Items per page (max 100)"` + Offset int `query:"offset" default:"0" doc:"Offset for pagination"` +} + type IdentityListOutput struct { Body struct { Identities []*domain.Identity `json:"identities"` Total int `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` } } @@ -203,13 +215,22 @@ func (a *API) getIdentityOp(ctx context.Context, input *IdentityIDInput) (*Ident return &IdentityOutput{Body: identity}, nil } -func (a *API) listIdentitiesOp(ctx context.Context, _ *struct{}) (*IdentityListOutput, error) { +func (a *API) listIdentitiesOp(ctx context.Context, input *ListIdentitiesInput) (*IdentityListOutput, error) { tenant, err := internalMiddleware.GetTenant(ctx) if err != nil { return nil, huma.Error401Unauthorized("missing tenant context") } - identities, err := a.identitySvc.ListIdentities(ctx, tenant.AccountID, tenant.ProjectID, nil, "", "", "", "") + limit := input.Limit + if limit <= 0 || limit > 100 { + limit = 20 + } + offset := input.Offset + if offset < 0 { + offset = 0 + } + + identities, total, err := a.identitySvc.ListIdentities(ctx, tenant.AccountID, tenant.ProjectID, input.IdentityType, input.Label, input.TrustLevel, input.IsActive, input.Search, limit, offset) if err != nil { log.Error().Err(err).Msg("failed to list identities") return nil, huma.Error500InternalServerError("failed to list identities") @@ -220,7 +241,9 @@ func (a *API) listIdentitiesOp(ctx context.Context, _ *struct{}) (*IdentityListO } out := &IdentityListOutput{} out.Body.Identities = identities - out.Body.Total = len(identities) + out.Body.Total = total + out.Body.Limit = limit + out.Body.Offset = offset return out, nil } diff --git a/internal/service/agent.go b/internal/service/agent.go index ebe2fdb..62506cf 100644 --- a/internal/service/agent.go +++ b/internal/service/agent.go @@ -181,23 +181,11 @@ func (s *AgentService) ListAgents(ctx context.Context, accountID, projectID stri offset = 0 } - identities, err := s.identitySvc.ListIdentities(ctx, accountID, projectID, identityTypes, label, trustLevel, isActive, search) + identities, total, err := s.identitySvc.ListIdentities(ctx, accountID, projectID, identityTypes, label, trustLevel, isActive, search, limit, offset) if err != nil { return nil, err } - // Apply simple offset/limit on the result set. - total := len(identities) - end := offset + limit - if offset >= total { - identities = nil - } else { - if end > total { - end = total - } - identities = identities[offset:end] - } - agents := make([]AgentResponse, len(identities)) for i, id := range identities { keyPrefix := s.getKeyPrefix(ctx, id.ID) diff --git a/internal/service/identity.go b/internal/service/identity.go index cd98bc2..1100ed3 100644 --- a/internal/service/identity.go +++ b/internal/service/identity.go @@ -171,8 +171,8 @@ func (s *IdentityService) GetIdentityByExternalID(ctx context.Context, externalI } // ListIdentities returns identities for a tenant, optionally filtered by identity_type(s) and label. -func (s *IdentityService) ListIdentities(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string) ([]*domain.Identity, error) { - return s.repo.List(ctx, accountID, projectID, identityTypes, label, trustLevel, isActive, search) +func (s *IdentityService) ListIdentities(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string, limit, offset int) ([]*domain.Identity, int, error) { + return s.repo.List(ctx, accountID, projectID, identityTypes, label, trustLevel, isActive, search, limit, offset) } // UpdateIdentityRequest holds parameters for identity updates. diff --git a/internal/store/postgres/identity.go b/internal/store/postgres/identity.go index babd7e4..daba395 100644 --- a/internal/store/postgres/identity.go +++ b/internal/store/postgres/identity.go @@ -75,7 +75,7 @@ func (r *IdentityRepository) GetByWIMSEURI(ctx context.Context, wimseURI, accoun // List returns identities for a tenant, optionally filtered by identity_type(s) and label. // The label parameter accepts "key:value" format (e.g. "product:guardrails", "team:platform") // and filters using JSONB containment: labels @> {"key": "value"}. -func (r *IdentityRepository) List(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string) ([]*domain.Identity, error) { +func (r *IdentityRepository) List(ctx context.Context, accountID, projectID string, identityTypes []string, label, trustLevel, isActive, search string, limit, offset int) ([]*domain.Identity, int, error) { var identities []*domain.Identity q := r.db.NewSelect().Model(&identities). Where("account_id = ?", accountID). @@ -90,7 +90,7 @@ func (r *IdentityRepository) List(ctx context.Context, accountID, projectID stri if label != "" { parts := strings.SplitN(label, ":", 2) if len(parts) != 2 || parts[0] == "" { - return nil, fmt.Errorf("invalid label format: expected non-empty-key:value, got %q", label) + return nil, 0, fmt.Errorf("invalid label format: expected non-empty-key:value, got %q", label) } labelJSON, _ := json.Marshal(map[string]string{parts[0]: parts[1]}) q = q.Where("labels @> ?::jsonb", string(labelJSON)) @@ -110,10 +110,22 @@ func (r *IdentityRepository) List(ctx context.Context, accountID, projectID stri q = q.Where("(name ILIKE ? OR external_id ILIKE ?)", searchPattern, searchPattern) } + total, err := q.Count(ctx) + if err != nil { + return nil, 0, fmt.Errorf("failed to count identities: %w", err) + } + + if limit > 0 { + q = q.Limit(limit) + } + if offset > 0 { + q = q.Offset(offset) + } + if err := q.Scan(ctx); err != nil { - return nil, fmt.Errorf("failed to list identities: %w", err) + return nil, 0, fmt.Errorf("failed to list identities: %w", err) } - return identities, nil + return identities, total, nil } // Update saves changes to an existing identity. diff --git a/tests/integration/identity_test.go b/tests/integration/identity_test.go index 379a2c5..1420910 100644 --- a/tests/integration/identity_test.go +++ b/tests/integration/identity_test.go @@ -2,6 +2,7 @@ package integration_test import ( "context" + "fmt" "net/http" "testing" @@ -212,3 +213,257 @@ func doRaw(t *testing.T, method, path string, body any, headers map[string]strin t.Helper() return http.DefaultClient.Do(newRequest(t, method, path, body, headers)) } + +// ── Filter & Pagination Tests ─────────────────────────────────────────────── + +// TestListAgentsFilterByTrustLevel verifies that trust_level filter works server-side. +func TestListAgentsFilterByTrustLevel(t *testing.T) { + fpExt := uid("trust-fp") + uvExt := uid("trust-uv") + + // Register a first_party agent. + post(t, "/api/v1/agents/register", map[string]any{ + "external_id": fpExt, + "identity_type": "agent", + "sub_type": "autonomous", + "trust_level": "first_party", + "name": "First Party Agent", + "created_by": "test-user", + "labels": map[string]string{"test": "trust-filter"}, + }, adminHeaders()) + + // Register an unverified agent. + post(t, "/api/v1/agents/register", map[string]any{ + "external_id": uvExt, + "identity_type": "agent", + "sub_type": "tool_agent", + "trust_level": "unverified", + "name": "Unverified Agent", + "created_by": "test-user", + "labels": map[string]string{"test": "trust-filter"}, + }, adminHeaders()) + + // Filter by first_party only. + resp := get(t, "/api/v1/agents/registry?trust_level=first_party&label=test:trust-filter", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body := decode(t, resp) + agents := body["agents"].([]any) + for _, a := range agents { + m := a.(map[string]any) + assert.Equal(t, "first_party", m["trust_level"], "should only return first_party agents") + } + assert.GreaterOrEqual(t, len(agents), 1, "should have at least one first_party agent") + + // Filter by unverified — should not include first_party. + resp = get(t, "/api/v1/agents/registry?trust_level=unverified&label=test:trust-filter", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body = decode(t, resp) + agents = body["agents"].([]any) + for _, a := range agents { + m := a.(map[string]any) + assert.Equal(t, "unverified", m["trust_level"], "should only return unverified agents") + } +} + +// TestListAgentsFilterByIsActive verifies that is_active filter works. +func TestListAgentsFilterByIsActive(t *testing.T) { + activeExt := uid("active-a") + inactiveExt := uid("active-b") + + // Register and keep one active. + post(t, "/api/v1/agents/register", map[string]any{ + "external_id": activeExt, + "identity_type": "agent", + "sub_type": "autonomous", + "trust_level": "unverified", + "name": "Active Agent", + "created_by": "test-user", + "labels": map[string]string{"test": "active-filter"}, + }, adminHeaders()) + + // Register and deactivate. + resp := post(t, "/api/v1/agents/register", map[string]any{ + "external_id": inactiveExt, + "identity_type": "agent", + "sub_type": "tool_agent", + "trust_level": "unverified", + "name": "Inactive Agent", + "created_by": "test-user", + "labels": map[string]string{"test": "active-filter"}, + }, adminHeaders()) + require.Equal(t, http.StatusCreated, resp.StatusCode) + registered := decode(t, resp) + agentID := registered["identity"].(map[string]any)["id"].(string) + + // Deactivate. + deactivateResp, err := doRaw(t, http.MethodPost, "/api/v1/agents/registry/"+agentID+"/deactivate", nil, adminHeaders()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, deactivateResp.StatusCode) + deactivateResp.Body.Close() + + // is_active=true should exclude deactivated. + resp = get(t, "/api/v1/agents/registry?is_active=true&label=test:active-filter", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body := decode(t, resp) + agents := body["agents"].([]any) + for _, a := range agents { + m := a.(map[string]any) + assert.Equal(t, "active", m["status"], "is_active=true should only return active agents") + } + + // is_active=false should only return non-active. + resp = get(t, "/api/v1/agents/registry?is_active=false&label=test:active-filter", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body = decode(t, resp) + agents = body["agents"].([]any) + for _, a := range agents { + m := a.(map[string]any) + assert.NotEqual(t, "active", m["status"], "is_active=false should exclude active agents") + } +} + +// TestListAgentsSearch verifies that name/external_id search works. +func TestListAgentsSearch(t *testing.T) { + searchTag := uid("search") + ext1 := uid("search-alpha") + ext2 := uid("search-beta") + + post(t, "/api/v1/agents/register", map[string]any{ + "external_id": ext1, + "identity_type": "agent", + "sub_type": "autonomous", + "trust_level": "unverified", + "name": "Alpha Research Bot " + searchTag, + "created_by": "test-user", + }, adminHeaders()) + + post(t, "/api/v1/agents/register", map[string]any{ + "external_id": ext2, + "identity_type": "agent", + "sub_type": "tool_agent", + "trust_level": "unverified", + "name": "Beta Trading Bot " + searchTag, + "created_by": "test-user", + }, adminHeaders()) + + // Search by name substring. + resp := get(t, "/api/v1/agents/registry?search=Alpha+Research", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body := decode(t, resp) + agents := body["agents"].([]any) + found := false + for _, a := range agents { + m := a.(map[string]any) + if m["external_id"] == ext1 { + found = true + } + } + assert.True(t, found, "search should find Alpha Research Bot by name") + + // Search by external_id substring. + resp = get(t, "/api/v1/agents/registry?search="+ext2[:20], adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body = decode(t, resp) + agents = body["agents"].([]any) + found = false + for _, a := range agents { + m := a.(map[string]any) + if m["external_id"] == ext2 { + found = true + } + } + assert.True(t, found, "search should find agent by external_id substring") +} + +// TestListAgentsPagination verifies limit and offset work correctly. +func TestListAgentsPagination(t *testing.T) { + paginationLabel := uid("page") + + // Register 5 agents with a unique label. + for i := 0; i < 5; i++ { + post(t, "/api/v1/agents/register", map[string]any{ + "external_id": uid(fmt.Sprintf("page-%d", i)), + "identity_type": "agent", + "sub_type": "autonomous", + "trust_level": "unverified", + "name": fmt.Sprintf("Page Agent %d", i), + "created_by": "test-user", + "labels": map[string]string{"pagination": paginationLabel}, + }, adminHeaders()) + } + + // Request first page (limit=2). + resp := get(t, "/api/v1/agents/registry?limit=2&offset=0&label=pagination:"+paginationLabel, adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body := decode(t, resp) + page1 := body["agents"].([]any) + assert.Equal(t, 2, len(page1), "first page should have 2 agents") + assert.Equal(t, float64(5), body["total"], "total should be 5") + + // Request second page (limit=2, offset=2). + resp = get(t, "/api/v1/agents/registry?limit=2&offset=2&label=pagination:"+paginationLabel, adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body = decode(t, resp) + page2 := body["agents"].([]any) + assert.Equal(t, 2, len(page2), "second page should have 2 agents") + + // Pages should not overlap. + page1IDs := map[string]bool{} + for _, a := range page1 { + page1IDs[a.(map[string]any)["id"].(string)] = true + } + for _, a := range page2 { + id := a.(map[string]any)["id"].(string) + assert.False(t, page1IDs[id], "page 2 should not contain agents from page 1") + } + + // Request last page (offset=4, limit=2) — should return 1 agent. + resp = get(t, "/api/v1/agents/registry?limit=2&offset=4&label=pagination:"+paginationLabel, adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body = decode(t, resp) + page3 := body["agents"].([]any) + assert.Equal(t, 1, len(page3), "last page should have 1 agent") +} + +// TestListIdentitiesEndpointFilters verifies that /api/v1/identities also supports filters. +func TestListIdentitiesEndpointFilters(t *testing.T) { + ext := uid("id-filter") + post(t, "/api/v1/identities", map[string]any{ + "external_id": ext, + "trust_level": "first_party", + "identity_type": "application", + "sub_type": "chatbot", + "owner_user_id": "test-user", + "name": "Filterable App " + ext, + "allowed_scopes": []string{"read:data"}, + }, adminHeaders()) + + // Filter by identity_type. + resp := get(t, "/api/v1/identities?identity_type=application", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body := decode(t, resp) + identities := body["identities"].([]any) + for _, i := range identities { + m := i.(map[string]any) + assert.Equal(t, "application", m["identity_type"]) + } + + // Search by name. + resp = get(t, "/api/v1/identities?search=Filterable+App", adminHeaders()) + require.Equal(t, http.StatusOK, resp.StatusCode) + body = decode(t, resp) + identities = body["identities"].([]any) + found := false + for _, i := range identities { + m := i.(map[string]any) + if m["external_id"] == ext { + found = true + } + } + assert.True(t, found, "search should find the identity by name") + + // Pagination metadata present. + assert.NotNil(t, body["total"], "response should include total") + assert.NotNil(t, body["limit"], "response should include limit") + assert.NotNil(t, body["offset"], "response should include offset") +} From eb129f40669c7f394e1a37e1e26d9497592364ba Mon Sep 17 00:00:00 2001 From: Yash Datta Date: Tue, 31 Mar 2026 17:30:35 +0800 Subject: [PATCH 3/3] fix: Fix trivy failure, review comment --- .trivyignore | 10 ++++++++++ internal/store/postgres/identity.go | 11 +++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 .trivyignore diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..5c53c37 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,10 @@ +# CVE-2026-34040: Moby AuthZ plugin bypass with oversized request bodies +# github.com/docker/docker v28.5.2+incompatible → fixed in v29.3.1 +# +# This is a test-only transitive dependency pulled in by testcontainers-go. +# Docker client is NOT used in production code — only in integration tests +# that spin up PostgreSQL containers. The vulnerable AuthZ plugin bypass +# requires local Docker daemon access which is not exposed in production. +# +# Will be resolved when testcontainers-go releases a version using docker v29+. +CVE-2026-34040 diff --git a/internal/store/postgres/identity.go b/internal/store/postgres/identity.go index daba395..1f9709b 100644 --- a/internal/store/postgres/identity.go +++ b/internal/store/postgres/identity.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strconv" "strings" "github.com/uptrace/bun" @@ -99,10 +100,12 @@ func (r *IdentityRepository) List(ctx context.Context, accountID, projectID stri q = q.Where("trust_level = ?", trustLevel) } if isActive != "" { - if isActive == "true" { - q = q.Where("status = 'active'") - } else if isActive == "false" { - q = q.Where("status != 'active'") + if active, err := strconv.ParseBool(isActive); err == nil { + if active { + q = q.Where("status = 'active'") + } else { + q = q.Where("status != 'active'") + } } } if search != "" {