security: add input sanitization for search queries
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Escape SQL LIKE wildcards (% and _) to prevent pattern injection - Limit search query length to 100 characters - Apply sanitization in both :search action and linking filters - FTS and fuzzy search use unsanitized query (wildcards not special there)
This commit is contained in:
parent
1ec6188884
commit
ca5fad0dcc
1 changed files with 33 additions and 3 deletions
|
|
@ -156,12 +156,15 @@ defmodule Mv.Membership.Member do
|
|||
|
||||
if is_binary(q) and String.trim(q) != "" do
|
||||
q2 = String.trim(q)
|
||||
pat = "%" <> q2 <> "%"
|
||||
# Sanitize for LIKE patterns (escape % and _), limit length to 100 chars
|
||||
q2_sanitized = sanitize_search_query(q2)
|
||||
pat = "%" <> q2_sanitized <> "%"
|
||||
|
||||
# Build search filters grouped by search type for maintainability
|
||||
# Priority: FTS > Substring > Custom Fields > Fuzzy Matching
|
||||
# Note: FTS and fuzzy use q2 (unsanitized), LIKE-based filters use pat (sanitized)
|
||||
fts_match = build_fts_filter(q2)
|
||||
substring_match = build_substring_filter(q2, pat)
|
||||
substring_match = build_substring_filter(q2_sanitized, pat)
|
||||
custom_field_match = build_custom_field_filter(pat)
|
||||
fuzzy_match = build_fuzzy_filter(q2, threshold)
|
||||
|
||||
|
|
@ -505,6 +508,31 @@ defmodule Mv.Membership.Member do
|
|||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Search Input Sanitization
|
||||
# ============================================================================
|
||||
|
||||
# Sanitizes search input to prevent LIKE pattern injection.
|
||||
# Escapes SQL LIKE wildcards (% and _) and limits query length.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# iex> sanitize_search_query("test%injection")
|
||||
# "test\\%injection"
|
||||
#
|
||||
# iex> sanitize_search_query("very_long_search")
|
||||
# "very\\_long\\_search"
|
||||
#
|
||||
defp sanitize_search_query(query) when is_binary(query) do
|
||||
query
|
||||
|> String.slice(0, 100)
|
||||
|> String.replace("\\", "\\\\")
|
||||
|> String.replace("%", "\\%")
|
||||
|> String.replace("_", "\\_")
|
||||
end
|
||||
|
||||
defp sanitize_search_query(_), do: ""
|
||||
|
||||
# ============================================================================
|
||||
# Search Filter Builders
|
||||
# ============================================================================
|
||||
|
|
@ -590,11 +618,13 @@ defmodule Mv.Membership.Member do
|
|||
if has_search do
|
||||
# Search query provided: return email-match OR fuzzy-search candidates
|
||||
trimmed_search = String.trim(search_query)
|
||||
# Sanitize for LIKE patterns (contains uses ILIKE internally)
|
||||
sanitized_search = sanitize_search_query(trimmed_search)
|
||||
|
||||
# Build search filters - excluding custom_field_filter for performance
|
||||
fts_match = build_fts_filter(trimmed_search)
|
||||
fuzzy_match = build_fuzzy_filter(trimmed_search, @default_similarity_threshold)
|
||||
email_substring_match = expr(contains(email, ^trimmed_search))
|
||||
email_substring_match = expr(contains(email, ^sanitized_search))
|
||||
|
||||
query
|
||||
|> Ash.Query.filter(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue