JustBash.BannedCallTracer (JustBash v0.4.0)

View Source

Detects banned remote function calls by inspecting compiled BEAM files.

Rather than re-compiling source, this module reads the abstract code (debug info) from already-compiled .beam files and walks the call tree looking for banned calls. No recompilation, no module-redefinition warnings, no load-order problems.

Security model

JustBash code must never touch real system resources — all I/O goes through the virtual filesystem and environment abstractions. The banned categories are:

Filesystem (File, :file)

Any call into File.* or the Erlang :file module. Covers reads, writes, stat, ls, exists?, mkdir, rm, etc.

Environment (System.get_env, System.put_env, System.delete_env)

Reading or mutating real OS environment variables.

Process / OS escape (System.cmd, System.shell, Port, :os, :erlang.open_port)

Spawning real OS processes or opening ports. These are the most dangerous — they can execute arbitrary host commands regardless of filesystem sandboxing.

Node escape (Node)

Connecting to or spawning processes on remote Erlang nodes.

Mutable shared state (Process.put/get/delete, :ets)

Process dictionary and ETS leak mutable state across calls, breaking referential transparency and making code unpredictable in concurrent use.

What this catches

  • Direct calls: File.read(path), System.cmd("rm", [...])
  • Calls through Elixir aliases (resolved before compilation)
  • apply/3 and :erlang.apply/3 when both the module and function are literal atoms at the call site, e.g. apply(File, :read, [path])

What this cannot catch

Dynamic dispatch where the module or function is a runtime variable:

mod = File
mod.read(path)          # module is a variable — opaque to static analysis

fun = :read
apply(File, fun, [path]) # function is a variable — opaque

These cases require runtime instrumentation (e.g. :erlang.trace) to detect. No purely static tool can catch them. The defense against dynamic dispatch is code review discipline and the fact that there is no legitimate reason for JustBash library code to hold a reference to File or System in a variable at all.

Summary

Functions

Checks all .beam files under beam_dir for banned calls. Returns a list of violations.

Checks a single .beam file for banned calls.

Types

violation()

@type violation() :: %{
  call: {module(), atom(), arity()},
  beam: Path.t(),
  line: non_neg_integer()
}

Functions

check_app(beam_dir \\ "_build/test/lib/just_bash/ebin")

@spec check_app(Path.t()) :: [violation()]

Checks all .beam files under beam_dir for banned calls. Returns a list of violations.

check_beam(beam_path)

@spec check_beam(Path.t()) :: [violation()]

Checks a single .beam file for banned calls.