From 3a8c7888807e4483bbdb3edd81c9893f3e2f427d Mon Sep 17 00:00:00 2001
From: ShadowNinja <shadowninja@minetest.net>
Date: Fri, 5 Sep 2014 20:08:51 -0400
Subject: [PATCH] Add mod security

Due to compatibility concerns, this is temporarily disabled.
---
 build/android/jni/Android.mk      |   3 +-
 doc/lua_api.txt                   |   4 +
 minetest.conf.example             |   3 +
 src/defaultsettings.cpp           |   1 +
 src/filesys.cpp                   |  13 +
 src/filesys.h                     |  10 +-
 src/script/common/c_internal.cpp  |   4 +-
 src/script/cpp_api/CMakeLists.txt |   3 +-
 src/script/cpp_api/s_base.cpp     |  44 +--
 src/script/cpp_api/s_base.h       |  15 +-
 src/script/cpp_api/s_security.cpp | 603 ++++++++++++++++++++++++++++++
 src/script/cpp_api/s_security.h   |  70 ++++
 src/script/lua_api/l_mapgen.cpp   |   7 +-
 src/script/lua_api/l_server.cpp   |   3 +-
 src/script/lua_api/l_settings.cpp |   2 +
 src/script/lua_api/l_util.cpp     |  14 +-
 src/script/scripting_game.cpp     |   7 +-
 src/script/scripting_game.h       |  18 +-
 src/script/scripting_mainmenu.cpp |   2 -
 src/server.cpp                    |  44 ++-
 src/server.h                      |   4 +-
 src/settings.cpp                  |  15 +-
 22 files changed, 809 insertions(+), 80 deletions(-)
 create mode 100644 src/script/cpp_api/s_security.cpp
 create mode 100644 src/script/cpp_api/s_security.h

diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk
index 206c30ccf..f78b78b9b 100644
--- a/build/android/jni/Android.mk
+++ b/build/android/jni/Android.mk
@@ -262,6 +262,7 @@ LOCAL_SRC_FILES +=                                \
 		jni/src/script/common/c_converter.cpp     \
 		jni/src/script/common/c_internal.cpp      \
 		jni/src/script/common/c_types.cpp         \
+		jni/src/script/cpp_api/s_async.cpp        \
 		jni/src/script/cpp_api/s_base.cpp         \
 		jni/src/script/cpp_api/s_entity.cpp       \
 		jni/src/script/cpp_api/s_env.cpp          \
@@ -271,8 +272,8 @@ LOCAL_SRC_FILES +=                                \
 		jni/src/script/cpp_api/s_node.cpp         \
 		jni/src/script/cpp_api/s_nodemeta.cpp     \
 		jni/src/script/cpp_api/s_player.cpp       \
+		jni/src/script/cpp_api/s_security.cpp     \
 		jni/src/script/cpp_api/s_server.cpp       \
-		jni/src/script/cpp_api/s_async.cpp        \
 		jni/src/script/lua_api/l_base.cpp         \
 		jni/src/script/lua_api/l_craft.cpp        \
 		jni/src/script/lua_api/l_env.cpp          \
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 0cc83bf69..2421af069 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -1825,8 +1825,12 @@ Call these functions only at load time!
 
 ### Setting-related
 * `minetest.setting_set(name, value)`
+    * Setting names can't contain whitespace or any of `="{}#`.
+    * Setting values can't contain the sequence `\n"""`.
+    * Setting names starting with "secure." can't be set.
 * `minetest.setting_get(name)`: returns string or `nil`
 * `minetest.setting_setbool(name, value)`
+    * See documentation on `setting_set` for restrictions.
 * `minetest.setting_getbool(name)`: returns boolean or `nil`
 * `minetest.setting_get_pos(name)`: returns position or nil
 * `minetest.setting_save()`, returns `nil`, save all settings to config file
diff --git a/minetest.conf.example b/minetest.conf.example
index 4e3e97b95..6474289bd 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -569,3 +569,6 @@
 #mgv7_np_cave1 = 0, 12, (100, 100, 100), 52534, 4, 0.5, 2.0
 #mgv7_np_cave2 = 0, 12, (100, 100, 100), 10325, 4, 0.5, 2.0
 
+#    Prevent mods from doing insecure things like running shell commands.
+#secure.enable_security = false
+
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 45188f791..f26b4c8ad 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -272,6 +272,7 @@ void set_default_settings(Settings *settings)
 	settings->setDefault("emergequeue_limit_diskonly", "32");
 	settings->setDefault("emergequeue_limit_generate", "32");
 	settings->setDefault("num_emerge_threads", "1");
+	settings->setDefault("secure.enable_security", "false");
 
 	// physics stuff
 	settings->setDefault("movement_acceleration_default", "3");
diff --git a/src/filesys.cpp b/src/filesys.cpp
index 4a4a2e418..9aeecf427 100644
--- a/src/filesys.cpp
+++ b/src/filesys.cpp
@@ -662,6 +662,19 @@ std::string RemoveRelativePathComponents(std::string path)
 	return path.substr(0, pos);
 }
 
