Packages
mob_dev
0.6.18
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/mob.audit_plugins.ex
defmodule Mix.Tasks.Mob.AuditPlugins do
use Mix.Task
@shortdoc "Static-analysis audit of activated Mob plugins"
@moduledoc """
Scans every activated Mob plugin's Elixir + C source for risky patterns
(see `MOB_PLUGIN_SECURITY.md`'s default ruleset).
mix mob.audit_plugins
mix mob.audit_plugins --plugin mob_demo_haptic_extras
mix mob.audit_plugins --accept-medium
Rules implemented (in `MobDev.Plugin.Audit`):
* `Code.eval_string/1,2,3` and `Code.compile_string/1,2` (high)
* `:erlang.binary_to_term/1` (the unbounded arity-1 form) (high)
* `String.to_atom/1` with a non-literal argument (medium)
* `Application.put_env(:mob, ...)` (medium)
* `File.write/cp/rm_rf`, `:os.cmd`, `System.cmd`, `Path.expand("~")` (medium)
* `system(3)`, `popen(3)`, `execve(2)`, `socket(2)` in NIF C (high/medium)
Kotlin and Swift sources are reported as "not yet audited" — proper
parsers land in a follow-up commit.
## Options
* `--plugin <name>` — scope the audit to one activated plugin.
* `--accept-medium` — exit 0 when only mediums are found (highs still
produce exit 2). Use after you've reviewed and decided to live with
the medium findings.
## Exit code
* `0` — no findings, or only `:low` findings, or `:medium` findings with
`--accept-medium`.
* `1` — at least one `:medium` finding (without `--accept-medium`).
* `2` — at least one `:high` finding.
"""
alias MobDev.Plugin
alias MobDev.Plugin.{Audit, Manifest, Report}
@switches [plugin: :string, accept_medium: :boolean]
@impl Mix.Task
def run(args) do
Mix.Task.run("loadpaths")
{opts, _, _} = OptionParser.parse(args, strict: @switches)
accept_medium? = Keyword.get(opts, :accept_medium, false)
only = parse_only(opts[:plugin])
reports = audit_all(only)
output = Report.render_audit(reports)
IO.puts("\n" <> output <> "\n")
case Audit.exit_code(reports, accept_medium?) do
0 -> :ok
code -> exit({:shutdown, code})
end
end
@doc """
Audits every activated plugin, optionally filtered to one name. Public for
testing — the matching CLI invocation is `mix mob.audit_plugins`.
"""
@spec audit_all(atom() | nil) :: [Audit.report()]
def audit_all(only \\ nil) do
plugins = activated_plugins()
plugins
|> Enum.filter(fn {name, _dir, _manifest} -> only == nil or name == only end)
|> Enum.map(fn {name, dir, manifest} ->
manifest = manifest || %{name: name}
Audit.audit_plugin(dir, manifest)
end)
end
@doc """
Returns `{name, dir, manifest}` for every activated plugin, resolving names
through `Mix.Project.deps_paths/0`. Public for testing.
"""
@spec activated_plugins() :: [{atom(), Path.t(), map() | nil}]
def activated_plugins do
deps = Mix.Project.deps_paths()
for name <- Plugin.activated_names(), dir = deps[name], not is_nil(dir) do
manifest =
case Manifest.load(dir) do
{:ok, m} -> m
{:error, _} -> nil
end
{name, dir, manifest}
end
end
defp parse_only(nil), do: nil
defp parse_only(name) when is_binary(name), do: String.to_atom(name)
end