Safeguard
Product•

SAST for Python in Safeguard: Dataflow Traces and CWE Mapping (EA)

Safeguard SAST traces untrusted input to dangerous sinks in Python code and maps each finding to CWE and OWASP. How to run it, read it, gate it.

Safeguard Security Team
38 min read

Safeguard SAST for Python is in GA. It reads your Python source without running it, follows untrusted input from where it enters (a request parameter, a command line argument, a file read) to where it can do damage (a SQL query, a shell command, a file path), and reports each hit with the full source to sink dataflow trace, a CWE and OWASP mapping, a severity and a code location. You run it from the Safeguard CLI, in your pipeline, or on a local runner so source never leaves your machine.

This post covers what the scanner does today, the standards behind it, a worked Python trace, the setup steps from Safeguard's docs, and the limits to plan around while it is generally available.

Key takeaways

  • SAST is generally available. Python is one of the three phase one languages in Safeguard's docs, alongside JavaScript and TypeScript and Java. Detection depth is still expanding, and Safeguard's docs say so plainly.
  • Every finding carries a dataflow trace. You see the ordered hops from source to sink across functions and files, not just the line where the dangerous call sits.
  • Findings are mapped to CWE and OWASP. That gives you a shared vocabulary with the CWE Top 25 and the OWASP Top 10, and with whatever secure coding standard your organisation already uses.
  • It runs where your code is. Use the Safeguard CLI (GA) in a pipeline, or the local runner (GA) so only results are uploaded. Air-gapped deployment (GA) is available for environments with no internet egress.
  • Findings land in one model. SAST results sit in the same unified findings model as SCA (GA) and DAST (GA), so a runtime issue found by DAST can be linked to the Python sink that caused it.
  • Gating and prioritisation are GA platform features. Policies and gates (GA) and reachability analysis (GA) sit around the scanner. Autonomous remediation (GA) ships switched off, with every category set to Manual, and Safeguard's docs do not describe automatic fix pull requests for SAST findings, so plan on developers fixing code.
  • Read results in the app. There is no CLI listing command or working findings REST API yet.

What SAST for Python means in Safeguard

Static application security testing analyses source code without executing it. Safeguard's documentation describes its SAST engine (GA) as tracing untrusted input from a source to a dangerous sink across functions and files. The docs give a request parameter, a command line argument and a file read as example sources, and a SQL query, command execution and a file path as example sinks.

The relevant facts from Safeguard's Application Security Testing docs page, in one place:

AspectWhat Safeguard's docs say
LabelGA. The docs say SAST and DAST are rolling out and detection depth expands over time
InputsA connected repository or a local directory, with no build required
Languages in phase oneJavaScript and TypeScript, Python, Java, with more to follow
What a finding containsThe source to sink dataflow trace, CWE and OWASP mapping, severity and a code location
Where it runsIn your pipeline, or on a local runner so source never leaves your perimeter
Where results appearThe app's AppSec views, with severity, rule, CWE, location and the dataflow trace
CLI listingNot available yet. The docs say there is no CLI command for listing findings

Safeguard's offering files add one more input detail: SAST accepts GitHub, GitLab, Bitbucket or any Git compatible repository, public or private.

For Python specifically, "no build required" matters. Python has no compile step that a scanner can hook into, and many Python services have a dependency install that is slow or fragile in a scanning environment. Safeguard's docs say the SAST input is a repository or a directory and that no build is needed, which means you can point it at a checkout of your service.

The rest of this post assumes that a finding looks the way the docs describe. Where the docs are silent on a Python detail, such as which specific frameworks or library calls are modelled, this post says so rather than guessing.

The problem: why Python code needs source to sink analysis

Python makes it easy to write code that works and is unsafe at the same time. Most of the dangerous calls live in the standard library or in the most popular frameworks, and they look ordinary in a diff.

Python's dangerous sinks are in the standard library

Four examples from the Python documentation itself show the pattern.

SQL built with string formatting. The sqlite3 documentation shows a query built with Python's percent formatting next to a comment warning that it is insecure, and explains that it is vulnerable to SQL injection. It then tells you to use placeholders so the driver binds values separately from the SQL text. The same rule applies to every database driver that follows Python's DB API, and to raw SQL escape hatches in object relational mappers.

Shell commands. The subprocess documentation has a security considerations section. It says that when the shell is invoked explicitly, it is the application's responsibility to quote shell metacharacters to avoid shell injection. The safe default is to pass an argument list and leave the shell out.

Deserialisation. The pickle documentation opens with a warning that the module is not secure. It says that crafted pickle data can execute arbitrary code during unpickling, and that you should never unpickle data that could have come from an untrusted source.

Dynamic code. Python's eval and exec run strings as code. Any path from user input to either one is code injection.

None of these calls is wrong on its own. cursor.execute is how you talk to a database. subprocess.run is how you run a tool. The weakness is the path: untrusted data reaching the call without the right handling on the way. That is exactly what a source to sink trace is built to show.

Line-level checks are not enough for Python

A pattern match can flag every cursor.execute call with an f-string inside it. That catches the obvious case and misses the common one, where the query is built in a helper three functions away from the route that received the input, in a different module.

Python's structure makes the gap wider than in some languages:

  • Frameworks hide the source. In a typical web service, the untrusted value arrives through a framework object such as a request's query arguments, form fields, JSON body or path parameters. The route function rarely touches the database directly. It calls a service layer, which calls a repository layer.
  • Values pass through many hands. A user supplied string may be stripped, lowercased, split, joined, logged and stored in a dictionary before it reaches the sink. Only some of those steps make it safe.
  • Sanitisers are contextual. Escaping for HTML does nothing for SQL. A regular expression that allows only digits makes a value safe for a numeric column and still unsafe as a file name if the code later joins it to a path.

A dataflow trace answers the question a developer actually asks when a scanner flags their code: why is this a problem, and where did the bad value come from. Safeguard's docs make this point directly, saying the trace lets a developer see why something was flagged, not just where.

What the standards ask for

Static analysis is not optional decoration in the main secure development frameworks. Here is what the primary sources say.

NIST SP 800-218, the Secure Software Development Framework. Practice PW.7 is "Review and/or Analyze Human-Readable Code to Identify Vulnerabilities and Verify Compliance with Security Requirements". Task PW.7.1 asks you to decide whether code review, code analysis or both should be used, as your organisation defines. Task PW.7.2 asks you to perform the review or analysis against your secure coding standards and to record and triage every discovered issue and recommended remediation in the development team's workflow or issue tracker. SAST is the "code analysis" half of PW.7. The recording and triage requirement is the part teams most often skip.

OWASP Top 10, 2025 edition. The 2025 list puts Injection at A05:2025. The OWASP page says Injection fell two places, from third to fifth, and maps 37 CWEs to the category, including SQL injection (CWE-89), OS command injection (CWE-78) and code injection (CWE-94). Two other Python relevant weaknesses moved in 2025: path traversal (CWE-22) and server-side request forgery (CWE-918) are now mapped under A01:2025 Broken Access Control, and deserialisation of untrusted data (CWE-502) is mapped under A08:2025 Software or Data Integrity Failures.

CWE Top 25, 2025. MITRE's 2025 list ranks SQL injection second, path traversal sixth, OS command injection ninth, code injection tenth, deserialisation of untrusted data fifteenth and server-side request forgery twenty-second. Every one of those has a well known Python sink.

CISA and FBI Secure by Design alerts. CISA and the FBI have published joint alerts on eliminating SQL injection and on eliminating OS command injection. The OS command injection alert calls it "a preventable class of vulnerability" and tells developers to use built-in library functions that separate commands from their arguments. In Python, that advice translates directly into argument lists for subprocess instead of shell strings.

Together these give an AppSec lead a clear line: analyse the code, map what you find to a standard taxonomy, and record and triage every result in the developer workflow. The next sections show how Safeguard covers the first two, and what you still own for the third.

How a Python dataflow trace reads

Safeguard's docs describe a SAST finding's trace as the ordered hops from source to sink. The best way to see why that matters is to walk a small Python example by hand.

