do
    -- Don't allow error() to be replaced out from underneath the security checks!
    local real_error = error

    do
        local real_collectgarbage = collectgarbage

        collectgarbage = function(oper)
            if oper == 'collect' or oper == 'count' then
                return real_collectgarbage(oper)
            end

            real_error(string.format('Security violation calling collectgarbage(): Operation %q not permitted.', oper), 2)
        end
    end

    do
        -- If no proxy is provided then permit access to the default print().  If the desire is to have print() be a no-op then the pre-sandbox code can set "printproxy = function() end".
        if printproxy then
            local real_printproxy = printproxy

            local function print_base(is_write, ...)
                -- Map each argument with tostring() and pass the result as a table to the proxy.

                -- is_write allows the proxy to differentiate between print() and io.write().  This is important since in the Lua baselib, print() places tabs between arguments and a newline at the end, while io.write() does neither.

                -- Note that we accept more than io.write() does, which only accepts strings and numbers.  I'm fine with this.
                local itemCount = select('#', ...)
                local items = { is_write=is_write, n=itemCount }

                for n=1, itemCount do
                    local e = select(n, ...)

                    table.insert(items, tostring(e))
                end

                return real_printproxy(items)
            end

            print = function(...)
                return print_base(false, ...)
            end

            io.write = function(...)
                return print_base(true, ...)
            end

            -- Make the proxy inaccessible directly.
            printproxy = nil
        end
    end

    do
        -- Critical functions we need for the security check; don't allow it to be replaced.
        local real_string_byte = string.byte
        local real_string_len = string.len
        local real_rawequal = rawequal

        local function is_bytecode(str)
            return real_string_byte(str, 1) == 27
        end

        local real_loadstring = loadstring

        loadstring = function(str, chunkname)
            if is_bytecode(str) then
                real_error('Security violation calling loadstring(): Argument is Lua bytecode.', 2)
            end

            return real_loadstring(str, chunkname)
        end

        local real_load = load

        load = function(func, chunkname)
            local checked = false

            local function delegate_func()
                local piece = func()

                if not checked and not rawequal(piece, nil) and real_string_len(piece) ~= 0 then
                    if is_bytecode(piece) then
                        real_error('Security violation calling load(): Argument is Lua bytecode.', 2)
                    end

                    checked = true
                end

                return piece
            end

            return real_load(delegate_func, chunkname)
        end
    end

    do
        local real_pcall = pcall

        local real_string_find = string.find

        local function test_result(flag, ...)
            if not flag then
                -- Test to see if this is a special uncatchable error.
                local error_text = select(1, ...)

                if real_string_find(error_text, '%$UNCATCHABLE%$') then
                    real_error(error_text, 0)
                end
            end

            return flag, ...
        end

        local function handle_xpcall(err, flag, ...)
            if not flag then
                return false, err(...)
            end

            return true, ...
        end

        pcall = function(...)
            return test_result(real_pcall(...))
        end

        xpcall = function(f, err)
            return handle_xpcall(err, test_result(real_pcall(f)))
        end
    end
end

dofile = nil    -- IO
loadfile = nil  -- IO

-- These are theoretically fine since we sandbox the global environment.  They can be permitted if there is a demonstrable need.
getfenv = nil
setfenv = nil

-- These could be made to be relatively secure, but it's more work than necessary for our needs.
module = nil
require = nil
package = nil

-- This could theoretically be used to examine the implementation of protected functions.
string.dump = nil

io = { write=io.write }

-- Allows too much access to the host.
debug = nil

newproxy = nil

os = {
    clock       = os.clock,
    difftime    = os.difftime,
    time        = os.time
}

-- Note that we do permit get/setmetatable, raw*, and we don't protect public tables (like math) since the Lua environment is not shared between scripts with different authors.  An author can shoot himself in the foot, but no-one else.