Safeguard
Security Guides•

Does pandas read_sql protect you from SQL injection?

pandas read_sql does not sanitize SQL, it forwards your string to the driver. Use the params argument, not f-strings, and know your driver's paramstyle.

Safeguard Security Team
4 min read

No. The pandas documentation says it does not attempt to sanitize SQL statements and simply forwards the statement to the underlying driver. If you build the query with an f-string or string concatenation, injection is possible. Pass values through the params argument and let the driver bind them.

What pandas actually says

The Notes section of read_sql states that pandas does not attempt to sanitize SQL statements. It forwards the statement to the underlying driver, which may or may not sanitize from there, and the docs say to be wary when accepting statements from arbitrary sources. Separately, for parameterized queries, the page recommends using params over string interpolation.

That is a clear division of labour. Pandas is a conduit. Safety comes from how you build the query.

The unsafe pattern and the safe one

Analysts often write queries like this, with a value taken from a notebook widget, a web form or a config file:

# unsafe: the value becomes part of the SQL text
q = f"SELECT * FROM orders WHERE customer = '{name}'"
df = pd.read_sql(q, conn)

If name is controlled by someone else, they can close the quote and add SQL of their own. In a read-only role the damage may be limited to data exposure, but exposure is usually the point of an attack on an analytics database.

The safer form keeps values out of the SQL text:

from sqlalchemy import text

q = text("SELECT * FROM orders WHERE customer = :name")
df = pd.read_sql(q, conn, params={"name": name})

The page shows this style, with a SQLAlchemy text() query and a named placeholder.

Know your driver's paramstyle

The params argument is driver dependent. The pandas docs tell you to check your driver documentation for which of the five styles in PEP 249 it supports. Their example is psycopg2, which uses %(name)s placeholders with a dictionary. Other drivers use question marks or colon names. If you copy a query from one database to another, the placeholder syntax may change, and that is a common reason people fall back to f-strings. Resist it.

What parameters cannot do

Placeholders bind values. They do not bind table names, column names or sort directions. If a user picks the column to group by, do not paste it into the SQL. Check it against a fixed allow list in code, then insert the approved identifier.

allowed = {"region", "product", "month"}
if col not in allowed:
    raise ValueError("unknown column")

A short review checklist

  1. Search the repository for read_sql calls whose first argument is built with an f-string, format or plus sign.
  2. Check where each variable comes from, including notebook inputs and scheduled job parameters.
  3. Replace values with bound parameters.
  4. Allow list any identifier that varies.
  5. Run the query with a database role that can only read what the job needs.

Try it yourself

Click through it at your own pace, or open it full screen. Look at how a scan flags user input reaching a query.

Where Safeguard fits

Safeguard offers code scanning and dependency scanning, and you can run a free scan on a repository that holds your analytics code.

Quick check

Does using SQLAlchemy make read_sql safe?

Only if you pass values as bound parameters. A SQLAlchemy engine does not protect a query string you built with an f-string.

Can parameters protect a table name?

No. Placeholders bind values only. Validate identifiers against an allow list.

Sources

Last reviewed: 19 October 2026.

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.