The code below is a hand-written example of a common shape, not Safeguard output. It is a small web service with a search route, a service function and a repository function, split across three files.

# app/routes.py
from flask import Blueprint, request, jsonify
from app.services import find_products

bp = Blueprint("catalog", __name__)

@bp.get("/products")
def list_products():
    term = request.args.get("q", "")          # hop 1: source
    results = find_products(term.strip())     # hop 2: passed on
    return jsonify(results)
# app/services.py
from app.repo import search_by_name

def find_products(term):
    normalised = term.lower()                 # hop 3: transformed, still tainted
    return search_by_name(normalised)         # hop 4: passed on
# app/repo.py
from app.db import get_cursor

def search_by_name(name):
    sql = f"SELECT id, name FROM products WHERE name LIKE '%{name}%'"  # hop 5: string built
    cur = get_cursor()
    cur.execute(sql)                          # hop 6: sink
    return cur.fetchall()

The problem is invisible in any single file:

  • In routes.py, the route only reads a query argument and calls a service. Nothing looks dangerous.
  • In services.py, the function lowercases a string. Nothing looks dangerous.
  • In repo.py, an f-string builds SQL. That looks suspicious, but a reviewer looking at this file alone cannot tell whether name is a constant, a validated value or raw user input.

A source to sink trace stitches these together. For this example the hops a trace would show are:

HopFileWhat happens to the value
1app/routes.pyUntrusted input enters from the request's query arguments
2app/routes.pystrip() removes whitespace and passes it to the service. Still untrusted
3app/services.pylower() changes case. Still untrusted
4app/services.pyPassed to the repository function
5app/repo.pyInterpolated into a SQL string with an f-string
6app/repo.pyExecuted by the database cursor. This is the sink

The weakness is SQL injection, CWE-89, which the OWASP Top 10 2025 maps to A05:2025 Injection. Safeguard's docs say each finding carries the trace, a CWE and OWASP mapping, a severity and a code location. Those four together are what a developer needs to fix the issue and what an auditor needs to see that it was triaged.

The fix in this case is a single line, and it is the one the sqlite3 documentation recommends: bind the value with a placeholder so the driver keeps data and SQL apart.

# app/repo.py, fixed
def search_by_name(name):
    sql = "SELECT id, name FROM products WHERE name LIKE ?"
    cur = get_cursor()
    cur.execute(sql, (f"%{name}%",))
    return cur.fetchall()

The placeholder style depends on your driver. The sqlite3 module uses question marks or named placeholders, and other DB API drivers document their own parameter style. What matters is that the value travels as a parameter, not as part of the SQL text.

Two habits follow from reading traces like this one.

First, fix at the sink, not the source. It is tempting to add input validation in the route. Validation is useful, but the reliable fix for injection is to make the sink safe for any input. A trace makes this easy because it points at the exact call.

Second, look at the middle hops before suppressing. If a trace runs through a function you believe sanitises the value, check that the sanitiser is right for this sink. A function that escapes HTML does nothing for SQL. If the sanitiser is correct and the scanner does not recognise it, record why the finding is not exploitable. Do not delete the evidence.

Which Python weakness classes map to which CWE and OWASP category

The table below lists the classes of weakness a source to sink analyser looks for in Python, with the CWE identifier, its 2025 CWE Top 25 rank, its OWASP Top 10 2025 category and the usual Python fix. The CWE ranks and OWASP mappings come from MITRE's and OWASP's 2025 pages.

This is a reference for the weakness classes themselves. Safeguard's docs name SQL queries, command execution and file paths as example sinks and list Python in phase one; they do not publish a per-rule list for Python. Ask Safeguard which of these sinks are covered in your build before you rely on any one of them.

