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
|
if is_binary(q) and String.trim(q) != "" do
|
||||||
q2 = String.trim(q)
|
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
|
# Build search filters grouped by search type for maintainability
|
||||||
# Priority: FTS > Substring > Custom Fields > Fuzzy Matching
|
# 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)
|
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)
|
custom_field_match = build_custom_field_filter(pat)
|
||||||
fuzzy_match = build_fuzzy_filter(q2, threshold)
|
fuzzy_match = build_fuzzy_filter(q2, threshold)
|
||||||
|
|
||||||
|
|
@ -505,6 +508,31 @@ defmodule Mv.Membership.Member do
|
||||||
end
|
end
|
||||||
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
|
# Search Filter Builders
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -590,11 +618,13 @@ defmodule Mv.Membership.Member do
|
||||||
if has_search do
|
if has_search do
|
||||||
# Search query provided: return email-match OR fuzzy-search candidates
|
# Search query provided: return email-match OR fuzzy-search candidates
|
||||||
trimmed_search = String.trim(search_query)
|
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
|
# Build search filters - excluding custom_field_filter for performance
|
||||||
fts_match = build_fts_filter(trimmed_search)
|
fts_match = build_fts_filter(trimmed_search)
|
||||||
fuzzy_match = build_fuzzy_filter(trimmed_search, @default_similarity_threshold)
|
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
|
query
|
||||||
|> Ash.Query.filter(
|
|> Ash.Query.filter(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue