# Even more bare baremodule

**URL:** <https://discourse.julialang.org/t/even-more-bare-baremodule/56156>\
**Category:** Internals & Design\
**Created:** [February 27, 2021, 9:11pm UTC](https://discourse.julialang.org/t/even-more-bare-baremodule/56156 "2021-02-27T21:11:48Z")\
**Posts on this page:** 1\
**Showing post:** 2

<div class="post-metadata">

**Author:** ![ColinCaine](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/colincaine/32/7211_2.png) [@ColinCaine](https://discourse.julialang.org/u/ColinCaine)\
**Post date:** [February 28, 2021, 12:43am UTC](https://discourse.julialang.org/t/even-more-bare-baremodule/56156/2 "2021-02-28T00:43:31Z")

</div>

Sandboxes are useful for defining smaller teaching languages (see Racket and the HTDP2e book) and, if they can be made secure, for evaluating untrusted code in a capability programming paradigm.

Here’s my attempt at building a sandbox with the currently available tools. Big thanks to Mason (who wrote the first version) and Thautwarm (who wrote MLStyle.jl).

Executing this code should dump you in a Sandbox REPL where the only literals available are `true` and `false` and the only function is `nand(a, b)`. You are invited to build your own computer from there 😉

It’s just a proof of concept, so don’t mind the slightly strange design decisions.

```julia
# With thanks to Mason Protter and Thautwarm :)

using ReplMaker, MLStyle

baremodule Sandbox end
@eval Sandbox nand(x, y) = $(!)($(&)(x, y))

name_shadow_dict = let 
    # ccall isn't in Core, it's a special-cased thing that appears in the AST
    # as a regular function call. I think it's the only one but idk.
    forbidden_names = setdiff([:ccall, names(Core; all=true, imported=true)...],
                              names(Sandbox; all=true, imported=true))
    Dict(n => gensym() for n in forbidden_names)
end
inverse_name_shadow_dict = Dict(reverse.(collect(name_shadow_dict)))

function sandbox_eval_expr(ex)
    filterer(ex) = @match ex begin
        x::Bool => x

        x::LineNumberNode => x

        # Ban everything.
        # Edit anything you want to allow.

        # It's a bit complicated to work out when a symbol is being used as a
        # reference and what scope it is in. I think the JuliaVariables people
        # have maybe done something with that?
        #
        # Anyway, we can keep it simple and just shadow all symbols accessible
        # in a baremodule.
        s::Symbol => get(name_shadow_dict, s, s)

        # Not allowed to import stuff
        Expr(:using, _...) ||
        Expr(:import, _...) => error("Imports are not permitted in the sandbox: $ex")

        # Julia literals, these are mostly or entirely harmless (I think)
        # but you might want to omit them or rewrite them to something else or whatever.
        ::Bool ||
        ::Number ||
        ::String ||
        Expr(:string, _...) || # string interpolation
        ::QuoteNode ||
        Expr(:quote, _...) ||
        Expr(:ref, _...) || # a[i], but also Int[]
        Expr(:typed_vcat, _...) ||
        Expr(:typed_hcat, _...) ||
        Expr(:vect, _...) ||
        Expr(:vcat, _...) ||
        Expr(:hcat, _...) ||
        Expr(:tuple, _...) || # also covers named tuples
        Expr(:comprehension, _...) ||
        Expr(:typed_comprehension, _...) ||
        Expr(:generator, _...) => error("This literal is not permitted in the sandbox: $ex")

        # Recurse
        Expr(head, args...) => Expr(head, filterer.(args)...)

        x => error("Unknown node type: $x")
    end
    # The try-catch is just fixing up error messages, you can ignore it.
    :(
        try
            (@eval Sandbox $(filterer(ex)))
        catch e
            if e isa UndefVarError && haskey(inverse_name_shadow_dict, e.var)
                rethrow(UndefVarError(inverse_name_shadow_dict[e.var]))
            else
                rethrow()
            end
        end
    )
end

function sandbox_parser(s::String)
    ex = Meta.parse(s)
    sandbox_eval_expr(ex)
end

function valid_julia(s)
    input = String(take!(copy(ReplMaker.LineEdit.buffer(s))))
    ex = Meta.parse(input)
    !(ex isa Expr && ex.head == :incomplete)
end

sandbox_mode = initrepl(sandbox_parser;
                        prompt_text="Sandbox> ",
                        prompt_color = :yellow,
                        startup_text = false,
                        mode_name = :sandbox,
                        valid_input_checker = valid_julia)

enter_mode!(sandbox_mode)

```

Edit: you can also escape this with `module X end; X.eval(...)`, but that’s trivial to ban.

---

_[View the full topic](https://discourse.julialang.org/t/even-more-bare-baremodule/56156)._