WeaknessCWE2025 Top 25 rankOWASP 2025Typical Python sinkUsual fix
SQL injectionCWE-892A05 Injectioncursor.execute with an f-string or percent formatting; raw SQL in an ORMPlaceholders and bound parameters
Path traversalCWE-226A01 Broken Access Controlopen, os.path.join, file send helpers with user supplied namesResolve the path and check it stays under an allowed base directory
OS command injectionCWE-789A05 Injectionos.system, subprocess with the shell enabledArgument lists without the shell; no string concatenation
Code injectionCWE-9410A05 Injectioneval, exec, dynamic imports from user inputRemove dynamic evaluation; use explicit dispatch tables
Deserialisation of untrusted dataCWE-50215A08 Software or Data Integrity Failurespickle.loads on request bodies, cache values or queue messagesUse a data only format such as JSON; never unpickle untrusted data
Server-side request forgeryCWE-91822A01 Broken Access ControlHTTP client calls with a user supplied URLAllow-list destinations; block internal address ranges

Three notes on reading the table for Python.

Shell use is the switch for command injection. The subprocess docs are explicit that quoting is your job once you invoke the shell. Passing a list of arguments with the shell left off removes the whole class for that call.

Pickle has no safe mode for untrusted input. The Python docs do not offer a sanitiser for pickle. The fix is to stop unpickling untrusted data. If a trace shows request data or a shared cache reaching pickle.loads, the remediation is a format change, not a filter.

OWASP 2025 moved things. Teams that wrote policies against the 2021 list will find SSRF is no longer its own category; the 2025 page maps CWE-918 into A01. Safeguard's docs say SAST findings carry OWASP mapping but do not say which edition the SAST mapping uses. Safeguard's separate OWASP Top 10 framework page in its compliance docs references the 2021 edition. If your policy is written against a specific edition, confirm which one your SAST findings use.

How to run SAST for Python in Safeguard, step by step

The steps below follow Safeguard's Application Security Testing, Unified Findings, CLI, Local Runner and Guardrails docs. Where the docs do not show a screen or command, the step describes the outcome instead.

1. Confirm SAST is switched on for your tenant

SAST is generally available, and Safeguard's Unified Findings docs say every engine is independently controlled by a feature flag that an administrator manages. The SAST sub-tab in a project's Application Security panel only appears when the tenant has the SAST flag enabled. If you do not see it, ask your Safeguard administrator or your Safeguard contact to enable SAST for your tenant before you go further.

While you are asking, get two answers in writing:

  • Which Python frameworks and sinks are modelled in your build.
  • Which OWASP Top 10 edition the SAST mapping uses.

Both can change as the engine evolves and affect your policy.

2. Decide where the scan runs

Safeguard's docs give two places for SAST to run: your pipeline, or a local runner so source never leaves your perimeter. The choice usually comes down to who is allowed to read the source.

SituationWhere to run Python SASTWhy
Repository already connected to Safeguard, pipeline scans are normal for youPipeline, through the Safeguard SAST integrationSame integration flow as your other scans; results land in the project
Private repository you cannot or will not grant a cloud service access toLocal runner (GA)The runner executes jobs on your machine and uploads only results, never source
Restricted network that allows only outbound HTTPSLocal runner (GA)All runner traffic is outbound HTTPS that the runner opens
No internet egress at all, classified or regulated environmentAir-gapped deployment (GA)Safeguard's docs say the CLI and runner are available inside an air-gapped deployment
A developer wants feedback on a branch before pushingSafeguard CLI (GA) on the developer's machinePoint the scan at a local directory; no build needed

Our post on choosing between a repository connection, the CLI and the local runner covers this decision in more depth for every Safeguard scan type.

3. Add the Safeguard SAST integration

Safeguard's docs say you add an AppSec integration from the Integrations page, the same way you add a repository or registry. For SAST:

  1. Open Integrations.
  2. Add Safeguard SAST.
  3. Select a repository, or point at a source directory.

Scans then trigger through the standard integration, pipeline and CLI flow, and results appear in your findings views.

For a Python repository, think about which directory you point at. Point the scan at your own application source. A checked-in virtual environment or a vendored copy of third-party packages is not your code; vulnerabilities in third-party packages are what SCA (GA) is for, and analysing them with SAST adds noise you cannot fix in place.

4. Or run it from the Safeguard CLI

The Safeguard CLI (GA) installs with a one-line installer. These are the commands from Safeguard's Local Runner docs:

# macOS and Linux
curl -fsSL https://cli.safeguard.sh/install | bash