+std::string AbsolutePath(const std::string &path)
+{
+#ifdef _WIN32
+	char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH);
+#else
+	char *abs_path = realpath(path.c_str(), NULL);
+#endif
+	if (!abs_path) return "";
+	std::string abs_path_str(abs_path);
+	free(abs_path);
+	return abs_path_str;
+}
+
 const char *GetFilenameFromPath(const char *path)
 {
 	const char *filename = strrchr(path, DIR_DELIM_CHAR);
diff --git a/src/filesys.h b/src/filesys.h
index 7560d3c15..19fcbb673 100644
--- a/src/filesys.h
+++ b/src/filesys.h
@@ -103,13 +103,17 @@ std::string RemoveLastPathComponent(const std::string &path,
 // this does not resolve symlinks and check for existence of directories.
 std::string RemoveRelativePathComponents(std::string path);
 
-// Return the filename from a path or the entire path if no directory delimiter
-// is found.
+// Returns the absolute path for the passed path, with "." and ".." path
+// components and symlinks removed.  Returns "" on error.
+std::string AbsolutePath(const std::string &path);
+
+// Returns the filename from a path or the entire path if no directory
+// delimiter is found.
 const char *GetFilenameFromPath(const char *path);
 
 bool safeWriteToFile(const std::string &path, const std::string &content);
 
-}//fs
+} // namespace fs
 
 #endif
 
diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp
index fcab98dc6..8cf39dc3a 100644
--- a/src/script/common/c_internal.cpp
+++ b/src/script/common/c_internal.cpp
@@ -63,10 +63,8 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f)
 		return f(L);  // Call wrapped function and return result.
 	} catch (const char *s) {  // Catch and convert exceptions.
 		lua_pushstring(L, s);
-	} catch (std::exception& e) {
+	} catch (std::exception &e) {
 		lua_pushstring(L, e.what());
-	} catch (...) {
-		lua_pushliteral(L, "caught (...)");
 	}
 	return lua_error(L);  // Rethrow as a Lua error.
 }
diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt
index 4584962f1..be4d0131e 100644
--- a/src/script/cpp_api/CMakeLists.txt
+++ b/src/script/cpp_api/CMakeLists.txt
@@ -1,4 +1,5 @@
 set(common_SCRIPT_CPP_API_SRCS
+	${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/s_base.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/s_entity.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp
@@ -7,8 +8,8 @@ set(common_SCRIPT_CPP_API_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/s_node.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/s_player.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/s_security.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/s_server.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp
 	PARENT_SCOPE)
 
 set(client_SCRIPT_CPP_API_SRCS
diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp
index 71473d215..4fb8411ef 100644
--- a/src/script/cpp_api/s_base.cpp
+++ b/src/script/cpp_api/s_base.cpp
@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "cpp_api/s_base.h"
 #include "cpp_api/s_internal.h"
+#include "cpp_api/s_security.h"
 #include "lua_api/l_object.h"
 #include "serverobject.h"
 #include "debug.h"
@@ -45,18 +46,18 @@ class ModNameStorer
 private:
 	lua_State *L;
 public:
-	ModNameStorer(lua_State *L_, const std::string &modname):
+	ModNameStorer(lua_State *L_, const std::string &mod_name):
 		L(L_)
 	{
-		// Store current modname in registry
-		lua_pushstring(L, modname.c_str());
-		lua_setfield(L, LUA_REGISTRYINDEX, "current_modname");
+		// Store current mod name in registry
+		lua_pushstring(L, mod_name.c_str());
+		lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD);
 	}
 	~ModNameStorer()
 	{
-		// Clear current modname in registry
+		// Clear current mod name from registry
 		lua_pushnil(L);
-		lua_setfield(L, LUA_REGISTRYINDEX, "current_modname");
+		lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD);
 	}
 };
 
@@ -112,32 +113,31 @@ ScriptApiBase::~ScriptApiBase()
 	lua_close(m_luastack);
 }
 
-bool ScriptApiBase::loadMod(const std::string &scriptpath,
-		const std::string &modname)
+bool ScriptApiBase::loadMod(const std::string &script_path,
+		const std::string &mod_name)
 {
-	ModNameStorer modnamestorer(getStack(), modname);
+	ModNameStorer mod_name_storer(getStack(), mod_name);
 
-	if (!string_allowed(modname, MODNAME_ALLOWED_CHARS)) {
-		errorstream<<"Error loading mod \""<<modname
-				<<"\": modname does not follow naming conventions: "
-				<<"Only chararacters [a-z0-9_] are allowed."<<std::endl;
-		return false;
-	}
-
-	return loadScript(scriptpath);
+	return loadScript(script_path);
 }
 
