Warning: Bocadillo is now UNMAINTAINED. Users are recommended to migrate to a supported alternative, such as Starlette or FastAPI. Please see #344 for more information.
Security
While Bocadillo was designed with security in mind, you should make sure you follow common security guidelines before putting your application in production.
DISCLAIMER
Information Security is hard, and we're no experts. If you notice anything wrong with the present recommendations, please let us know!
HTTPS
HTTPS encrypts the communication between the server and the client, which means no one will be able to intercept or forge packets. This typically helps in preventing Man-in-the-Middle (MITM) attacks and is a necessary condition for securing a web application. Many web browsers today even display your site as "insecure" if it does not have HTTPS.
To set up HTTPS, you need to get a certificate from a Certificate Authority (CA). Let's Encrypt is a popular, free and open source CA. Certificates need to be regularly updated, so we recommend you use Certbot to automate the process.
Once your certificate and key files (.crt
, .key
) are generated, you can provide them to your Bocadillo application either by:
- Passing
--ssl-certfile
and--ssl-keyfile
to uvicorn:
# Command line usage:
uvicorn myproject.asgi:app --ssl-certfile=path/to/server.crt --ssl-keyfile=path/to/server.key
# Programmatic usage:
import uvicorn
from myproject.asgi import app
uvicorn.run(
app,
ssl_certfile="path/to/server.crt",
ssl_keyfile="path/to/server.key"
)
- Passing
--certfile
and--keyfile
to Gunicorn in a production setup (see deployment):
gunicorn --certfile=path/to/server.crt --keyfile=path/to/server.key ...
If you're hosting your app via a cloud provider, refer to their documentation as they may provide a feature to set up HTTPS for you.
It can be a good idea to enable HSTS so that all HTTP traffic is redirected to HTTPS.
XSS
XSS stands for Cross-Site Scripting. This attack consists in injecting malicious JavaScript code into HTML, such as through a <script>
tag.
One way to prevent XSS attacks is by escaping all quotes, replacing them with their HTML equivalent.
If you're using the templating utilities provided by Bocadillo, you are already benefiting from Jinja2 escaping strings in XML and HTML templates.
However, you should always quote attributes in order for this to work, e.g.
<input value="{{ value }}">
instead of
<input value={{ value }}>
Besides, Jinja2 cannot protect you against someone injecting malicious codes in the href
attribute of a link tag. To prevent this, you need to set the Content Security Policy (CSP) header, which can be achieved via a middleware:
from bocadillo import Middleware
class CSPMiddleware(Middleware):
async def after_dispatch(self, req, res):
# Only load scripts from the origin where the response
# is served.
res.headers["content-security-policy"] = "default-src 'self'"
# ...
app.add_middleware(CSPMiddleware)
For reference, see Jinja2 autoescaping.
CSRF
CSRF stands for Cross-Site Request Forgery. It is a type of attack in which a malicious website or program causes an authenticated user's browser to perform an unwanted operation on a trusted site.
These attacks work because web browsers automatically provide session cookies and the IP address with the request, hence allowing the attacker to forge a request against them.
A Token-based method is a popular way to mitigate CSRF attacks.
Bocadillo does not provide any such CSRF mitigation mechanism at the moment.
For more information on CSRF, see the OWASP CSRF guide and Prevention Cheatsheet.
SQL Injection
SQL injection allows a malicious user to execute arbitrary SQL code on a database — from writing unwanted rows to dropping the entire database.
Bocadillo does not provide any official way of interacting with a database (yet), so if you decide to use a database library, you'll be on your own.
That said, preventing SQL injection is easy enough: when handling user-provided query parameters, make sure to use parameter substitution instead of embedding them directly into the query.
For example, don't write:
symbol = "RHAT"
cursor.execute(f"SELECT * FROM stocks WHERE symbol = '{symbol}'")
Instead, use:
cursor.execute('SELECT * FROM stocks WHERE symbol=?', ("RHAT",))
(Snippets taken from the sqlite3 documentation.)
This will allow the underlying database driver to escape parameters for you.
Of course, this depends on the driver you're using; we recommend you refer to its documentation to make sure it supports this functionality.
Host header validation
An HTTP Host Header attack consists in a malicious user providing a fake Host
header in a request in order to execute a potentially harmful operation. An example of this is cache poisoning.
In order to prevent this type of attack, every Bocadillo applications has a whitelist of hosts which the Host
header is validated against. An invalid Host
will result in Bocadillo sending a 400 Bad Request
response.
By default, all hosts are whitelisted. For production usage, you should configure it accordingly — see allowed hosts.