Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 11 additions & 19 deletions app/controlplane/pkg/data/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,7 @@ func (r *WorkflowRepo) List(ctx context.Context, orgID uuid.UUID, filter *biz.Wo
wfQuery := baseQuery.Where(workflow.DeletedAtIsNil())

// Apply additional filters to the Workflow query based on the provided options
wfQuery, err := applyWorkflowFilters(wfQuery, filter)
if err != nil {
return nil, 0, err
}
wfQuery = applyWorkflowFilters(wfQuery, filter)

// Get the count of all filtered rows without the limit and offset
count, err := wfQuery.Count(ctx)
Expand Down Expand Up @@ -340,7 +337,7 @@ func applyWorkflowRunFilters(baseQuery *ent.WorkflowQuery, opts *biz.WorkflowLis
}

// applyWorkflowFilters applies filters to the Workflow query based on the provided options
func applyWorkflowFilters(wfQuery *ent.WorkflowQuery, opts *biz.WorkflowListOpts) (*ent.WorkflowQuery, error) {
func applyWorkflowFilters(wfQuery *ent.WorkflowQuery, opts *biz.WorkflowListOpts) *ent.WorkflowQuery {
if opts != nil {
if opts.ProjectIDs != nil {
wfQuery = wfQuery.Where(workflow.ProjectIDIn(opts.ProjectIDs...))
Expand All @@ -361,20 +358,15 @@ func applyWorkflowFilters(wfQuery *ent.WorkflowQuery, opts *biz.WorkflowListOpts

// Append the JSON Filters to the query
if len(opts.JSONFilters) != 0 {
// Build the predicates for each JSON filter up front so that any
// validation error (e.g. an unsafe field path) is surfaced instead
// of being silently ignored inside the query builder closure.
predicates := make([]*sql.Predicate, 0, len(opts.JSONFilters))
for _, filter := range opts.JSONFilters {
// Include the column where the filter is applied
filter.Column = workflow.FieldMetadata
jsonPredicate, err := jsonfilter.BuildEntSelectorFromJSONFilter(filter)
if err != nil {
return nil, biz.NewErrValidation(fmt.Errorf("invalid JSON filter: %w", err))
}
predicates = append(predicates, jsonPredicate)
}
wfQuery = wfQuery.Where(func(selector *sql.Selector) {
// Build the predicates for each JSON filter
predicates := make([]*sql.Predicate, 0, len(opts.JSONFilters))
for _, filter := range opts.JSONFilters {
// Include the column where the filter is applied
filter.Column = workflow.FieldMetadata
jsonPredicate, _ := jsonfilter.BuildEntSelectorFromJSONFilter(filter)
predicates = append(predicates, jsonPredicate)
}
// Combine the predicates using AND logic
selector.Where(sql.And(predicates...))
})
Expand All @@ -398,7 +390,7 @@ func applyWorkflowFilters(wfQuery *ent.WorkflowQuery, opts *biz.WorkflowListOpts
}
}

return wfQuery, nil
return wfQuery
}

// GetOrgScoped Gets a workflow making sure it belongs to a given org
Expand Down
33 changes: 0 additions & 33 deletions pkg/jsonfilter/jsonfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,6 @@ import (
"entgo.io/ent/dialect/sql/sqljson"
)

// fieldPathRegexp matches a safe JSON field path expressed in dot notation.
// A path is a dot-separated sequence of identifiers (starting with a letter or
// underscore) optionally followed by numeric array indices, e.g. "name",
// "labels.env" or "items[0].name".
//
// This allowlist is security-critical: the underlying ent sqljson helpers
// concatenate path segments verbatim into single-quoted PostgreSQL JSON path
// literals without escaping, so any character outside this set (e.g. a single
// quote) would allow breaking out of the literal and injecting arbitrary SQL.
var fieldPathRegexp = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*|\[[0-9]+\])*$`)

// columnRegexp matches a safe SQL column identifier (a single unqualified
// identifier such as "metadata").
//
Expand Down Expand Up @@ -83,13 +72,6 @@ func BuildEntSelectorFromJSONFilter(jsonFilter *JSONFilter) (*entsql.Predicate,
return nil, err
}

// Validate the field path before it reaches the SQL builder. The ent
// sqljson helpers concatenate the path segments unescaped into the query,
// so an unsafe value would allow SQL injection.
if err := validateFieldPath(jsonFilter.FieldPath); err != nil {
return nil, err
}

// Convert the dot notation to the path that Ent expects.
dotPath := sqljson.DotPath(jsonFilter.FieldPath)

Expand Down Expand Up @@ -133,18 +115,3 @@ func validateColumn(column string) error {

return nil
}

// validateFieldPath ensures the JSON field path only contains safe characters
// before it is concatenated into the SQL query by the ent sqljson helpers.
// An empty path is allowed: it targets the column itself and is not injectable.
func validateFieldPath(fieldPath string) error {
if fieldPath == "" {
return nil
}

if !fieldPathRegexp.MatchString(fieldPath) {
return fmt.Errorf("invalid field path %q: must be dot-separated identifiers with optional numeric array indices", fieldPath)
}

return nil
}
Loading