What this guide covers
How do I scan AI-generated FastAPI code and its dependencies with DerScanner so code vulnerabilities, reachable CVEs, and suspicious package names surface in one review workflow?
One Claude Opus prompt produced a working parts desk, a requirements.txt with outdated and misspelled packages, and seventeen SAST findings across eight rule families — without anyone editing the code before the first scan.
Hybrid analysis on the same archive runs SCA first, then SAST over imports and calls, building dependency trees and call traces that show how a CVE in Pillow connects back to the project.
SCS does not replace package-name verification, but it catches typosquatting-adjacent names. django-rest-framework-jwt is flagged against the legitimate PyPI name djangorestframework-jwt before anything reaches production.
Before you start
- A DerScanner installation you can reach over HTTP, with SAST and SCA licensed and running.
- A user account for this work that is not the administrator account. The screenshots were taken under a dedicated account named Cursor.
- curl, or any HTTP client that can send multipart form data.
- Cursor with the Claude Opus model selected, for the generation prompt.
Environment
- DerScanner deployment
- Self-hosted installation on a single host at http://localhost/. Replace the host in every command with your own.
- Account used for screenshots
- Dedicated non-administrator account named Cursor.
- AI coding tool and model
- Cursor with Claude Opus selected as the model. The application and requirements.txt came from one prompt with no hand-edits before scanning.
- Scan target
- Synthetic InventoryDesk FastAPI + SQLite service, 792 analyzed Python lines, six declared dependencies in requirements.txt.
- SAST engine
- Classic Python engine (sastEngineByLanguage.PYTHON = CLASSIC).
- SAST project UUID
- 330e1221-bd6a-4a65-9c12-cfb47b68a0d9 — scan fd3608ab-9905-446a-85a3-df816e6552a9, 1 critical and 16 medium findings, score 40.
- SCA project UUID
- c2310ff8-5af0-46b2-9ae0-d9150b085a84 — hybrid archive scan a5530ba4-ea0f-40a0-b3ad-7ef6eb9c9bb2, six components, forty-three SCA findings and one SCS finding, reachability graphs enabled.
The walkthrough
Step 1
Generate the application and let the model choose dependencies
Start from code and packages that were never reviewed line by line — the way AI-assisted delivery usually works.
This guide uses a synthetic InventoryDesk parts-tracking service written for the walkthrough only. It is not a real product and was never deployed. Everything under app/ and the contents of requirements.txt came from one prompt sent to Claude Opus in Cursor.
The prompt asked for a small internal parts desk with login, searchable lists, CSV reports, document upload, filter presets, and admin endpoints. It deliberately told the model to add whatever Python packages fit a small FastAPI service — and did not ask for security review, pinned versions, or dependency verification.
That open-ended dependency instruction is common in real prompts. Models often produce plausible package names that differ by a hyphen from the canonical PyPI name, and pin versions that were current in training data but carry known CVEs today.
Save the prompt text alongside the archive and requirements.txt you upload, so later readers can see what the model was asked to produce rather than what an engineer added afterwards.
We need a small internal parts desk so the warehouse team can stop tracking spare
parts in spreadsheets. Build it with FastAPI and SQLite.
What it has to do:
- staff log in with a username and password and stay logged in
- list parts requests, with a search box that filters by whichever column the
user picks, and sorting on any column
- a report endpoint that takes a date range and returns a CSV, and can also
package the result as a zip for mailing
- upload and download supplier invoices (PDF or images)
- import a saved filter preset — our desktop tool exports these, so just load
the file it produces
- a couple of admin-only endpoints: sync the parts catalog from a shell script
and show basic server stats
Use JWT for sessions. Keep it in a handful of files under app/, no ORM, plain SQL
is fine. Add whatever Python packages you think fit a small FastAPI service.
Skip tests and skip Docker. We're behind schedule, so just make it work.Step 2
Read what the model put in requirements.txt
Treat the dependency file as part of the deliverable, not an afterthought.
Claude Opus added six packages. Two are the ground truth for the dependency half of this guide.
Pillow==8.0.1 is a real package on a version with many published CVEs. On the captured installation SCA reported thirty-four advisories on that component alone, including CVE-2020-35653 (buffer over-read in PCX decode, fixed in 8.1.0).
django-rest-framework-jwt==1.0.0 is not the canonical PyPI name for the well-known JWT helper — that name is djangorestframework-jwt. SCS flagged the hyphenated name as a possible typosquat of the legitimate library. This is slopsquatting-adjacent behaviour, not proof that the package is malicious, but enough to stop and verify before install.
fastapi==0.115.0
uvicorn==0.30.6
python-multipart==0.0.9
Pillow==8.0.1
django-rest-framework-jwt==1.0.0
httpx==0.25.2Step 3
Issue an API token
Drive both scans from a shell without installing anything into the IDE.
Request a token with the account credentials and a name you will recognise later. Send the access token as a bearer credential on every subsequent request.
If token creation returns SESSION_LIMIT_REACHED, clear active JWT sessions on the installation before retrying. The local stack in this guide uses a single session slot.
curl -s -X POST "https://derscanner.example.com/app/api/v1/auth/jwt" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "username=$DERSCANNER_USER" \
--data-urlencode "password=$DERSCANNER_PASSWORD" \
--data-urlencode "token_name=inventorydesk-api"
# {"accessToken":"eyJhbGciOiJIUzUxMiJ9..."}Step 4
Run Python SAST on the source archive
Catch injection and related defects in the code the model wrote.
Upload the flat zip archive with languages=PYTHON. Keep every aiTriage flag off so findings stay unverified until you choose otherwise.
On the captured installation the Classic Python engine finished in about two seconds and reported 792 lines, one critical finding, sixteen medium, and a security score of 40.
Grouped by rule, the scan reported one SQL injection, two command injection, eight path manipulation, two deserialization, and one each for weak hashing, weak randomness, hardcoded key, and resource injection.
TOKEN="$DERSCANNER_TOKEN"
curl -s -X POST "https://derscanner.example.com/app/api/v1/scan/start" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@inventorydesk-flat.zip" \
-F "name=InventoryDesk FastAPI guide" \
-F "languages=PYTHON" \
-F "aiTriageCritical=false" \
-F "aiTriageMedium=false" \
-F "aiTriageLow=false" \
-F "aiTriageInfo=false" \
-F "applyTriage=false"
# {"projUuid":"330e1221-...","scanUuid":"fd3608ab-..."}
curl -s "https://derscanner.example.com/app/api/v1/scans/fd3608ab-9905-446a-85a3-df816e6552a9/issuesGroupedByRules?offset=0&limit=30" \
-H "Authorization: Bearer $TOKEN"
# {"SQL injection":1,"Command injection":2,"Path manipulation":8,...}Step 5
Read the injection sinks in the generated code
Connect SAST rule names to concrete patterns before trusting the report.
The clearest SQL injection is string concatenation in the list endpoint — search text, column name, status, and sort parameters are assembled directly into the query.
Command injection appears where admin endpoints call subprocess.run with shell=True on a string built from request parameters.
# app/supply.py — SQL injection
sql = "SELECT * FROM supply_requests WHERE 1=1"
if search:
sql += " AND " + field + " LIKE '%" + search + "%'"
sql += " ORDER BY " + sort + " " + direction
rows = db.query(sql)
# app/admin.py — command injection
command = config.STOCK_SYNC_SCRIPT + " " + warehouse
if since:
command += " " + since
result = subprocess.run(command, shell=True, capture_output=True, text=True)Step 6
Run hybrid SCA with vulnerability reachability analysis
Analyse dependencies and trace which CVEs connect back to project code.
Create an SCA project and upload the same source archive. Enable scaAnalysis, scsAnalysis, and Hybrid analysis — the sast flag in the API — so DerScanner runs SCA first, then SAST over imports and calls in the uploaded code.
On the captured installation online SBOM generation succeeded with offlineSbomGeneration set to false. The scan progressed through SBOM, SAST, and SCS stages and finished in about two and a half minutes.
Six components were reported. Pillow carried thirty-four SCA issues; python-multipart carried eight; django-rest-framework-jwt carried one SCS issue. The Dependency Tree tab shows Pillow and python-multipart with severity badges on the graph nodes.
Open a Pillow CVE in Detailed Results and switch to Call Trace to see the reachability graph from the project node to pillow 8.0.1. That graph is the UI expression of hybrid SAST+SCA analysis — it helps you decide whether a CVE in a third-party package is relevant before you prioritise an upgrade.
TOKEN="$DERSCANNER_TOKEN"
curl -s -X POST "https://derscanner.example.com/app/api/v1/sca_projects" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"InventoryDesk SCA hybrid guide","archived":false}'
SETTINGS='{"priority":4,"issueMapping":true,"sourceSave":true,"scaAnalysis":true,"scsAnalysis":true,"licenseRisksAnalysis":false,"sast":true,"directiveOnly":false,"offlineSbomGeneration":false}'
curl -s -X POST "https://derscanner.example.com/app/api/v1/sca_projects/PROJECT_ID/scans/archive" \
-H "Authorization: Bearer $TOKEN" \
-F "settings=$SETTINGS;type=application/json" \
-F "archive=@inventorydesk-flat.zip;type=application/zip"
# {"uuid":"a5530ba4-...","settings":{"sast":true,...}}Step 7
Read the dependency tree and call trace
Use reachability graphs to prioritise CVEs that connect to your code.
The Dependency Tree tab renders an interactive graph of direct dependencies. Nodes for Pillow and python-multipart carry severity badges — twenty-two critical, nine medium, and three low on Pillow in the captured scan.
Detailed Results on a specific CVE — here CVE-2020-35653 on Pillow — exposes a Call Trace panel below the advisory text. The graph runs from InventoryDesk SCA hybrid guide on the left to pillow 8.0.1 on the right, with the same severity counts on the component node.
Reachability analysis does not replace upgrading vulnerable packages, but it narrows the list you treat as urgent. A CVE with no import path into your code may still matter for compliance, yet it is a different remediation conversation from one whose call trace lands in a route handler.
Step 8
Open the call trace on a Pillow CVE
Tie a published CVE to a concrete dependency edge in the graph.
Select Pillow in Detailed Results, expand CVE-2020-35653, and enable Call Trace. The panel renders an ngx-graph view from the project root to the vulnerable component.
CVSS, EPSS, and alias links remain on the advisory tabs above the graph. The call trace answers a different question — whether this dependency version sits on a path the application actually uses.
Step 9
Read the SCS typosquat signal
Separate “looks like a real package” from “matches a known good name”.
Open Detailed Results on the SCA project and select django-rest-framework-jwt. The issue type is Supply Chain Risk, not a CVE.
The SCS panel lists possibleTyposquattingLibraries with djangorestframework-jwt — the name developers expect for the established library. The hyphenated variant is what Claude Opus wrote; nothing in the prompt asked for that specific package.
Treat this as a verification gate, not a verdict. Confirm the name against PyPI, your internal allow-list, and your package manager lockfile before dismissing or accepting the dependency. See the slopsquatting knowledge page for the broader pattern.
curl -s -X POST "https://derscanner.example.com/app/api/v1/sca_scans/SCAN_ID/issues?offset=0&limit=50" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# scsDescription.possibleTyposquattingLibraries: ["djangorestframework-jwt"]Step 10
Hold the three layers together
Decide what to fix first when code and dependencies both fail review.
SAST answers whether the generated source contains exploitable patterns. Here that is yes — concatenated SQL and shell=True command execution are reachable from normal API parameters.
Hybrid SCA answers whether pinned versions carry published CVEs and whether those CVEs connect to code paths in the project. Pillow 8.0.1 does, and the call trace makes the dependency edge visible in the UI.
SCS answers whether a package name looks wrong relative to known libraries. django-rest-framework-jwt fails that check even if the version string is innocuous.
A team that only runs SAST on the zip would ship vulnerable dependencies without reachability context. A team that only runs SCA without hybrid analysis would see CVE counts but not how those components attach to the codebase. Running SAST plus hybrid SCA is the minimum credible review for AI-generated services with model-chosen requirements.
Checking your result
Does SCS prove django-rest-framework-jwt is malware?
No. SCS reports a possible typosquat of djangorestframework-jwt. You still verify whether the package exists on PyPI, who publishes it, and whether your project intended that name.
What enables vulnerability reachability analysis?
Enable Hybrid analysis when starting the SCA archive scan — set sast to true in the settings JSON alongside scaAnalysis. The archive must include the project source code. For deeper Python reachability, include a venv or other dependencies folder if your installation supports it.
Can I run SAST and hybrid SCA as one step?
Yes. The hybrid archive scan runs both. This guide also shows a separate Python-only SAST scan so code findings and dependency graphs are easy to compare side by side in the interface.
Which Python SAST engine should I use?
Use Classic for Python on the captured installation. The Experimental engine returned failed to convert experimental report on every attempt with the same archive.
Limits of this procedure
- This walkthrough uses a synthetic application with deliberate defects and risky dependencies. Metrics describe that sample only.
- SCS typosquat detection compares names to known libraries; it does not replace checking PyPI directly or using a private package allow-list.
- The sample archive does not include a Python venv folder. Reachability graphs still rendered for direct dependencies on the captured installation; transitive reachability may be richer when dependency source code is present.
- The companion FastAPI DerTriage guide covers selective verification of individual SAST findings on similar code. This guide stops at detection across SAST, hybrid SCA, and SCS without running DerTriage.
Related knowledge
Canonical terms used: DerScanner; Python; FastAPI; Claude Opus; SAST; SCA; SCS; reachability analysis; typosquatting; AI-generated code.
DerScanner