# `Dune`
[🔗](https://github.com/functional-rewire/dune/blob/v0.3.18/lib/dune.ex#L1)

A sandbox for Elixir to safely evaluate untrusted code from user input.

## Features

- only authorized modules and functions can be executed (see
`Dune.Allowlist.Default`)
- no access to environment variables, file system, network...
- code executed in an isolated process
- execution within configurable limits: timeout, maximum reductions and memory
(inspired by [Luerl](https://github.com/rvirding/luerl))
- captured standard output
- atoms, without atom leaks: parsing and runtime do not
[leak atoms](https://hexdocs.pm/elixir/String.html#to_atom/1) (i.e. does not
keep
[filling the atom table](https://learnyousomeerlang.com/starting-out-for-real#atoms)
until the VM crashes)
- modules, without actual module creation: Dune does not let users define any
actual module (would leak memory and modify the state of the VM globally), but
`defmodule` simulates the basic behavior of a module, including private and
recursive functions

The list of modules and functions authorized by default is defined by the
`Dune.Allowlist.Default` module, but this list can be extended and customized
(at your own risk!) using `Dune.Allowlist`.

If you need to keep the state between evaluations, you might consider
`Dune.Session`.

# `eval_quoted`

```elixir
@spec eval_quoted(Macro.t(), Keyword.t()) :: Dune.Success.t() | Dune.Failure.t()
```

Evaluates the quoted `ast` in the sandbox.

Available options are detailed in `Dune.Opts` (parsing restrictions have no effect)..

Returns a `Dune.Success` struct if the execution went successfully,
a `Dune.Failure` else.

## Examples

    iex> Dune.eval_quoted(quote do: [1, 2] ++ [3, 4])
    %Dune.Success{inspected: "[1, 2, 3, 4]", stdio: "", value: [1, 2, 3, 4]}

    iex> Dune.eval_quoted(quote do: System.get_env())
    %Dune.Failure{message: "** (DuneRestrictedError) function System.get_env/0 is restricted", type: :restricted}

    iex> Dune.eval_quoted(quote do: Process.sleep(500))
    %Dune.Failure{message: "Execution timeout - 50ms", type: :timeout}

# `eval_string`

```elixir
@spec eval_string(String.t(), Keyword.t()) :: Dune.Success.t() | Dune.Failure.t()
```

Evaluates the `string` in the sandbox.

Available options are detailed in `Dune.Opts`.

Returns a `Dune.Success` struct if the execution went successfully,
a `Dune.Failure` else.

## Examples

    iex> Dune.eval_string("IO.puts(\"Hello world!\")")
    %Dune.Success{inspected: ":ok", stdio: "Hello world!\n", value: :ok}

    iex> Dune.eval_string("File.cwd!()")
    %Dune.Failure{message: "** (DuneRestrictedError) function File.cwd!/0 is restricted", type: :restricted}

    iex> Dune.eval_string("List.duplicate(:spam, 100_000)")
    %Dune.Failure{message: "Execution stopped - memory limit exceeded", stdio: "", type: :memory}

    iex> Dune.eval_string("Foo.bar()")
    %Dune.Failure{message: "** (UndefinedFunctionError) function Foo.bar/0 is undefined (module Foo is not available)", type: :exception}

    iex> Dune.eval_string("][")
    %Dune.Failure{message: "unexpected token: ]", type: :parsing}

Atoms used during parsing and execution might be transformed to prevent atom leaks:

    iex> Dune.eval_string("some_variable = IO.inspect(:some_atom)")
    %Dune.Success{inspected: ":some_atom", stdio: ":some_atom\n", value: :a__Dune_atom_2__}

The `value` field shows the actual runtime value, but `inspected` and `stdio` are safe to display to the user.

# `string_to_quoted`

```elixir
@spec string_to_quoted(String.t(), Keyword.t()) :: Dune.Success.t() | Dune.Failure.t()
```

Returns the AST corresponding to the provided `string`, without leaking atoms.

Available options are detailed in `Dune.Opts` (runtime restrictions have no effect).

Returns a `Dune.Success` struct if the execution went successfully,
a `Dune.Failure` else.

## Examples

    iex> Dune.string_to_quoted("1 + 2")
    %Dune.Success{inspected: "{:+, [line: 1], [1, 2]}", stdio: "", value: {:+, [line: 1], [1, 2]}}

    iex> Dune.string_to_quoted("[invalid")
    %Dune.Failure{stdio: "", message: "missing terminator: ]", type: :parsing}

The `pretty` option can make the AST more readable by adding newlines to `inspected`:

    iex> Dune.string_to_quoted("IO.puts(:hello)", pretty: true).inspected
    "{{:., [line: 1], [{:__aliases__, [line: 1], [:IO]}, :puts]}, [line: 1],\n [:hello]}"

    iex> Dune.string_to_quoted("IO.puts(:hello)").inspected
    "{{:., [line: 1], [{:__aliases__, [line: 1], [:IO]}, :puts]}, [line: 1], [:hello]}"

Since the code isn't executed, there is no allowlist restriction:

    iex> Dune.string_to_quoted("System.halt()")
    %Dune.Success{
      inspected: "{{:., [line: 1], [{:__aliases__, [line: 1], [:System]}, :halt]}, [line: 1], []}",
      stdio: "",
      value: {{:., [line: 1], [{:__aliases__, [line: 1], [:System]}, :halt]}, [line: 1], []}
    }

Atoms might be transformed during parsing to prevent atom leaks:

    iex> Dune.string_to_quoted("some_variable = :some_atom")
    %Dune.Success{
      inspected: "{:=, [line: 1], [{:some_variable, [line: 1], nil}, :some_atom]}",
      stdio: "",
      value: {:=, [line: 1], [{:a__Dune_atom_1__, [line: 1], nil}, :a__Dune_atom_2__]}
    }

The `value` field shows the actual runtime value, but `inspected` is safe to display to the user.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
