Packages
metastatic
0.14.2
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/missing_authorization.ex
defmodule Metastatic.Analysis.BusinessLogic.MissingAuthorization do
@moduledoc """
Detects sensitive operations without authorization checks (CWE-862).
This analyzer identifies code patterns where data modification or access
operations are performed without apparent authorization verification.
## Cross-Language Applicability
Missing authorization is a **universal access control vulnerability**:
- **Elixir/Phoenix**: `def delete(conn, %{"id" => id}), do: Repo.delete!(id)`
- **Python/Django**: `def delete(request, id): Model.objects.get(id=id).delete()`
- **JavaScript/Express**: `app.delete('/item/:id', (req, res) => Item.delete(req.params.id))`
- **Ruby/Rails**: `def destroy; @item.destroy; end`
- **Java/Spring**: `@DeleteMapping public void delete(@PathVariable id) { repo.deleteById(id); }`
- **C#/ASP.NET**: `public IActionResult Delete(int id) => _repo.Delete(id);`
- **Go**: `func DeleteHandler(w http.ResponseWriter, r *http.Request) { db.Delete(id) }`
## Problem
When sensitive operations lack authorization checks:
- Any authenticated user can modify any data
- Horizontal privilege escalation is possible
- Data integrity is compromised
- Compliance requirements may be violated
## Detection Strategy
Detects patterns where:
1. CRUD operations (create, update, delete) are performed
2. The function context doesn't show authorization checks
3. User-supplied IDs are used directly without ownership verification
## Examples
### Bad (Elixir)
def delete(conn, %{"id" => id}) do
post = Repo.get!(Post, id)
Repo.delete!(post)
json(conn, %{status: "deleted"})
end
### Good (Elixir)
def delete(conn, %{"id" => id}) do
user = conn.assigns.current_user
post = Repo.get!(Post, id)
if post.user_id == user.id or user.admin? do
Repo.delete!(post)
json(conn, %{status: "deleted"})
else
conn |> put_status(403) |> json(%{error: "Forbidden"})
end
end
"""
@behaviour Metastatic.Analysis.Analyzer
alias Metastatic.Analysis.Analyzer
@sensitive_operations ~w[
delete delete! remove destroy
update update! save put patch
create insert insert! new
Repo.delete Repo.update Repo.insert
.delete .update .save .destroy
deleteById updateById removeById
delete_all update_all
]
@authorization_indicators ~w[
authorize authorized? can? permit? allowed?
authorize! check_permission has_permission?
current_user conn.assigns user_id owner
policy policies ability abilities
admin? is_admin role roles
forbidden 403 unauthorized 401
Bodyguard Canada CanCan Pundit
]
@action_names ~w[
delete destroy remove update edit
create new index show
]
@impl true
def info do
%{
name: :missing_authorization,
category: :security,
description: "Detects sensitive operations without authorization checks (CWE-862)",
severity: :error,
explanation: """
Missing authorization occurs when sensitive operations (create, update, delete)
are performed without verifying the user has permission. This allows:
- Any user to modify or delete any resource
- Horizontal privilege escalation attacks
- Data tampering and integrity violations
Always verify authorization before sensitive operations:
- Check resource ownership (resource.user_id == current_user.id)
- Use authorization libraries (Bodyguard, CanCan, etc.)
- Implement policy-based access control
""",
configurable: true
}
end
@impl true
# Detect function definitions that contain sensitive operations without auth
def analyze({:function_def, meta, body} = node, context) when is_list(meta) do
func_name = Keyword.get(meta, :name, "")
if sensitive_action?(func_name) do
body_list = if is_list(body), do: body, else: [body]
has_auth? = has_authorization_check?(body_list, context)
has_sensitive_op? = has_sensitive_operation?(body_list)
if has_sensitive_op? and not has_auth? do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :security,
severity: :error,
message:
"Missing authorization: sensitive operation in '#{func_name}' without auth check",
node: node,
metadata: %{
cwe: 862,
function: func_name,
suggestion: "Add authorization check before performing sensitive operations"
}
)
]
else
[]
end
else
[]
end
end
# Detect direct sensitive operations with user-supplied IDs
def analyze({:function_call, meta, args} = node, context) when is_list(meta) do
func_name = Keyword.get(meta, :name, "")
if sensitive_function?(func_name) and
has_user_supplied_id?(args, context) and
not in_authorization_context?(context) do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :security,
severity: :warning,
message: "Potential missing authorization: '#{func_name}' with user-supplied ID",
node: node,
metadata: %{
cwe: 862,
function: func_name,
suggestion: "Verify user has permission to access/modify this resource"
}
)
]
else
[]
end
end
def analyze(_node, _context), do: []
# ----- Private Helpers -----
defp sensitive_action?(func_name) when is_binary(func_name) do
func_lower = String.downcase(func_name)
Enum.any?(@action_names, &String.contains?(func_lower, &1))
end
defp sensitive_action?(_), do: false
defp sensitive_function?(func_name) when is_binary(func_name) do
func_lower = String.downcase(func_name)
Enum.any?(@sensitive_operations, fn op ->
String.contains?(func_lower, String.downcase(op))
end)
end
defp sensitive_function?(_), do: false
defp has_authorization_check?(body, _context) when is_list(body) do
Enum.any?(body, &contains_authorization?/1)
end
defp has_authorization_check?(body, context), do: has_authorization_check?([body], context)
defp contains_authorization?(node) do
case node do
{:function_call, meta, _args} when is_list(meta) ->
func_name = Keyword.get(meta, :name, "")
auth_function?(func_name)
{:conditional, _meta, [condition | _branches]} ->
contains_authorization?(condition)
{:binary_op, meta, [left, right]} when is_list(meta) ->
operator = Keyword.get(meta, :operator)
# Check for equality comparisons that might be auth checks
if operator in [:==, :===, :!=, :!==] do
involves_auth_variable?(left) or involves_auth_variable?(right)
else
contains_authorization?(left) or contains_authorization?(right)
end
{:attribute_access, _meta, children} when is_list(children) ->
Enum.any?(children, &involves_auth_variable?/1)
{:block, _meta, statements} when is_list(statements) ->
Enum.any?(statements, &contains_authorization?/1)
{:case, _meta, [_expr | arms]} ->
Enum.any?(arms, &contains_authorization?/1)
{:pipe, _meta, stages} when is_list(stages) ->
Enum.any?(stages, &contains_authorization?/1)
tuple when is_tuple(tuple) ->
tuple |> Tuple.to_list() |> Enum.any?(&contains_authorization?/1)
list when is_list(list) ->
Enum.any?(list, &contains_authorization?/1)
_ ->
false
end
end
defp auth_function?(func_name) when is_binary(func_name) do
func_lower = String.downcase(func_name)
Enum.any?(@authorization_indicators, fn ind ->
String.contains?(func_lower, String.downcase(ind))
end)
end
defp auth_function?(_), do: false
defp involves_auth_variable?({:variable, _meta, name}) when is_binary(name) do
name_lower = String.downcase(name)
Enum.any?(["user", "owner", "admin", "role", "permission"], &String.contains?(name_lower, &1))
end
defp involves_auth_variable?({:attribute_access, _meta, children}) when is_list(children) do
Enum.any?(children, fn
{:literal, _, attr} when is_binary(attr) ->
attr_lower = String.downcase(attr)
String.contains?(attr_lower, "user") or String.contains?(attr_lower, "id")
other ->
involves_auth_variable?(other)
end)
end
defp involves_auth_variable?(_), do: false
defp has_sensitive_operation?(body) when is_list(body) do
Enum.any?(body, &contains_sensitive_operation?/1)
end
defp contains_sensitive_operation?(node) do
case node do
{:function_call, meta, _args} when is_list(meta) ->
func_name = Keyword.get(meta, :name, "")
sensitive_function?(func_name)
{:pipe, _meta, stages} when is_list(stages) ->
Enum.any?(stages, &contains_sensitive_operation?/1)
{:block, _meta, statements} when is_list(statements) ->
Enum.any?(statements, &contains_sensitive_operation?/1)
tuple when is_tuple(tuple) ->
tuple |> Tuple.to_list() |> Enum.any?(&contains_sensitive_operation?/1)
list when is_list(list) ->
Enum.any?(list, &contains_sensitive_operation?/1)
_ ->
false
end
end
defp has_user_supplied_id?(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)
String.contains?(name_lower, "id") or String.contains?(name_lower, "param")
{:map_access, _meta, _} ->
true
{:attribute_access, _meta, _} ->
true
_ ->
false
end
end)
end
defp has_user_supplied_id?(_, _), do: false
defp in_authorization_context?(context) do
parent_stack = Map.get(context, :parent_stack, [])
Enum.any?(parent_stack, &contains_authorization?/1)
end
end