Packages
metastatic
0.13.3
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/metastatic/analysis/business_logic/improper_input_validation.ex
defmodule Metastatic.Analysis.BusinessLogic.ImproperInputValidation do
@moduledoc """
Detects improper input validation patterns (CWE-20).
This analyzer identifies code patterns where user input is used in sensitive
operations without apparent validation or sanitization.
## Cross-Language Applicability
Input validation is a **universal security requirement**:
- **Elixir**: Using `params` directly without changeset validation
- **Python**: Using `request.args` without validation
- **JavaScript**: Using `req.body` without schema validation
- **Ruby**: Using `params` without strong parameters
- **Java**: Using request parameters without Bean Validation
- **C#**: Using model binding without DataAnnotations
## Problem
When input is not validated:
- Type confusion vulnerabilities
- Buffer overflows (in some languages)
- Logic flaws from unexpected values
- Injection attacks (SQL, command, etc.)
- Denial of service through malformed input
## Detection Strategy
Detects patterns where:
1. User input is used directly in operations
2. No validation function calls are apparent
3. No schema/changeset validation is used
4. Input flows to sensitive operations
"""
@behaviour Metastatic.Analysis.Analyzer
alias Metastatic.Analysis.Analyzer
@user_input_sources ~w[
params request args query body
input form data payload
get post put patch
]
@validation_functions ~w[
validate valid? changeset cast
schema validate_required validate_format
validate_length validate_inclusion
sanitize clean filter escape
permit strong_parameters
Bean Validator DataAnnotation
Joi Yup zod
]
@sensitive_operations ~w[
query execute run eval
send call request
write save insert update delete
open read file path
create new build
]
@impl true
def info do
%{
name: :improper_input_validation,
category: :security,
description: "Detects user input used without validation (CWE-20)",
severity: :warning,
explanation: """
Improper input validation occurs when user-supplied data is used in operations
without checking its format, type, length, or content. This can lead to:
- Injection attacks (SQL, command, XSS)
- Type confusion and logic errors
- Denial of service
- Data corruption
Always validate input:
- Use schema validation (Ecto changesets, JSON Schema, etc.)
- Validate type, format, length, and range
- Use allowlists over denylists
- Sanitize before use in sensitive operations
""",
configurable: true
}
end
@impl true
# Detect function definitions that use input without validation
def analyze({:function_def, meta, body} = node, context) when is_list(meta) do
func_name = Keyword.get(meta, :name, "")
params = Keyword.get(meta, :params, [])
if has_input_params?(params) do
body_list = if is_list(body), do: body, else: [body]
has_validation? = has_input_validation?(body_list)
has_sensitive_use? = has_sensitive_input_use?(body_list, context)
if has_sensitive_use? and not has_validation? do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :security,
severity: :warning,
message:
"Improper input validation: '#{func_name}' uses input in sensitive operations without validation",
node: node,
metadata: %{
cwe: 20,
function: func_name,
suggestion:
"Add input validation using changesets, schemas, or validation functions"
}
)
]
else
[]
end
else
[]
end
end
# Detect direct use of user input in sensitive function calls
def analyze({:function_call, meta, args} = node, context) when is_list(meta) do
func_name = Keyword.get(meta, :name, "")
if sensitive_operation?(func_name) and
has_direct_input_argument?(args, context) and
not in_validation_context?(context) do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :security,
severity: :warning,
message:
"Potential improper input validation: user input passed directly to '#{func_name}'",
node: node,
metadata: %{
cwe: 20,
function: func_name,
suggestion: "Validate and sanitize input before passing to this function"
}
)
]
else
[]
end
end
def analyze(_node, _context), do: []
# ----- Private Helpers -----
defp has_input_params?(params) when is_list(params) do
Enum.any?(params, fn
{:param, _, name} when is_binary(name) ->
name_lower = String.downcase(name)
Enum.any?(@user_input_sources, &String.contains?(name_lower, &1))
_ ->
false
end)
end
defp has_input_params?(_), do: false
defp has_input_validation?(body) when is_list(body) do
Enum.any?(body, &contains_validation?/1)
end
defp contains_validation?(node) do
case node do
{:function_call, meta, _args} when is_list(meta) ->
func_name = Keyword.get(meta, :name, "")
validation_function?(func_name)
{:pipe, _meta, stages} when is_list(stages) ->
Enum.any?(stages, &contains_validation?/1)
{:block, _meta, statements} when is_list(statements) ->
Enum.any?(statements, &contains_validation?/1)
{:conditional, _meta, [condition | _branches]} ->
type_check?(condition) or contains_validation?(condition)
{:case, _meta, _} ->
# Case statements often indicate pattern matching validation
true
{:with, _meta, _} ->
# With statements often used for validation chains
true
tuple when is_tuple(tuple) ->
tuple |> Tuple.to_list() |> Enum.any?(&contains_validation?/1)
list when is_list(list) ->
Enum.any?(list, &contains_validation?/1)
_ ->
false
end
end
defp validation_function?(func_name) when is_binary(func_name) do
func_lower = String.downcase(func_name)
Enum.any?(@validation_functions, fn pattern ->
String.contains?(func_lower, String.downcase(pattern))
end)
end
defp validation_function?(_), do: false
defp type_check?(node) do
case node do
{:function_call, meta, _args} when is_list(meta) ->
func_name = Keyword.get(meta, :name, "")
func_lower = String.downcase(func_name)
String.contains?(func_lower, "is_") or
String.contains?(func_lower, "type") or
String.contains?(func_lower, "match")
_ ->
false
end
end
defp has_sensitive_input_use?(body, context) when is_list(body) do
Enum.any?(body, &contains_sensitive_input_use?(&1, context))
end
defp contains_sensitive_input_use?(node, context) do
case node do
{:function_call, meta, args} when is_list(meta) ->
func_name = Keyword.get(meta, :name, "")
sensitive_operation?(func_name) and
has_direct_input_argument?(args, context)
{:pipe, _meta, stages} when is_list(stages) ->
Enum.any?(stages, &contains_sensitive_input_use?(&1, context))
{:block, _meta, statements} when is_list(statements) ->
Enum.any?(statements, &contains_sensitive_input_use?(&1, context))
tuple when is_tuple(tuple) ->
tuple
|> Tuple.to_list()
|> Enum.any?(&contains_sensitive_input_use?(&1, context))
list when is_list(list) ->
Enum.any?(list, &contains_sensitive_input_use?(&1, context))
_ ->
false
end
end
defp sensitive_operation?(func_name) when is_binary(func_name) do
func_lower = String.downcase(func_name)
Enum.any?(@sensitive_operations, &String.contains?(func_lower, &1))
end
defp sensitive_operation?(_), do: false
defp has_direct_input_argument?(args, _context) when is_list(args) do
Enum.any?(args, fn arg ->
case arg do
{:variable, _meta, name} when is_binary(name) ->
name_lower = String.downcase(name)
Enum.any?(@user_input_sources, &String.contains?(name_lower, &1))
{:map_access, _meta, _} ->
true
{:attribute_access, _meta, children} when is_list(children) ->
Enum.any?(children, fn
{:variable, _, name} when is_binary(name) ->
name_lower = String.downcase(name)
Enum.any?(@user_input_sources, &String.contains?(name_lower, &1))
_ ->
false
end)
_ ->
false
end
end)
end
defp has_direct_input_argument?(_, _), do: false
defp in_validation_context?(context) do
parent_stack = Map.get(context, :parent_stack, [])
Enum.any?(parent_stack, &contains_validation?/1)
end
end