Packages
mob_dev
0.5.13
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/mob_dev/security_scan/runner.ex
defmodule MobDev.SecurityScan.Runner do
@moduledoc """
Orchestrates the scan: invokes each enabled layer in turn,
collects `LayerResult`s, and returns a `Report`.
Layers are run sequentially (not concurrently) so that terminal
output stays readable in the streaming formatter and so that
resource-heavy scanners like semgrep don't dogpile a laptop.
Layers never raise — failures land as `%LayerResult{status: :error}`.
The runner additionally guards every callback with a `try`, so a
bug in one layer can't take the whole scan down.
"""
alias MobDev.SecurityScan.{LayerResult, Report}
@doc """
Runs the listed layers in order. `opts` is forwarded to each
layer's `run/1` callback. The optional `:on_layer_start` and
`:on_layer_done` callbacks let the formatter stream progress
to the terminal as layers complete.
"""
@spec run([module()], keyword()) :: Report.t()
def run(layers, opts \\ []) do
project_root = Keyword.get(opts, :project_root, File.cwd!())
skip = Keyword.get(opts, :skip, [])
on_start = Keyword.get(opts, :on_layer_start, fn _ -> :ok end)
on_done = Keyword.get(opts, :on_layer_done, fn _ -> :ok end)
started_at = DateTime.utc_now()
layer_results =
Enum.map(layers, fn layer ->
on_start.(layer.name())
result = run_layer(layer, skip, opts)
on_done.(result)
result
end)
%Report{
started_at: started_at,
finished_at: DateTime.utc_now(),
project_root: project_root,
layers: layer_results
}
end
defp run_layer(layer, skip, opts) do
name = layer.name()
if name in skip do
%LayerResult{
name: name,
status: :skipped,
notes: ["skipped via --skip flag"]
}
else
execute(layer, opts)
end
end
defp execute(layer, opts) do
started = System.monotonic_time(:millisecond)
try do
%LayerResult{} = result = layer.run(opts)
duration = System.monotonic_time(:millisecond) - started
%{result | duration_ms: duration}
rescue
e ->
duration = System.monotonic_time(:millisecond) - started
%LayerResult{
name: layer.name(),
status: :error,
error: Exception.message(e),
duration_ms: duration
}
end
end
end