# Windows (PowerShell)
irm https://cli.safeguard.sh/install.ps1 | iex

Start it with safeguard, type /login, and approve the session in your browser. The docs say the first run also downloads the scanner tools and the vulnerability database.

The SAST command, exactly as Safeguard's Application Security Testing docs show it:

# Static analysis of a source directory
safeguard appsec sast --dir ./src --tenant-id <tenant> --product-id <product>

Replace the directory with the root of your Python package. For a typical layout that is the folder that holds your application modules, such as ./app or ./src. Replace the tenant and product values with your own; the product ID decides which product the findings attach to, so use one product per service if you want results grouped that way.

Safeguard's docs do not list extra flags for Python, such as interpreter version, framework hints or exclusion patterns, so do not assume them. If you need to exclude tests, generated code or migrations, the reliable approach today is to point the scan at the directory that holds only the code you want analysed.

5. Or let the local runner pick up the job

If your organisation wants scans assigned from the platform but executed on your own hardware, use the local runner (GA). Safeguard's docs describe it as part of the CLI, not a separate product. It sends a heartbeat, polls the platform for jobs assigned to that machine, runs each job with its own local scan server, and uploads the result files and job status. Source code stays on the machine.

The headless commands from the docs:

# Start without the terminal UI and serve work for this workspace
safeguard runner start --workspace ./path/to/repo

# Is it running, and what is it doing
safeguard runner status

# Drain in-flight work and stop
safeguard runner stop

On networks that do not allow long-held connections, the docs say to set the poll wait to zero so the runner polls on a fixed interval instead.

6. Read the findings in the app

Safeguard's docs say there is no CLI command for listing SAST findings yet. Read them in the app:

  1. Open the project.
  2. On the project Info page, the Scanner Coverage section shows one card per scanner you can access, with a live finding count. Click the SAST card to jump straight into the Findings tab with SAST preselected.
  3. Or open the Findings tab directly and choose SAST in the Scanner dropdown at the top. That scopes the table, the summary stats and the underlying queries to SAST findings.
  4. Further down the same tab, the Application Security panel has a SAST sub-tab, which appears when your tenant has the SAST flag enabled.

Each SAST finding shows severity, rule, CWE, location and the dataflow trace, per the docs. Start with the trace. Read it hop by hop, as in the worked example above, before you decide anything.

A note on the API. Safeguard's ESSCM Findings docs page carries a banner saying its REST endpoints are not yet available and describe the intended shape only. Do not build automation against those endpoints today. Use the app, the CLI and your CI integration instead.

7. Triage every finding, and record the decision

This is the step that SSDF task PW.7.2 cares about most: record and triage every discovered issue in the development team's workflow.

Safeguard's ESSCM Findings docs describe a status workflow for findings (Open, In Progress, Resolved, Mitigated, Suppressed) and detail view actions to resolve, mitigate, suppress, assign and export. They also describe sending findings to issue trackers such as Jira, GitHub Issues, Azure DevOps and ServiceNow. Those docs cover findings generally, so check that every action you plan to use is available on SAST findings in your build.

A workable Python triage routine:

  • True positive, fix now. The trace reaches the sink with no adequate sanitiser. Assign it to the owning team with the trace attached.
  • True positive, mitigated elsewhere. For example, the sink is only reachable by an authenticated administrator and the risk is accepted for now. Mark it mitigated and write down the reason and the review date.
  • False positive. The trace passes through a sanitiser the scanner does not recognise, or the source is not actually reachable from outside. Record which hop makes it safe. GA engines will produce some of these; the record helps Safeguard tune them and helps your auditor.
  • Duplicate. If you also run DAST (GA), the same issue may appear from both engines. Safeguard's docs say SAST, DAST and SCA findings share correlation keys, and that a DAST-confirmed runtime issue can be linked to the SAST source code sink that produced it. AutoTriage (GA) is documented as collapsing cross-engine duplicates into one canonical finding.

8. Use findings in policy, carefully

