diff --git a/docs/header_names/allowed_framework_names.adoc b/docs/header_names/allowed_framework_names.adoc index 56aa1dc6f64..d334993bc80 100644 --- a/docs/header_names/allowed_framework_names.adoc +++ b/docs/header_names/allowed_framework_names.adoc @@ -79,6 +79,7 @@ * Django Templates * Flask * aiohttp +* FastAPI * Jinja * lxml * Paramiko diff --git a/rules/S5131/python/how-to-fix-it/fastapi.adoc b/rules/S5131/python/how-to-fix-it/fastapi.adoc new file mode 100644 index 00000000000..605fbb2024a --- /dev/null +++ b/rules/S5131/python/how-to-fix-it/fastapi.adoc @@ -0,0 +1,82 @@ +== How to fix it in FastAPI + +=== Code examples + +The following code is vulnerable to cross-site scripting because it returns an HTML response that contains user input. + +If you do not intend to send HTML code to clients, the vulnerability can be fixed by specifying the type of data returned in the response. +For example, you can use the `JsonResponse` class to return JSON messages securely. + +It is also possible to set the `Content-Type` manually by using the `media_type` parameter of the `Response` constructor. + +==== Noncompliant code example + +[source,python,diff-id=41,diff-type=noncompliant] +---- +from fastapi import FastAPI, Response +import json + +app = FastAPI() + +@app.get("/example") +def example(input: str): + json_str = json.dumps({"data": input}) + return Response(json_str) # Noncompliant +---- + +[source,python,diff-id=42,diff-type=noncompliant] +---- +from fastapi import FastAPI, Response + +app = FastAPI() + +@app.get("/example") +def example(input: str): + return Response(input) # Noncompliant +---- + +==== Compliant solution + +[source,python,diff-id=41,diff-type=compliant] +---- +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI() + +@app.get("/example") +def example(input: str): + return JSONResponse({"data": input}) +---- + +[source,python,diff-id=42,diff-type=compliant] +---- +from fastapi import FastAPI, Response + +app = FastAPI() + +@app.get("/example") +def example(input: str): + return Response(input, media_type="text/plain") +---- + +=== How does this work? + +If the HTTP response is HTML code, it is highly recommended to use a template engine like https://jinja.palletsprojects.com/[Jinja] to generate it. +This template engine separates the view from the business logic and automatically encodes the output of variables, drastically reducing the risk of cross-site scripting vulnerabilities. + +If you do not intend to send HTML code to clients, the vulnerability can be fixed by correctly setting the `Content-Type` HTTP header. +This HTTP header defines which media type the browser can expect from the response, so the browser can parse it correctly. By specifying a type that is not HTML, the browser does not interpret the response as HTML, which in turn prevents cross-site scripting. + +For example, when setting the `Content-Type` header to `text/plain`, browsers will interpret the HTTP response as plaintext and will not process it any further. This allows user input to be reflected safely. + +=== Pitfalls + +include::../../common/pitfalls/content-types.adoc[] + +include::../../common/pitfalls/validation.adoc[] + +=== Going the extra mile + +include::../../common/extra-mile/csp.adoc[] + diff --git a/rules/S5131/python/rule.adoc b/rules/S5131/python/rule.adoc index 4a560b9f00f..b812840c3b1 100644 --- a/rules/S5131/python/rule.adoc +++ b/rules/S5131/python/rule.adoc @@ -16,6 +16,8 @@ include::how-to-fix-it/flask.adoc[] include::how-to-fix-it/jinja.adoc[] +include::how-to-fix-it/fastapi.adoc[] + == Resources include::../common/resources/docs.adoc[]