-bool ScriptApiBase::loadScript(const std::string &scriptpath)
+bool ScriptApiBase::loadScript(const std::string &script_path)
 {
-	verbosestream<<"Loading and running script from "<<scriptpath<<std::endl;
+	verbosestream << "Loading and running script from " << script_path << std::endl;
 
 	lua_State *L = getStack();
 
-	int ret = luaL_loadfile(L, scriptpath.c_str()) || lua_pcall(L, 0, 0, m_errorhandler);
-	if (ret) {
+	bool ok;
+	if (m_secure) {
+		ok = ScriptApiSecurity::safeLoadFile(L, script_path.c_str());
+	} else {
+		ok = !luaL_loadfile(L, script_path.c_str());
+	}
+	ok = ok && !lua_pcall(L, 0, 0, m_errorhandler);
+	if (!ok) {
 		errorstream << "========== ERROR FROM LUA ===========" << std::endl;
 		errorstream << "Failed to load and run script from " << std::endl;
-		errorstream << scriptpath << ":" << std::endl;
+		errorstream << script_path << ":" << std::endl;
 		errorstream << std::endl;
 		errorstream << lua_tostring(L, -1) << std::endl;
 		errorstream << std::endl;
diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h
index 4ea3677a9..cf9b7b934 100644
--- a/src/script/cpp_api/s_base.h
+++ b/src/script/cpp_api/s_base.h
@@ -35,6 +35,12 @@ extern "C" {
 
 #define SCRIPTAPI_LOCK_DEBUG
 
+#define SCRIPT_MOD_NAME_FIELD "current_mod_name"
+// MUST be an invalid mod name so that mods can't
+// use that name to bypass security!
+#define BUILTIN_MOD_NAME "*builtin*"
+
+
 class Server;
 class Environment;
 class GUIEngine;
@@ -42,17 +48,18 @@ class ServerActiveObject;
 
 class ScriptApiBase {
 public:
-
 	ScriptApiBase();
 	virtual ~ScriptApiBase();
 
-	bool loadMod(const std::string &scriptpath, const std::string &modname);
-	bool loadScript(const std::string &scriptpath);
+	bool loadMod(const std::string &script_path, const std::string &mod_name);
+	bool loadScript(const std::string &script_path);
 
 	/* object */
 	void addObjectReference(ServerActiveObject *cobj);
 	void removeObjectReference(ServerActiveObject *cobj);
 
+	Server* getServer() { return m_server; }
+
 protected:
 	friend class LuaABM;
 	friend class InvRef;
@@ -69,7 +76,6 @@ class ScriptApiBase {
 	void scriptError();
 	void stackDump(std::ostream &o);
 
-	Server* getServer() { return m_server; }
 	void setServer(Server* server) { m_server = server; }
 
 	Environment* getEnv() { return m_environment; }
@@ -84,6 +90,7 @@ class ScriptApiBase {
 	JMutex          m_luastackmutex;
 	// Stack index of Lua error handler
 	int             m_errorhandler;
+	bool            m_secure;
 #ifdef SCRIPTAPI_LOCK_DEBUG
 	bool            m_locked;
 #endif
diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp
new file mode 100644
index 000000000..abe5b3e97
--- /dev/null
+++ b/src/script/cpp_api/s_security.cpp
@@ -0,0 +1,603 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "cpp_api/s_security.h"
+
+#include "filesys.h"
+#include "porting.h"
+#include "server.h"
+#include "settings.h"
+
+#include <cerrno>
+#include <string>
+#include <iostream>
+
+
+#define SECURE_API(lib, name) \
+	lua_pushcfunction(L, sl_##lib##_##name); \
+	lua_setfield(L, -2, #name);
+
+
+static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1)
+{
+	if (from < 0) from = lua_gettop(L) + from + 1;
+	if (to   < 0) to   = lua_gettop(L) + to   + 1;
+	for (unsigned i = 0; i < (len / sizeof(list[0])); i++) {
+		lua_getfield(L, from, list[i]);
+		lua_setfield(L, to,   list[i]);
+	}
+}
+
+// Pushes the original version of a library function on the stack, from the old version
+static inline void push_original(lua_State *L, const char *lib, const char *func)
+{
+	lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup");
+	lua_getfield(L, -1, lib);
+	lua_remove(L, -2);  // Remove globals_backup
+	lua_getfield(L, -1, func);
+	lua_remove(L, -2);  // Remove lib
+}
+
+
+void ScriptApiSecurity::initializeSecurity()
+{
+	static const char *whitelist[] = {
+		"assert",
+		"core",
+		"collectgarbage",
+		"DIR_DELIM",
+		"error",
+		"getfenv",
+		"getmetatable",
+		"ipairs",
+		"next",
+		"pairs",
+		"pcall",
+		"print",
+		"rawequal",
+		"rawget",
+		"rawset",
+		"select",
+		"setfenv",
+		"setmetatable",
+		"tonumber",
+		"tostring",
+		"type",
+		"unpack",
+		"_VERSION",
+		"xpcall",
+		// Completely safe libraries
+		"coroutine",
+		"string",
+		"table",
+		"math",
+	};
+	static const char *io_whitelist[] = {
+		"close",
+		"flush",
+		"read",
+		"type",
+		"write",
+	};
+	static const char *os_whitelist[] = {
+		"clock",
+		"date",
+		"difftime",
+		"exit",
+		"getenv",
+		"setlocale",
+		"time",
+		"tmpname",
+	};
+	static const char *debug_whitelist[] = {
+		"gethook",
+		"traceback",
+		"getinfo",
+		"getmetatable",
+		"setupvalue",
+		"setmetatable",
+		"upvalueid",
+		"upvaluejoin",
+		"sethook",
+		"debug",
+		"getupvalue",
+		"setlocal",
+	};
+	static const char *package_whitelist[] = {
+		"config",
+		"cpath",
+		"path",
+		"searchpath",
+	};
+	static const char *jit_whitelist[] = {
+		"arch",
+		"flush",
+		"off",
+		"on",
+		"opt",
+		"os",
+		"status",
+		"version",
+		"version_num",
+	};
+
+	m_secure = true;
+
+	lua_State *L = getStack();
+
+	// Backup globals to the registry
+	lua_getglobal(L, "_G");
+	lua_setfield(L, LUA_REGISTRYINDEX, "globals_backup");
+
+	// Replace the global environment with an empty one
+#if LUA_VERSION_NUM <= 501
+	int is_main = lua_pushthread(L);  // Push the main thread
+	FATAL_ERROR_IF(!is_main, "Security: ScriptApi's Lua state "
+			"isn't the main Lua thread!");
+#endif
+	lua_newtable(L);  // Create new environment
+	lua_pushvalue(L, -1);
+	lua_setfield(L, -2, "_G");  // Set _G of new environment
+#if LUA_VERSION_NUM >= 502  // Lua >= 5.2
+	// Set the global environment
+	lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
+#else  // Lua <= 5.1
+	// Set the environment of the main thread
+	FATAL_ERROR_IF(!lua_setfenv(L, -2), "Security: Unable to set "
+			"environment of the main Lua thread!");
+	lua_pop(L, 1);  // Pop thread
+#endif
+
+	// Get old globals
+	lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup");
+	int old_globals = lua_gettop(L);
+
+
+	// Copy safe base functions
+	lua_getglobal(L, "_G");
+	copy_safe(L, whitelist, sizeof(whitelist));
+
+	// And replace unsafe ones
+	SECURE_API(g, dofile);
+	SECURE_API(g, load);
+	SECURE_API(g, loadfile);
+	SECURE_API(g, loadstring);
+	SECURE_API(g, require);
+	lua_pop(L, 1);
+
+
+	// Copy safe IO functions
+	lua_getfield(L, old_globals, "io");
+	lua_newtable(L);
+	copy_safe(L, io_whitelist, sizeof(io_whitelist));
+
+	// And replace unsafe ones
+	SECURE_API(io, open);
+	SECURE_API(io, input);
+	SECURE_API(io, output);
+	SECURE_API(io, lines);
+
+	lua_setglobal(L, "io");
+	lua_pop(L, 1);  // Pop old IO
+
+
+	// Copy safe OS functions
+	lua_getfield(L, old_globals, "os");
+	lua_newtable(L);
+	copy_safe(L, os_whitelist, sizeof(os_whitelist));
+
+	// And replace unsafe ones
+	SECURE_API(os, remove);
+	SECURE_API(os, rename);
+
+	lua_setglobal(L, "os");
+	lua_pop(L, 1);  // Pop old OS
+
+
+	// Copy safe debug functions
+	lua_getfield(L, old_globals, "debug");
+	lua_newtable(L);
+	copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
+	lua_setglobal(L, "debug");
+	lua_pop(L, 1);  // Pop old debug
+
+
+	// Copy safe package fields
+	lua_getfield(L, old_globals, "package");
+	lua_newtable(L);
+	copy_safe(L, package_whitelist, sizeof(package_whitelist));
+	lua_setglobal(L, "package");
+	lua_pop(L, 1);  // Pop old package
+
+
+	// Copy safe jit functions, if they exist
+	lua_getfield(L, -1, "jit");
+	if (!lua_isnil(L, -1)) {
+		lua_newtable(L);
+		copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
+		lua_setglobal(L, "jit");
+	}
+	lua_pop(L, 1);  // Pop old jit
+
+	lua_pop(L, 1); // Pop globals_backup
+}
+
+
+bool ScriptApiSecurity::isSecure(lua_State *L)
+{
+	lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup");
+	bool secure = !lua_isnil(L, -1);
+	lua_pop(L, 1);
+	return secure;
+}
+
+
+#define CHECK_FILE_ERR(ret, fp) \
+	if (ret) { \
+		if (fp) std::fclose(fp); \
+		lua_pushfstring(L, "%s: %s", path, strerror(errno)); \
+		return false; \
+	}
+
+
+bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path)
+{
+	FILE *fp;
+	char *chunk_name;
+	if (path == NULL) {
+		fp = stdin;
+		chunk_name = const_cast<char *>("=stdin");
+	} else {
+		fp = fopen(path, "r");
+		if (!fp) {
+			lua_pushfstring(L, "%s: %s", path, strerror(errno));
+			return false;
+		}
+		chunk_name = new char[strlen(path) + 2];
+		chunk_name[0] = '@';
+		chunk_name[1] = '\0';
+		strcat(chunk_name, path);
+	}
+
+	size_t start = 0;
+	int c = std::getc(fp);
+	if (c == '#') {
+		// Skip the first line
+		while ((c = std::getc(fp)) != EOF && c != '\n');
+		if (c == '\n') c = std::getc(fp);
+		start = std::ftell(fp);
+	}
+
+	if (c == LUA_SIGNATURE[0]) {
+		lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
+		return false;
+	}
+
+	// Read the file
+	int ret = std::fseek(fp, 0, SEEK_END);
+	CHECK_FILE_ERR(ret, fp);
+	if (ret) {
+		std::fclose(fp);
+		lua_pushfstring(L, "%s: %s", path, strerror(errno));
+		return false;
+	}
+	size_t size = std::ftell(fp) - start;
+	char *code = new char[size];
+	ret = std::fseek(fp, start, SEEK_SET);
+	CHECK_FILE_ERR(ret, fp);
+	if (ret) {
+		std::fclose(fp);
+		lua_pushfstring(L, "%s: %s", path, strerror(errno));
+		return false;
+	}
+	size_t num_read = std::fread(code, 1, size, fp);
+	if (path) {
+		std::fclose(fp);
+	}
+	if (num_read != size) {
+		lua_pushliteral(L, "Error reading file to load.");
+		return false;
+	}
+
+	if (luaL_loadbuffer(L, code, size, chunk_name)) {
+		return false;
+	}
+
+	if (path) {
+		delete [] chunk_name;
+	}
+	return true;
+}
+
+
+bool ScriptApiSecurity::checkPath(lua_State *L, const char *path)
+{
+	std::string str;  // Transient
+
+	std::string norel_path = fs::RemoveRelativePathComponents(path);
+	std::string abs_path = fs::AbsolutePath(norel_path);
+
+	if (!abs_path.empty()) {
+		// Don't allow accessing the settings file
+		str = fs::AbsolutePath(g_settings_path);
+		if (str == abs_path) return false;
+	}
+
+	// If we couldn't find the absolute path (path doesn't exist) then
+	// try removing the last components until it works (to allow
+	// non-existent files/folders for mkdir).
+	std::string cur_path = norel_path;
+	std::string removed;
+	while (abs_path.empty() && !cur_path.empty()) {
+		std::string tmp_rmed;
+		cur_path = fs::RemoveLastPathComponent(cur_path, &tmp_rmed);
+		removed = tmp_rmed + (removed.empty() ? "" : DIR_DELIM + removed);
+		abs_path = fs::AbsolutePath(cur_path);
+	}
+	if (abs_path.empty()) return false;
+	// Add the removed parts back so that you can't, eg, create a
+	// directory in worldmods if worldmods doesn't exist.
+	if (!removed.empty()) abs_path += DIR_DELIM + removed;
+
+	// Get server from registry
+	lua_getfield(L, LUA_REGISTRYINDEX, "scriptapi");
+	ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
+	lua_pop(L, 1);
+	const Server *server = script->getServer();
+
+	if (!server) return false;
+
+	// Get mod name
+	lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD);
+	if (lua_isstring(L, -1)) {
+		std::string mod_name = lua_tostring(L, -1);
+
+		// Builtin can access anything
+		if (mod_name == BUILTIN_MOD_NAME) {
+			return true;
+		}
+
+		// Allow paths in mod path
+		const ModSpec *mod = server->getModSpec(mod_name);
+		if (mod) {
+			str = fs::AbsolutePath(mod->path);
+			if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
+				return true;
+			}
+		}
+	}
+	lua_pop(L, 1);  // Pop mod name
+
+	str = fs::AbsolutePath(server->getWorldPath());
+	if (str.empty()) return false;
+	// Don't allow access to world mods.  We add to the absolute path
+	// of the world instead of getting the absolute paths directly
+	// because that won't work if they don't exist.
+	if (fs::PathStartsWith(abs_path, str + DIR_DELIM + "worldmods") ||
+			fs::PathStartsWith(abs_path, str + DIR_DELIM + "game")) {
+		return false;
+	}
+	// Allow all other paths in world path
+	if (fs::PathStartsWith(abs_path, str)) {
+		return true;
+	}
+
+	// Default to disallowing
+	return false;
+}
+
+
+int ScriptApiSecurity::sl_g_dofile(lua_State *L)
+{
+	int nret = sl_g_loadfile(L);
+	if (nret != 1) {
+		return nret;
+	}
+	int top_precall = lua_gettop(L);
+	lua_call(L, 0, LUA_MULTRET);
+	// Return number of arguments returned by the function,
+	// adjusting for the function being poped.
+	return lua_gettop(L) - (top_precall - 1);
+}
+
+
+int ScriptApiSecurity::sl_g_load(lua_State *L)
+{
+	size_t len;
+	const char *buf;
+	std::string code;
+	const char *chunk_name = "=(load)";
+
+	luaL_checktype(L, 1, LUA_TFUNCTION);
+	if (!lua_isnone(L, 2)) {
+		luaL_checktype(L, 2, LUA_TSTRING);
+		chunk_name = lua_tostring(L, 2);
+	}
+
+	while (true) {
+		lua_pushvalue(L, 1);
+		lua_call(L, 0, 1);
+		int t = lua_type(L, -1);
+		if (t == LUA_TNIL) {
+			break;
+		} else if (t != LUA_TSTRING) {
+			lua_pushnil(L);
+			lua_pushliteral(L, "Loader didn't return a string");
+			return 2;
+		}
+		buf = lua_tolstring(L, -1, &len);
+		code += std::string(buf, len);
+		lua_pop(L, 1); // Pop return value
+	}
+	if (code[0] == LUA_SIGNATURE[0]) {
+		lua_pushnil(L);
+		lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
+		return 2;
+	}
+	if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name)) {
+		lua_pushnil(L);
+		lua_insert(L, lua_gettop(L) - 1);
+		return 2;
+	}
+	return 1;
+}
+
+
+int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
+{
+	const char *path = NULL;
+
+	if (lua_isstring(L, 1)) {
+		path = lua_tostring(L, 1);
+		CHECK_SECURE_PATH(L, path);
+	}
+
+	if (!safeLoadFile(L, path)) {
+		lua_pushnil(L);
+		lua_insert(L, -2);
+		return 2;
+	}
+
+	return 1;
+}
+
+
+int ScriptApiSecurity::sl_g_loadstring(lua_State *L)
+{
+	const char *chunk_name = "=(load)";
+
+	luaL_checktype(L, 1, LUA_TSTRING);
+	if (!lua_isnone(L, 2)) {
+		luaL_checktype(L, 2, LUA_TSTRING);
+		chunk_name = lua_tostring(L, 2);
+	}
+
+	size_t size;
+	const char *code = lua_tolstring(L, 1, &size);
+
+	if (size > 0 && code[0] == LUA_SIGNATURE[0]) {
+		lua_pushnil(L);
+		lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
+		return 2;
+	}
+	if (luaL_loadbuffer(L, code, size, chunk_name)) {
+		lua_pushnil(L);
+		lua_insert(L, lua_gettop(L) - 1);
+		return 2;
+	}
+	return 1;
+}
+
+
+int ScriptApiSecurity::sl_g_require(lua_State *L)
+{
+	lua_pushliteral(L, "require() is disabled when mod security is on.");
+	return lua_error(L);
+}
+
+
+int ScriptApiSecurity::sl_io_open(lua_State *L)
+{
+	luaL_checktype(L, 1, LUA_TSTRING);
+	const char *path = lua_tostring(L, 1);
+	CHECK_SECURE_PATH(L, path);
+
+	push_original(L, "io", "open");
+	lua_pushvalue(L, 1);
+	lua_pushvalue(L, 2);
+	lua_call(L, 2, 2);
+	return 2;
+}
+
+
+int ScriptApiSecurity::sl_io_input(lua_State *L)
+{
+	if (lua_isstring(L, 1)) {
+		const char *path = lua_tostring(L, 1);
+		CHECK_SECURE_PATH(L, path);
+	}
+
+	push_original(L, "io", "input");
+	lua_pushvalue(L, 1);
+	lua_call(L, 1, 1);
+	return 1;
+}
+
+
+int ScriptApiSecurity::sl_io_output(lua_State *L)
+{
+	if (lua_isstring(L, 1)) {
+		const char *path = lua_tostring(L, 1);
+		CHECK_SECURE_PATH(L, path);
+	}
+
+	push_original(L, "io", "output");
+	lua_pushvalue(L, 1);
+	lua_call(L, 1, 1);
+	return 1;
+}
+
+
+int ScriptApiSecurity::sl_io_lines(lua_State *L)
+{
+	if (lua_isstring(L, 1)) {
+		const char *path = lua_tostring(L, 1);
+		CHECK_SECURE_PATH(L, path);
+	}
+
+	push_original(L, "io", "lines");
+	lua_pushvalue(L, 1);
+	int top_precall = lua_gettop(L);
+	lua_call(L, 1, LUA_MULTRET);
+	// Return number of arguments returned by the function,
+	// adjusting for the function being poped.
+	return lua_gettop(L) - (top_precall - 1);
+}
+
+
+int ScriptApiSecurity::sl_os_rename(lua_State *L)
+{
+	luaL_checktype(L, 1, LUA_TSTRING);
+	const char *path1 = lua_tostring(L, 1);
+	CHECK_SECURE_PATH(L, path1);
+
+	luaL_checktype(L, 2, LUA_TSTRING);
+	const char *path2 = lua_tostring(L, 2);
+	CHECK_SECURE_PATH(L, path2);
+
+	push_original(L, "os", "rename");
+	lua_pushvalue(L, 1);
+	lua_pushvalue(L, 2);
+	lua_call(L, 2, 2);
+	return 2;
+}
+
+
+int ScriptApiSecurity::sl_os_remove(lua_State *L)
+{
+	luaL_checktype(L, 1, LUA_TSTRING);
+	const char *path = lua_tostring(L, 1);
+	CHECK_SECURE_PATH(L, path);
+
+	push_original(L, "os", "remove");
+	lua_pushvalue(L, 1);
+	lua_call(L, 1, 2);
+	return 2;
+}
+
diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h
new file mode 100644
index 000000000..4a4389cf5
--- /dev/null
+++ b/src/script/cpp_api/s_security.h
@@ -0,0 +1,70 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef S_SECURITY_H
+#define S_SECURITY_H
+
+#include "cpp_api/s_base.h"
+
+
+#define CHECK_SECURE_PATH(L, path) \
+	if (!ScriptApiSecurity::checkPath(L, path)) { \
+		lua_pushstring(L, (std::string("Attempt to access external file ") + \
+					path + " with mod security on.").c_str()); \
+		lua_error(L); \
+	}
+#define CHECK_SECURE_PATH_OPTIONAL(L, path) \
+	if (ScriptApiSecurity::isSecure(L)) { \
+		CHECK_SECURE_PATH(L, path); \
+	}
+
+
+class ScriptApiSecurity : virtual public ScriptApiBase
+{
+public:
+	// Sets up security on the ScriptApi's Lua state
+	void initializeSecurity();
+	// Checks if the Lua state has been secured
+	static bool isSecure(lua_State *L);
+	// Loads a file as Lua code safely (doesn't allow bytecode).
+	static bool safeLoadFile(lua_State *L, const char *path);
+	// Checks if mods are allowed to read and write to the path
+	static bool checkPath(lua_State *L, const char *path);
+
+private:
+	// Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name>
+	// (sl stands for Secure Lua)
+
+	static int sl_g_dofile(lua_State *L);
+	static int sl_g_load(lua_State *L);
+	static int sl_g_loadfile(lua_State *L);
+	static int sl_g_loadstring(lua_State *L);
+	static int sl_g_require(lua_State *L);
+
+	static int sl_io_open(lua_State *L);
+	static int sl_io_input(lua_State *L);
+	static int sl_io_output(lua_State *L);
+	static int sl_io_lines(lua_State *L);
+
+	static int sl_os_rename(lua_State *L);
+	static int sl_os_remove(lua_State *L);
+};
+
+#endif
+
diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp
index d94f902c4..dc3644e1c 100644
--- a/src/script/lua_api/l_mapgen.cpp
+++ b/src/script/lua_api/l_mapgen.cpp
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "lua_api/l_vmanip.h"
 #include "common/c_converter.h"
 #include "common/c_content.h"
+#include "cpp_api/s_security.h"
 #include "util/serialize.h"
 #include "server.h"
 #include "environment.h"
@@ -1031,6 +1032,10 @@ int ModApiMapgen::l_generate_decorations(lua_State *L)
 int ModApiMapgen::l_create_schematic(lua_State *L)
 {
 	INodeDefManager *ndef = getServer(L)->getNodeDefManager();
+
+	const char *filename = luaL_checkstring(L, 4);
+	CHECK_SECURE_PATH_OPTIONAL(L, filename);
+
 	Map *map = &(getEnv(L)->getMap());
 	Schematic schem;
 
@@ -1069,8 +1074,6 @@ int ModApiMapgen::l_create_schematic(lua_State *L)
 		}
 	}
 
-	const char *filename = luaL_checkstring(L, 4);
-
 	if (!schem.getSchematicFromMap(map, p1, p2)) {
 		errorstream << "create_schematic: failed to get schematic "
 			"from map" << std::endl;
diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp
index 99e73b03e..0d8926317 100644
--- a/src/script/lua_api/l_server.cpp
+++ b/src/script/lua_api/l_server.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "lua_api/l_internal.h"
 #include "common/c_converter.h"
 #include "common/c_content.h"
+#include "cpp_api/s_base.h"
 #include "server.h"
 #include "environment.h"
 #include "player.h"
@@ -342,7 +343,7 @@ int ModApiServer::l_show_formspec(lua_State *L)
 int ModApiServer::l_get_current_modname(lua_State *L)
 {
 	NO_MAP_LOCK_REQUIRED;
-	lua_getfield(L, LUA_REGISTRYINDEX, "current_modname");
+	lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD);
 	return 1;
 }
 
diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp
index 9c88a3e05..35b82b435 100644
--- a/src/script/lua_api/l_settings.cpp
+++ b/src/script/lua_api/l_settings.cpp
@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "lua_api/l_settings.h"
 #include "lua_api/l_internal.h"
+#include "cpp_api/s_security.h"
 #include "settings.h"
 #include "log.h"
 
@@ -188,6 +189,7 @@ int LuaSettings::create_object(lua_State* L)
 {
 	NO_MAP_LOCK_REQUIRED;
 	const char* filename = luaL_checkstring(L, 1);
+	CHECK_SECURE_PATH_OPTIONAL(L, filename);
 	LuaSettings* o = new LuaSettings(filename);
 	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
 	luaL_getmetatable(L, className);
diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp
index 283cca01f..151d449d5 100644
--- a/src/script/lua_api/l_util.cpp
+++ b/src/script/lua_api/l_util.cpp
@@ -92,12 +92,19 @@ int ModApiUtil::l_log(lua_State *L)
 	return 0;
 }
 
+#define CHECK_SECURE_SETTING(L, name) \
+	if (name.compare(0, 7, "secure.") == 0) {\
+		lua_pushliteral(L, "Attempt to set secure setting.");\
+		lua_error(L);\
+	}
+
 // setting_set(name, value)
 int ModApiUtil::l_setting_set(lua_State *L)
 {
 	NO_MAP_LOCK_REQUIRED;
-	const char *name = luaL_checkstring(L, 1);
-	const char *value = luaL_checkstring(L, 2);
+	std::string name = luaL_checkstring(L, 1);
+	std::string value = luaL_checkstring(L, 2);
+	CHECK_SECURE_SETTING(L, name);
 	g_settings->set(name, value);
 	return 0;
 }
@@ -120,8 +127,9 @@ int ModApiUtil::l_setting_get(lua_State *L)
 int ModApiUtil::l_setting_setbool(lua_State *L)
 {
 	NO_MAP_LOCK_REQUIRED;
-	const char *name = luaL_checkstring(L, 1);
+	std::string name = luaL_checkstring(L, 1);
 	bool value = lua_toboolean(L, 2);
+	CHECK_SECURE_SETTING(L, name);
 	g_settings->setBool(name, value);
 	return 0;
 }
diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp
index 5bcd2a33d..9321c38a9 100644
--- a/src/script/scripting_game.cpp
+++ b/src/script/scripting_game.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "scripting_game.h"
 #include "server.h"
 #include "log.h"
+#include "settings.h"
 #include "cpp_api/s_internal.h"
 #include "lua_api/l_base.h"
 #include "lua_api/l_craft.h"
@@ -49,10 +50,12 @@ GameScripting::GameScripting(Server* server)
 	// setEnv(env) is called by ScriptApiEnv::initializeEnvironment()
 	// once the environment has been created
 
-	//TODO add security
-
 	SCRIPTAPI_PRECHECKHEADER
 
+	if (g_settings->getBool("secure.enable_security")) {
+		initializeSecurity();
+	}
+
 	lua_getglobal(L, "core");
 	int top = lua_gettop(L);
 
diff --git a/src/script/scripting_game.h b/src/script/scripting_game.h
index 14dbd9170..16d5dcb37 100644
--- a/src/script/scripting_game.h
+++ b/src/script/scripting_game.h
@@ -27,19 +27,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "cpp_api/s_node.h"
 #include "cpp_api/s_player.h"
 #include "cpp_api/s_server.h"
+#include "cpp_api/s_security.h"
 
 /*****************************************************************************/
 /* Scripting <-> Game Interface                                              */
 /*****************************************************************************/
 
-class GameScripting
-		: virtual public ScriptApiBase,
-		  public ScriptApiDetached,
-		  public ScriptApiEntity,
-		  public ScriptApiEnv,
-		  public ScriptApiNode,
-		  public ScriptApiPlayer,
-		  public ScriptApiServer
+class GameScripting :
+		virtual public ScriptApiBase,
+		public ScriptApiDetached,
+		public ScriptApiEntity,
+		public ScriptApiEnv,
+		public ScriptApiNode,
+		public ScriptApiPlayer,
+		public ScriptApiServer,
+		public ScriptApiSecurity
 {
 public:
 	GameScripting(Server* server);
diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp
index 54b3133c5..c74c18edc 100644
--- a/src/script/scripting_mainmenu.cpp
+++ b/src/script/scripting_mainmenu.cpp
@@ -38,8 +38,6 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine)
 {
 	setGuiEngine(guiengine);
 
-	//TODO add security
-
 	SCRIPTAPI_PRECHECKHEADER
 
 	lua_getglobal(L, "core");
diff --git a/src/server.cpp b/src/server.cpp
index f032da406..778a93241 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -295,31 +295,37 @@ Server::Server(
 
 	m_script = new GameScripting(this);
 
-	std::string scriptpath = getBuiltinLuaPath() + DIR_DELIM "init.lua";
+	std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua";
 
-	if (!m_script->loadScript(scriptpath))
-		throw ModError("Failed to load and run " + scriptpath);
+	if (!m_script->loadMod(script_path, BUILTIN_MOD_NAME)) {
+		throw ModError("Failed to load and run " + script_path);
+	}
 
-	// Print 'em
-	infostream<<"Server: Loading mods: ";
+	// Print mods
+	infostream << "Server: Loading mods: ";
 	for(std::vector<ModSpec>::iterator i = m_mods.begin();
 			i != m_mods.end(); i++){
 		const ModSpec &mod = *i;
-		infostream<<mod.name<<" ";
+		infostream << mod.name << " ";
 	}
-	infostream<<std::endl;
+	infostream << std::endl;
 	// Load and run "mod" scripts
-	for(std::vector<ModSpec>::iterator i = m_mods.begin();
-			i != m_mods.end(); i++){
+	for (std::vector<ModSpec>::iterator i = m_mods.begin();
+			i != m_mods.end(); i++) {
 		const ModSpec &mod = *i;
-		std::string scriptpath = mod.path + DIR_DELIM + "init.lua";
-		infostream<<"  ["<<padStringRight(mod.name, 12)<<"] [\""
-				<<scriptpath<<"\"]"<<std::endl;
-		bool success = m_script->loadMod(scriptpath, mod.name);
-		if(!success){
-			errorstream<<"Server: Failed to load and run "
-					<<scriptpath<<std::endl;
-			throw ModError("Failed to load and run "+scriptpath);
+		if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
+			errorstream << "Error loading mod \"" << mod.name
+					<< "\": mod_name does not follow naming conventions: "
+					<< "Only chararacters [a-z0-9_] are allowed." << std::endl;
+			throw ModError("Mod \"" + mod.name + "\" does not follow naming conventions.");
+		}
+		std::string script_path = mod.path + DIR_DELIM "init.lua";
+		infostream << "  [" << padStringRight(mod.name, 12) << "] [\""
+				<< script_path << "\"]" << std::endl;
+		if (!m_script->loadMod(script_path, mod.name)) {
+			errorstream << "Server: Failed to load and run "
+					<< script_path << std::endl;
+			throw ModError("Failed to load and run " + script_path);
 		}
 	}
 
@@ -3206,9 +3212,9 @@ IWritableCraftDefManager* Server::getWritableCraftDefManager()
 	return m_craftdef;
 }
 
-const ModSpec* Server::getModSpec(const std::string &modname)
+const ModSpec* Server::getModSpec(const std::string &modname) const
 {
-	for(std::vector<ModSpec>::iterator i = m_mods.begin();
+	for(std::vector<ModSpec>::const_iterator i = m_mods.begin();
 			i != m_mods.end(); i++){
 		const ModSpec &mod = *i;
 		if(mod.name == modname)
diff --git a/src/server.h b/src/server.h
index f53e23a3a..2030d6669 100644
--- a/src/server.h
+++ b/src/server.h
@@ -322,10 +322,10 @@ class Server : public con::PeerHandler, public MapEventReceiver,
 	IWritableNodeDefManager* getWritableNodeDefManager();
 	IWritableCraftDefManager* getWritableCraftDefManager();
 
-	const ModSpec* getModSpec(const std::string &modname);
+	const ModSpec* getModSpec(const std::string &modname) const;
 	void getModNames(std::vector<std::string> &modlist);
 	std::string getBuiltinLuaPath();
-	inline std::string getWorldPath()
+	inline std::string getWorldPath() const
 			{ return m_path_world; }
 
 	inline bool isSingleplayer()
diff --git a/src/settings.cpp b/src/settings.cpp
index 9adcd1587..e95bd436d 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -68,10 +68,11 @@ Settings & Settings::operator = (const Settings &other)
 
 bool Settings::checkNameValid(const std::string &name)
 {
-	size_t pos = name.find_first_of("\t\n\v\f\r\b =\"{}#");
-	if (pos != std::string::npos) {
-		errorstream << "Invalid character '" << name[pos]
-			<< "' found in setting name" << std::endl;
+	bool valid = name.find_first_of("=\"{}#") == std::string::npos;
+	if (valid) valid = trim(name) == name;
+	if (!valid) {
+		errorstream << "Invalid setting name \"" << name << "\""
+			<< std::endl;
 		return false;
 	}
 	return true;
@@ -83,7 +84,7 @@ bool Settings::checkValueValid(const std::string &value)
 	if (value.substr(0, 3) == "\"\"\"" ||
 		value.find("\n\"\"\"") != std::string::npos) {
 		errorstream << "Invalid character sequence '\"\"\"' found in"
-			" setting value" << std::endl;
+			" setting value!" << std::endl;
 		return false;
 	}
 	return true;
@@ -92,9 +93,9 @@ bool Settings::checkValueValid(const std::string &value)
 
 std::string Settings::sanitizeName(const std::string &name)
 {
-	std::string n(name);
+	std::string n = trim(name);
 
-	for (const char *s = "\t\n\v\f\r\b =\"{}#"; *s; s++)
+	for (const char *s = "=\"{}#"; *s; s++)
 		n.erase(std::remove(n.begin(), n.end(), *s), n.end());
 
 	return n;
-- 
GitLab