Policies and gates are GA in Safeguard. Safeguard's Application Security Testing docs point to Guardrails and Enforcement for using findings as policy gates in CI/CD. The Guardrails docs describe policies written in YAML with three possible effects: BLOCK, WARN and AUTO_FIX.

For any new scanner in your pipeline, the safest rollout is:

  1. Start with WARN on SAST findings for a few Python services. Watch the false positive rate for two or three sprints.
  2. Move the highest confidence classes (for example SQL injection with a full trace from a request source) to BLOCK on new code only.
  3. Keep historical findings out of the gate until they are triaged, or every pull request will fail for reasons the author did not cause.

The Guardrails docs list the data that rules evaluate against, and that list centres on SBOM, vulnerability, license, signature, provenance and reachability data. Confirm with Safeguard how SAST findings are exposed to your gate before you write a BLOCK rule against them. Our post on turning plain English security policies into release gates walks through the policy side in detail.

9. Fix the code, then rescan

SAST findings are fixed in your code. Safeguard's autonomous remediation (GA) ships switched off, with every category set to Manual, and is documented around dependency upgrades and related categories. The docs do not describe an automatic fix pull request for a SAST finding, so plan on developers making the change, guided by the trace.

After the fix merges, rescan the same directory or let the next pipeline run pick it up, and check whether the same trace still appears. If it does, compare the new trace with the old one; a common cause is a second path to the same sink that the first fix did not cover.

Requirement versus what Safeguard does versus what you still own

RequirementWhat it asksWhat Safeguard doesWhat you still own
SSDF PW.7.1Decide whether code review, code analysis or both applyProvides automated code analysis for Python through SAST (GA)The decision and the written policy on when each is used
SSDF PW.7.2Perform the analysis against your secure coding standard and record and triage every issue in the team's workflowProduces findings with trace, CWE, OWASP mapping and severity in one findings modelYour secure coding standard; the triage decisions; the tracker workflow
SSDF PW.8.2Scope, design and perform executable code testing and record resultsDAST (GA) tests running applications against verified targets, and links to SAST sinksTest design, environments and authorisation
OWASP Top 10 2025, A05 InjectionPrevent injection across the mapped CWEsMaps SAST findings to CWE and OWASP; traces injection paths to the sinkConfirming which OWASP edition the mapping uses; the fixes
CWE Top 25 2025Prioritise the most dangerous weakness classesCWE identifiers on each finding let you filter and report against the listChoosing which classes to gate on
CISA and FBI Secure by Design alerts on SQL and OS command injectionEliminate these classes at the source with parameterised queries and argument separated commandsTraces show where unparameterised SQL and shell built commands are reachable from inputChanging the code patterns and adding them to your coding standard

Where Python SAST fits next to SCA and DAST

A Python service has three different kinds of risk, and Safeguard has a different engine for each.

  • SCA (GA) looks at the packages you install from PyPI and their transitive dependencies, matches them to known vulnerabilities, and prioritises with EPSS, KEV and reachability analysis (GA). It answers: is a library I depend on vulnerable, and does my code call the vulnerable part.
  • SAST (GA) looks at the code you wrote. It answers: does untrusted input reach a dangerous call in my own modules.
  • DAST (GA) looks at the running service from the outside, against a target you have verified you own. It answers: does the deployed application actually respond in a vulnerable way.

The engines overlap at the edges. A SQL injection in your repository layer is a SAST finding. The same issue, confirmed by a request against the running service, is a DAST finding. Safeguard's unified findings model is designed to link those two, so the pair is prioritised together instead of being worked twice.

They do not overlap on dependencies. A vulnerable version of a PyPI package is an SCA finding. SAST does not replace SCA, and pointing SAST at site-packages does not turn it into one.

For the reachability side, see how Safeguard prioritises vulnerabilities with reachability, EPSS and KEV. For the DAST side, see what Safeguard DAST scopes, discovers and will not test. For the full capability list with labels, see what Safeguard does today, capability by capability.

Python specifics that change how you set it up

A few features of Python projects deserve a decision before your first scan.

Project layout

Python projects use several layouts: a flat package at the repository root, a src layout, a Django project with many apps, or a monorepo with one directory per service. The CLI takes a directory. Point it at the directory that holds your importable application code. For a monorepo, run one scan per service directory and attach each to its own product, so findings and ownership line up.

