DEV Community

Matteo Vitali
Matteo Vitali

Posted on

1

Secure by design in Python: A FastAPI app with 5 DevSecOps tools and a real time SSTI vulnerability remediation

🌟 Introduction

Security should not be an afterthought in software development. Instead, it should be a core principle baked into your design, code, and CI/CD workflows.
To demonstrate this approach, I've created an intentionally insecure FastAPI app as a playground.

https://github.com/trottomv/python-insecure-app

In this post, we'll walk through:

  • 5 open-source security tools (SCA, SAST, DAST, container scanning, API fuzzing)
  • How they help uncover vulnerabilities
  • How to remediate a real Server-Side Template Injection (SSTI) vulnerability in FastAPI

Let's dive in 👇


1️pip-audit: Python dependency vulnerability scanning

pip-audit
Enter fullscreen mode Exit fullscreen mode

This tool checks for known vulnerabilities in your Python dependencies using safety-db or PyPI advisories. It's simple, fast, and effective.

Sample output:

jinja2 3.0.0 CVE-2022-XXXXX Template injection possible
Enter fullscreen mode Exit fullscreen mode

Remediation:

  • Upgrade the affected package if a patched version is available
  • Pin versions in requirements.txt

2️Bandit: static code analysis (SAST) for Python

bandit .
Enter fullscreen mode Exit fullscreen mode

Bandit performs static analysis on Python code to find common security issues like:

  • Use of dangerous functions (eval, exec, etc.)
  • Hardcoded passwords or secrets
  • Insecure file handling
  • External calls with requests without a timeout — which may lead to denial of service if the remote server hangs

But: Bandit currently does not detect Server-Side Template Injection (SSTI), even when using jinja2 templates unsafely.

Reference: Issue #728

This shows a limitation of SAST — it's useful, but not sufficient alone.


3️Schemathesis: API fuzzing from OpenAPI schemas

schemathesis run http://localhost:8000/openapi.json
Enter fullscreen mode Exit fullscreen mode

Schemathesis generates test cases based on your OpenAPI schema and runs fuzzing-like checks on all endpoints.

Benefits:

  • Detects unexpected 500s and unhandled edge cases
  • Validates assumptions in your parameter handling

In our demo, this helped uncover inconsistent behavior in the root / endpoint, especially with template injection payloads.


4️Trivy: container image vulnerability scanner

trivy image trottomv/python-insecure-app
Enter fullscreen mode Exit fullscreen mode

Trivy scans for:

  • OS package vulnerabilities (APT, APK, etc.)
  • Python dependency CVEs
  • Misconfigurations

Hardening tips:

  • Use python:3.12-slim or smaller base images
  • Avoid running as root (USER app)
  • Consider introduce distroless approach

Bonus: Trivy also outputs SBOMs for compliance.


5️OWASP ZAP: dynamic app security testing (DAST)

ZAP acts as a proxy or scanner against your running app (e.g. FastAPI on localhost:8000).

In our case, ZAP successfully discovered a Server-Side Template Injection (SSTI) vulnerability:

GET /?name={{7*6}} → "Hello 42!"
Enter fullscreen mode Exit fullscreen mode

This issue was not detected by Bandit, highlighting the importance of dynamic analysis.


🛠️ Remediation: fixing the SSTI in FastAPI

Vulnerable code:

# main.py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from jinja2 import Template

app = FastAPI()

@app.get("/", response_class=HTMLResponse)
async def try_hack_me(name: str = "John Ripper"):
    content = f"<h1>Hello, {name}!</h1>"
    return Template(content).render()
Enter fullscreen mode Exit fullscreen mode

Why it's dangerous:

  • Template(content).render() evaluates the string using Jinja2, which allows remote code execution if input is unsanitized

Secure fix:

# main.py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from jinja2 import Template

app = FastAPI()

@app.get("/", response_class=HTMLResponse)
async def try_hack_me(name: str = "John Ripper"):
    content = "<h1>Hello, {{name}}!</h1>"
    return Template(content).render(name=name)
Enter fullscreen mode Exit fullscreen mode

Test it:

# tests.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root():
    response = client.get("/?name={{7*6}}")
    assert response.status_code == 200
    assert "42" not in response.content.decode()
Enter fullscreen mode Exit fullscreen mode

✅ Conclusion

Security is not a tool — it's a mindset and a workflow.

Combining:

  • pip-audit for software composition analysis
  • bandit for static code issues
  • schemathesis for fuzzing testing on APIs
  • trivy for vulnerability scanning and SBOMs generation on artifacts
  • zap for dynamic runtime scanning

...provides layered defense. Fixing real issues like SSTI shows how practical this approach is.

If you're curious:

  • 🌐 Try the demo app
  • ✏️ Suggest improvements or contribute new findings
  • 🚀 Integrate these tools in your next CI/CD pipeline

Stay secure ✨


Top comments (0)