This guide outlines some key security best practices for using SurrealDB v2.x.x
. While SurrealDB offers powerful and flexible features to help you meet your desired security standards, your use of those features will ultimately determine whether or not you meet them.
The following is a non-exhaustive list of security best practices you should consider when building services and applications with SurrealDB to help you address common security challenges while preventing frequent pitfalls.
When running a SurrealDB server, you can configure the capabilities for your SurrealQL queries. Most of these capabilities are disabled by default to expose as little attack surface as possible to malicious actors.
For the strongest security, we recommend denying all capabilities by default and only allowing the specific capabilities necessary for your service, following an allowlisting approach. We strongly discourage running SurrealDB with all capabilities allowed.
# Allow SurrealDB to call any functions from the array and string families, generate and compare Argon2 hashes # and make HTTP GET requests over HTTPS to the address of a specific API. surreal start --deny-all --allow-funcs "array, string, crypto::argon2, http::get" --allow-net api.example.com:443
When you need to enable a capability, we recommend doing it specifically instead of generally. For example, suppose you know that your queries need to be able to parse emails using functions. In that case, we recommend you run SurrealDB with the --allow-funcs "parse::email::*"
flag instead of allowing all functions with --allow-funcs
without arguments. Doing this can help mitigate the performance impact that users can have when using certain functions and ensures that your SurrealDB instance will not be affected by vulnerabilities in the code of any other functions that a malicious actor could leverage to attack SurrealDB.
In the case where it is absolutely necessary to generally allow a capability, we recommend carefully reviewing the scope of that capability and denying any specific instances where it may introduce unacceptable risks. This is especially important in the case of the network capability, which allows SurrealDB to perform network requests such as those required by the http::*
functions. Allowing untrusted users to perform network requests from your SurrealDB instance can allow them access to its local network or services that specifically allow network access from the SurrealDB server.
# Avoid doing this:# Allow SurrealDB to make outgoing HTTP GET and POST request to any address except to some known private CIDR blocks. surreal start --deny-all --allow-funcs "http::get, http::post" --allow-net --deny-net "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16"
Following a denylisting approach as described above should only be used as a last resort, since it is common to miss some risky cases (e.g. 169.254.169.254), which in that case would become allowed by default.
If you require storing passwords for your users, do not rely on table or field permissions to keep them private. In the event that your application or database is compromised, these passwords would become known by the attacker. Instead, use the password hashing functions provided by SurreaDB such as crypto::argon2::*
, crypto::bcrypt::*
, crypto::pbkdf2::*
and crypto::scrypt::*
. These functions ensure that irreversible cryptographic hashes are stored instead of the original passwords, so that the passwords from your users remain safe even in the event of a compromise.
Do not use other cryptographic hash functions (e.g. crypto::md5
, crypto::sha1
, crypto::sha512
) for hashing passwords, even if you do use an additional salt. These functions are designed to be efficient in computing, which will benefit an attacker that sets out to crack any hashes that they may have obtained from the compromise of your application. Hash functions intended for password hashing already incorporate a salt as well as other mechanisms to prevent hash cracking by making the computation of such hashes less efficient. This mitigates password cracking at scale at the small cost of adding a few milliseconds delay while checking credentials for legitimate users.
Even it you only store password hashes, it is a good practice to additionally use field permissions to prevent unauthorised access to the password hashes, which could allow an attacker to perform inefficient but potentially effective attacks such as testing candidate passwords against a specific hash. For even better security, you may store passwords in a separate table and use table permissions to disallow all access to that table. Due to their internal implementation, table permissions provide additional security compared to field permissions.
DEFINE TABLE user SCHEMAFULL -- Only allow users to query their own record, including their password. PERMISSIONS FOR select, update, delete WHERE id = $auth.id; DEFINE FIELD name ON user TYPE string; DEFINE FIELD email ON user TYPE string ASSERT string::is::email($value); DEFINE FIELD password ON user TYPE string; DEFINE INDEX email ON user FIELDS email UNIQUE; DEFINE ACCESS user ON DATABASE TYPE RECORD SIGNUP ( CREATE user CONTENT { name: $name, email: $email, password: crypto::argon2::generate($password) -- Use Argon2 to generate the hash. } ) SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) -- Use Argon2 to compare the hashes. );
When defining users and access methods, ensure that you set a specific session and token duration whenever possible using the DURATION
clause.
Default values provided by SurrealDB are intended to support cases where SurrealDB is used as a traditional backend database, which is why sessions do not expire by default. Suppose you build an application where your end users directly connect with SurrealDB. In that case, we strongly encourage setting a session expiration that is as short as possible (typically a few hours) to provide a good experience to your users without compromising security.
Expiring user sessions ensures that a user cannot remain authenticated long after their access has been revoked. This cannot be done on demand, as user sessions do not persist in the database. However, unlike tokens, user sessions are not typically susceptible to being stolen, as they exist only in the context of an established WebSocket connection.
DEFINE USER username ON DATABASE PASSWORD 'CHANGE_THIS' DURATION FOR SESSION 5d;
DEFINE ACCESS account ON DATABASE TYPE RECORD SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) ) SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) ) DURATION FOR SESSION 12h ;
Tokens, however, are usually stored in the client (e.g. a web browser) and may be stolen by client-side attacks such as a cross-site scripting vulnerability in your application. For this reason, we strongly recommend reducing the token duration from the default one hour to the minimum amount of time that your use case can tolerate. Ideally, a token should only be valid for as long as the client needs to use it to establish a session, which can be as little as a few seconds.
DEFINE USER username ON DATABASE PASSWORD 'CHANGE_THIS' DURATION FOR TOKEN 15m;
DEFINE ACCESS account ON DATABASE TYPE RECORD SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) ) SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) ) DURATION FOR TOKEN 5s ;
When using SurrealDB as a traditional backend database, your application will usually build SurrealQL queries that may need to contain some untrusted input, such as that provided by the users of your application. To do so, SurrealDB offers bind
as a method to query
(implemented in other SDKs as the vars
argument to query
), which should always be used when including untrusted input into queries. Otherwise, SurrealDB will be unable to separate the actual query syntax from the user input, resulting in the well-known SQL injection vulnerabilities. This practice is known as prepared statements or parameterised queries.
Binding parameters ensure that untrusted data is passed to SurrealDB as SurrealQL parameters, which are independent of the query syntax, preventing SQL injection attacks.
// Do this: let name = "tobie"; // User-controlled input. let mut result = db .query("CREATE person CONTENT name = $name;") .bind(("name", name)) .await?;
// Do NOT do this:let name = "tobie"; // User-controlled input. let mut result = db .query(format!("CREATE person CONTENT name = {name};")) .await?;
// Do this: const name = "tobie"; // User-controlled input. const result = await db.query( 'CREATE person CONTENT name = $name;', { name } );
// Do NOT do this:const name = "tobie"; // User-controlled input. const result = await db.query(`CREATE person CONTENT name = "${name}";`);
// Do this: string name = "tobie"; // User-controlled input. var result = await db.Query($"CREATE person CONTENT name = {name};"); // Translated as "CREATE person CONTENT name = $p0;" // with the parameter $p0 having the value "tobie"
// Do NOT do this:string name = "tobie"; // User-controlled input. var result = await db.RawQuery($"CREATE person CONTENT name = "{name}";");
Requestcurl -X POST -u "root:root" -H "surreal-ns: mynamespace" -H "surreal-db: mydatabase" -H "Accept: application/json" \ -d 'SELECT * FROM person WHERE age > $age' http://localhost:8000/sql?age=18
Requestcurl -X POST -u "root:root" -H "ns: mynamespace" -H "db: mydatabase" -H "Accept: application/json" \ -d 'SELECT * FROM person WHERE age > $age' http://localhost:8000/sql?age=18
Content generated by users and other untrusted parties will often be stored in SurrealDB and later rendered in an HTML page to be displayed. Regardless of SurrealDB, rendering untrusted content is the source of some dangerous pitfalls which can lead to cross-site scripting attacks and other client-side code injection issues like site defacement or clickjacking.
When retrieving content that may be rendered in an HTML document, we strongly recommend that you use the string::html::encode
function, which will encode any characters that have special meaning in HTML syntax (e.g. <
, >
, &
…) into HTML entities (e.g. <
, >
, &
…) that will be rendered as the actual original character instead of interpreted as HTML syntax.
RETURN string::html::encode("<h1>Safe Title</h1><script>alert('XSS')</script><p>Safe paragraph. Not safe <span onload='logout()'>event</span>.</p>"); ['<h1>Safe Title</h1><script>alert('XSS')</script><p>Safe paragraph. Not safe <span onload='logout()'>event</span>.</p>']
If you absolutely require user-generated content to be rendered as HTML but still want to prevent users from injecting dangerous HTML into your page, you can use the string::html::sanitize
function instead, which will keep all characters intact, so that the content can be interpreted as HTML syntax, while removing the specific syntax that is deemed dangerous. It is important to note that, although the set of accepted syntax is very conservative, sanitization is less safe that encoding and could potentially be bypassed due to a flaw in the function.
RETURN string::html::sanitize("<h1>Safe Title</h1><script>alert('XSS')</script><p>Safe paragraph. Not safe <span onload='logout()'>event</span>.</p>"); ['<h1>Safe Title</h1><p>Safe paragraph. Not safe <span>event</span>.</p>']
When configuring how JSON Web Tokens are verified before authenticating a system or record user with DEFINE ACCESS ... TYPE JWT
or DEFINE ACCESS ... TYPE RECORD ... WITH JWT
, we recommend using an asymmetric algorithm (i.e. PSXXX
, RSXXX
, ECXXX
) when only a mechanism for token verification is being defined. This ensures that the only key stored by SurrealDB is a public key that does not represent a threat in the event of a compromise.
On the other hand, symmetric algorithms (i.e., HSXXX) use the same key for signature and verification, which the attacker could use to issue tokens that SurrealDB would trust.
DEFINE ACCESS token ON DATABASE TYPE RECORD WITH JWT ALGORITHM RS256 KEY "-----BEGIN PUBLIC KEY----- MUO52Me9HEB4ZyU+7xmDpnixzA/CUE7kyUuE0b7t38oCh+sQouREqIjLwgHhFdhh3cQAwr6GH07D ThioYrZL8xATJ3Youyj8C45QnZcGUif5PkpWXDi0HJSoMFekbW6Pr4xuqIqb2LGxGDVJcLZwJ2AS Gtu2UAfPXbBD3ffiad393M22g1iHM80YaNi+xgswG7qtXE4lR/Lt4s0MeKKX7stdWI1VIsoB+y3i r/OWUvJPjjDNbAsyy8tQmxydv+FUnLEP9TNT4AhN4DXcJ+XsDtW7OWt4EdSVDeKpGbIMvIrh1Pe+ Nilj8UHNyNDHa2AjK3seMo6CMvaIQJKj5o4xGFblFGwvvPD03SbuQLs1FdRjsZCeWLdYeQ3JDHE9 sFG7DCXlpMJcaYT1mf4XHJ0gPekNLQyewTY3Vxf7FgV3GCNjV20kcDFgJA2+iVW2wSrb+txD1ycE kbi8jh0pedWwE40VQWaTh/8eAvX7IHWya/AEro25mq+m6vktNZLbvLphhp586kJK3Tdt3YjpkPre M3nkFWOWurIyKbtIV9JemfwCgt89sNV45dTlnEDEZFFGnIgDnWgx3CUo4XmhICEQU8+tklw9jJYx iCTjhbIDEBHySSSc/pQ4ftHQmhToTlQeOdEy4LYiaEIgl1X+hzRH1hBYvWlNKe4EY1nMCKcjgt0= -----END PUBLIC KEY-----";
Additionally, we recommend using JSON Web Key Sets to configure the verification algorithm and key from a remote authoritative source using the URL
clause instead of providing them directly to SurrealDB using the ALGORITHM
and KEY
clauses. This ensures that the original token issuer will be able to rotate keys in the event of a compromise to prevent potentially compromised tokens to be used with your application without affecting the availability of your service.
DEFINE ACCESS token ON DATABASE TYPE RECORD WITH JWT URL "https://example.com/.well-known/jwks.json";
When deploying SurrealDB, we recommend limiting the attack surface as much as possible in order to minimise the risk of attacks or information gathering from unauthorised parties. If your database should only be available to other internal services, we suggest that you expose SurrealDB exclusively to the internal network instead of deploying the service with a publicly addressable network interface that is accessible from the internet, regardless of whether or not allowlisting has been applied at the networking or application level.
If you must publish SurrealDB to the internet (e.g. if your users directly connect to SurrealDB), you may want to monitor and prevent unwanted connections using tools such as a network intrusion prevention system or a web application firewall. If you do so, ensure that these systems are appropriately tuned and do not interfere with the regular use of SurrealDB.
In cases where SurrealDB is publicly exposed in environments where any sort of information leakage is unacceptable, the --no-identification-headers
flag can be enabled, which will result in the SurrealDB server no longer responding to HTTP requests with headers that identify the product or its current version to prevent passive fingerprinting and metadata indexing. Note that this will not prevent active fingerprinting such as directly querying the /version
endpoint if available or directly attempting to exploit a known security vulnerability without regard for compatibility. On the other hand, consider whether or not enabling this feature is compatible with your clients, which may rely on these headers in order to identify the version of SurrealDB running on the server.
$ surreal start & $ curl -vvv "127.0.0.1:8000" * Trying 127.0.0.1:8000... * Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0) > GET / HTTP/1.1 > Host: 127.0.0.1:8000 > User-Agent: curl/7.81.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 307 Temporary Redirect < location: https://surrealdb.com/app < access-control-allow-origin: * < vary: origin < vary: access-control-request-method < vary: access-control-request-headers # highlight-start < surreal-version: surrealdb-2.0.0+20240612.2184e80f < server: SurrealDB # highlight-end < x-request-id: 157413ce-7cc4-41a1-a93b-0940bf87874c < content-length: 0 < date: Mon, 17 Jun 2024 15:47:29 GMT < * Connection #0 to host 127.0.0.1 left intact
$ surreal start --no-identification-headers & $ curl -vvv "127.0.0.1:8000" * Trying 127.0.0.1:8000... * Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0) > GET / HTTP/1.1 > Host: 127.0.0.1:8000 > User-Agent: curl/7.81.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 307 Temporary Redirect < location: https://surrealdb.com/app < access-control-allow-origin: * < vary: origin < vary: access-control-request-method < vary: access-control-request-headers < x-request-id: deec3301-e930-4389-a0da-b2a336bd2631 < content-length: 0 < date: Mon, 17 Jun 2024 15:49:43 GMT < * Connection #0 to host 127.0.0.1 left intact
When defining system users in SurrealDB, you may assign them roles that will limit the actions they can perform inside the level where they are defined. Ensure that you employ the principle of least privilege and create users at the lowest level possible and with the minimum role in order to be able to perform their duties inside of SurrealDB. This will mitigate some of the risk in the case where credentials for that user are ever compromised.
A user who only needs to query the database:
DEFINE USER db_viewer ON DATABASE PASSWORD 'CHANGE_THIS' ROLES VIEWER;
A user who only needs to manage content in any databases on the same namespace:
DEFINE USER ns_editor ON NAMESPACE PASSWORD 'CHANGE_THIS' ROLES EDITOR;
Encryption in transit is recommended, especially when deploying SurrealDB on a server in a different network than its clients. This mitigates the impact of man-in-the-middle attacks and provides confidentiality and integrity guarantees with regard to the data being exchanged. Encryption in transit can be achieved by using the SurrealDB server to serve its interfaces through HTTPS by providing the --web-crt
and --web-key
arguments when calling the start
subcommand in the CLI. For production deployments, we recommend that TLS termination be performed by a load balancer or reverse proxy, which will often provide additional guarantees to the process.
# If you want to serve TLS directly with SurrealDB: surreal start --web-crt "cert.pem" --web-key "key.pem"
Encryption at rest is recommended especially when storing sensitive data in a location where you cannot guarantee the security of the storage media. If encryption at rest is not used, physical access to the storage media may result in the complete compromise of the data stored. It is important to note that most kinds of encryption at rest will not prevent logical attacks from resulting in compromise of the data, as such attacks will often access the data using the compromised system as a confused deputy in order to leverage its ability to access data after it is already decrypted.
Encryption at rest can be achieved by ensuring that the data is stored encrypted using a disk encryption solution such as LUKS or BitLocker in Linux and Windows systems respectively and, in the case where you are hosting SurrealDB in a cloud provider, by leveraging their storage encryption solutions in the volume or disk that will store your data.
You might consider additional encryption for your datastore in some specific scenarios. This can provide increased security when your database servers, storage media and their corresponding encryption keys are managed in different security contexts, where the storage media and its keys may be compromised without also compromising the datastore servers. Encryption at rest at the datastore level can be achieved by using a datastore backend that offers transparent encryption such as TiKV (≥4.0) or FoundationDB (≥7.2). This encryption is independent from SurrealDB.
It is important to note that, even in this scenario, physical or logical access to the SurrealDB server will result in access to the data, as SurrealDB must receive decrypted data from the datastore in order to perform any sort of queries.
Due to the powerful SurrealQL language and the addition of functions, scripting and network capabilities, running untrusted queries in SurrealDB as a system user should be treated similarly to running untrusted software in any system. When copying queries or importing datasets from sources that you do not trust, make sure to review their contents to ensure that they do not contain any malicious code intended to perform unauthorized changes, computations or network requests.
One of the interfaces to SurrealDB is RPC through WebSockets. This interface is usually used by the official SDKs and offers performance benefits over the HTTP REST API, which requires establishing a new connection for every operation. The RPC interface can either be directly exposed to end users or used internally from your backend to communicate with SurrealDB.
In the later scenario, some developers may choose to still authenticate each user individually (e.g. using signin
or authenticate
in the WebSockets session as opposed to using a single service user for their backend. This could be done by calling invalidate
or just authenticating a new user in the same connection and may provide some performance benefits over establishing a new WebSocket connection for each user. However, we recommend using separate WebSocket sessions or connections for different users. Consider terminating the connection and establishing a new one for every individual user.
WebSocket connections offer an additional degree of isolation between users that may become relevant in the event where some session information for previous users who were using the same connection was not properly cleared. Additionally, even if successfully isolated from the security perspective, some resources associated with users are freed by SurrealDB only when the connection is terminated. Sharing the same WebSockets connection between several users may cause these unused resources to grow indefinitely.
When SurrealDB is part of your service or application, vulnerabilities that affect SurrealDB may also impact your environment. Due to this fact, we highly recommend that you track vulnerabilities published for SurrealDB so that you become aware of any updates that address vulnerabilities that you may be affected by. This can be done most effectively by leveraging automation tools that will consume the Github Advisory Database. These automations will usually also warn of vulnerabilities in dependencies used by SurrealDB, which may also have an impact in your environment. Keeping up to date with the latest releases of SurrealDB is, in general, a good practice.
If you identify a vulnerability in SurrealDB that has not been published yet, we encourage you to create a security advisory report on Github so that the SurrealDB team can privately look into it in order to identify and work on a solution that can benefit you as well as the rest of the users.