Tests, migrations and generated code

Test suites often contain deliberately unsafe calls, such as raw SQL in fixtures or shell commands that set up test data. Database migration files may contain raw SQL by design. Generated code, such as protocol buffer stubs, is not something your developers edit. Decide up front whether these directories are in scope. Since the docs do not show an exclusion flag for the SAST command, scope by directory.

Frameworks and dynamic dispatch

Static analysis of any dynamic language has limits, and Python is very dynamic. Routes registered by decorators, handlers looked up with getattr, plugins loaded by name at run time, and monkeypatching all make it harder for any static tool to see every path from a source to a sink. Expect the engine to follow straightforward calls across functions and files more reliably than heavily dynamic code. If a known risky path does not appear as a finding, do not assume it is safe; review it by hand and tell Safeguard, so the case can be modelled.

Jupyter notebooks and scripts

Data teams often keep notebooks and one-off scripts in the same repository as services. Safeguard's docs do not say whether notebooks are analysed. If notebooks or scripts reach production systems or credentials, ask before you assume coverage.

What Safeguard does not do

These limits are specific to SAST for Python as Safeguard documents it today.

  1. Detection depth is still expanding. Safeguard's own docs say SAST and DAST are rolling out and that detection depth expands over time. Expect missed paths and some false positives, especially in heavily dynamic code. Do not present SAST results to an auditor as complete coverage.
  2. There is no published per-rule list for Python. The docs name example sinks (SQL queries, command execution, file paths) and list Python as a phase one language. They do not list which Python frameworks, libraries or sinks are modelled. You need to ask, and to re-ask as the engine changes.
  3. There is no CLI command to list SAST findings, and no working findings REST API yet. The docs say there is no CLI listing command. The ESSCM Findings page says its REST endpoints are not yet available. Plan on reading results in the app until that changes.
  4. It does not fix your code for you. Autonomous remediation (GA) ships switched off, with every category set to Manual, and the docs do not describe automatic fix pull requests for SAST findings. Developers make the fix.
  5. It does not replace human code review. SSDF PW.7 treats code review and code analysis as complements. Business logic flaws, broken authorisation rules and insecure design rarely show up as a source to sink path, and a scanner will not tell you that a feature should not exist.
  6. It does not analyse third-party packages as your code. Vulnerabilities in PyPI dependencies are SCA's job. SAST is for the modules your team writes.
  7. It does not confirm exploitability on its own. A trace shows a path in code. Whether it is exploitable in your deployment depends on configuration, authentication and network exposure. DAST (GA) and your own testing answer that part.
  8. It does not cover every language yet. Phase one is JavaScript and TypeScript, Python and Java. If a Python service shells out to scripts in another language, or calls a native extension, the analysis stops at the boundary.
  9. It does not decide your policy. Which classes block a merge, which teams own which findings, and how fast each severity must be fixed are your decisions. Safeguard enforces the policy you write.

Common mistakes

Pointing the scan at the whole repository including a virtual environment. Thousands of third-party files get analysed as if your team wrote them. Point at your application directory and leave dependencies to SCA.

Turning on BLOCK on day one. A new scanner plus a historical backlog equals a red pipeline that developers learn to bypass. Start with WARN, triage the backlog, then block on new code for the highest confidence classes.

Fixing at the source instead of the sink. Adding a length check in the route does not stop SQL injection in the repository layer. Use placeholders at the query, argument lists at the subprocess call, and an allowed base directory check at the file open.

Suppressing without a reason. SSDF PW.7.2 asks you to record and triage every issue. A suppressed finding with no note is a finding nobody can defend six months later. Write down which hop makes it safe.

Treating "no findings" as "no vulnerabilities". For an engine with no published rule list, an empty result means the scanner found nothing it models. Keep code review and DAST in the process.

Assuming the OWASP edition. The 2025 Top 10 moved SSRF and path traversal under Broken Access Control and moved Injection to fifth place. If your policy references category IDs, confirm which edition your findings use.

