SafeCode is currently a very early stage project to answer the question "Is this code safe to load and run in an elixir application?". The immediate driving force is the potential need to answer this question in the Beacon LiveView CMS. It also has potential to be used in other projects that load code dynamically such as LiveBook.
Yes! One of Elixirs many strengths is the ability to load modules on the fly via the Code.compile_string function. However, with great power comes great responsibility. As noted in the function docs: "Warning: string can be any Elixir code and code can be executed with the same privileges as the Erlang VM: this means that such code could compromise the machine (for example by executing system commands). Don't use compile_string/2 with untrusted input (such as strings coming from the network)."
One of the questions to answer as this project develops is "what is safe". While it is obvious that System.cmd("rm", ["-rf", "/"])
is not safe, and 1 + 1
is safe, what about IO.puts
? Could overloading logging be an attack vector? And what about non-tail recursion or simply running code that chews up cpu cycles? What about process communication? Can that be allowed safely? At the outset we are focusing on determining what simple code is safe (e.g. Enum.map(vals, fn {x, y} -> x + y end)
), but the more advanced attack vectors are front of mind.
As Beacon LiveView CMS is an early potential use of SafeCode, we will out of the gate support validating both regular Elixir code and Heex templates. This will allow a user to define Beacon Components by entering the LiveComponent code via a web interface, and load that code into memory on a running system to be rendered on request.
This project is using an allow-list approach. We are parsing the code into AST and checking if each function call in the AST is allowed. If any function validators return true for a given function, the call passes. If no function validators return true for any given function call, SafeCode raises.
The main entry points are SafeCode.Validator.validate/2
and SafeCode.Validator.validate_heex!/2
. validate/2
takes straight Elixir code and tests against the Elixir
FunctionValidator
, and validate_heex!/2
takes Phoenix Template code and tests against both Elixir
and Phoenix
FunctionValidator
s.
Additional function validators can be specified by passing the extra_function_validators
keyword option like this (supports single validator or list of validators):
SafeCode.Validator.validate_heex!(body, extra_function_validators: MyApp.SafeCodeValidator)
These validator(s) should implement SafeCode.Validator.FunctionValidators.Behaviour
. As this is an allow-list approach, your implemented functions should return true if the function is valid, false if it cannot be vouched for. SafeCode will move on to other validators until it finds one willing to vouch for the function by returning true. See the Elixir and Phoenix Function Validators in the same folder as the behaviour for examples.
Add safe_code
to your list of dependencies in mix.exs
:
def deps do
[
{:safe_code, "~> 0.2.3"}
]
end
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/safe_code.