Running SAST and DAST in separate projects. Correlation works on the shared findings model. Keep the scans for one service in one project so a DAST-confirmed issue can link to its SAST sink.

FAQ

Does Safeguard support SAST for Python?

Yes. Safeguard's Application Security Testing docs list JavaScript and TypeScript, Python and Java as the phase one SAST languages. Each finding includes a source to sink dataflow trace, CWE and OWASP mapping, severity and code location.

Is Safeguard SAST generally available?

Yes. Safeguard's docs say SAST and DAST are rolling out and that detection depth expands over time.

How do I run a Python SAST scan from the command line?

Install the Safeguard CLI, run safeguard, type /login, then run the SAST command from Safeguard's docs against your source directory with your tenant and product IDs. The exact command is in step 4 above.

Do I need to build or install my Python dependencies before scanning?

Safeguard's docs say SAST takes a connected repository or a local directory and that no build is required. You point it at source. Dependency vulnerabilities are handled separately by SCA (GA).

Does my Python source code leave my network?

Not if you use the local runner. Safeguard's docs say the runner executes scan jobs on your machine and uploads only the results, never your source code, and that all its traffic is outbound HTTPS it opens itself. Air-gapped deployment (GA) is also available.

What does a SAST dataflow trace show?

The ordered hops from source to sink: where untrusted input enters, each function and file it passes through, and the dangerous call it reaches. Safeguard's docs say this lets a developer see why something was flagged, not just where.

Which CWE and OWASP categories do Python SAST findings map to?

Each finding carries a CWE and OWASP mapping. Common Python weakness classes include SQL injection (CWE-89), path traversal (CWE-22), OS command injection (CWE-78), code injection (CWE-94), deserialisation of untrusted data (CWE-502) and SSRF (CWE-918). Ask Safeguard which sinks your build models and which OWASP edition it maps to.

Can Safeguard open a pull request to fix a SAST finding?

Safeguard's docs do not describe automatic fix pull requests for SAST findings. Autonomous remediation (GA) ships switched off, with every category set to Manual, and is documented around dependency and related categories. Plan on developers fixing SAST findings, guided by the trace.

Can I fail a build on Python SAST findings?

Policies and gates are GA, and Safeguard's docs point to Guardrails and Enforcement for using findings as CI/CD gates. Start with a WARN effect, confirm with Safeguard how SAST findings reach your gate, then block only high confidence classes on new code.

If you are ready to try it, download the CLI and apps at safeguard.sh/download and follow the setup guides at docs.safeguard.sh. For how the IDE side and the Claude and ChatGPT connector directory installs fit around a scan, see Safeguard in your IDE and in Claude and ChatGPT.

SAST helps with the code your team writes, but most Python services also run on a long tail of PyPI packages and base images that your team did not write. The block below is the same one we publish on every product post; it covers what to do when the vulnerable code sits in a dependency rather than in your own modules.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The first demo is a real Safeguard CLI session. It shows the install, safeguard opening the interactive terminal, and a request to fix a dependency CVE in a repository: the CLI reads the finding and advisory, asks before any command that changes something, edits the manifest, runs the install and tests with approval, prints a summary and pushes nothing. It ends with the permissions, model and scan slash commands. The same CLI is where you sign in and run the SAST command for a Python source directory.

Click through it at your own pace, or open it full screen.

The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. This is the route for Python teams whose source cannot leave their network: the runner executes the job locally and uploads only results.

Click through it at your own pace, or open it full screen.

Quick check

A SAST trace shows request input reaching a subprocess call through two helper functions. Where is the most reliable fix?

At the sink. Pass the command as an argument list with the shell left off, as the Python subprocess docs and the CISA and FBI alert on OS command injection both advise. Input validation in the route is useful, but it does not make the subprocess call safe for every caller.

Your first Python SAST scan in Safeguard returns no findings. Can you report the service as free of injection flaws?

No. Safeguard's docs say detection depth is still expanding, and there is no published per-rule list for Python. An empty result means nothing the scanner models was found. Keep code review and DAST in the process, and ask Safeguard which sinks your build covers.

Sources

Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.