Skip to content

Use parameterized SQL in query_tracks() to prevent injection#78

Open
arpitjain099 wants to merge 1 commit into
dbetchkal:mainfrom
arpitjain099:fix/parameterize-sql-queries
Open

Use parameterized SQL in query_tracks() to prevent injection#78
arpitjain099 wants to merge 1 commit into
dbetchkal:mainfrom
arpitjain099:fix/parameterize-sql-queries

Conversation

@arpitjain099

Copy link
Copy Markdown
Contributor

Summary

The query_tracks() function in nps_active_space/utils/helpers.py builds its SQL query by interpolating start_date and end_date directly into the query string via f-strings. If those values ever come from user-controlled input, this could allow SQL injection.

This change switches the date clause to use SQLAlchemy's text() with bound parameters (:start_date, :end_date), so the database driver handles escaping safely. The spatial filter (ST_Intersects with WKT geometry) is left as a string literal since it is derived from an internally-constructed GeoDataFrame, not from external input.

What changed

  • Added from sqlalchemy import text import
  • Replaced the f-string date interpolation with named bind parameters
  • Wrapped the query string in text(...).bindparams(...) before passing it to from_postgis()

The query logic and output are unchanged. gpd.GeoDataFrame.from_postgis() accepts text() objects the same way it accepts raw strings.

Testing

  • Verified the module parses without syntax errors
  • The generated SQL is logically identical to the original, just with driver-level parameter binding for the date range

Replace f-string interpolation of date values in the SQL query with
SQLAlchemy text() bound parameters. This prevents potential SQL
injection through the start_date and end_date arguments.

The spatial filter (ST_Intersects with WKT) is left as a literal since
the geometry is derived from an internally-constructed GeoDataFrame,
not from external user input.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
@dbetchkal dbetchkal requested a review from elliott-ruebush July 2, 2026 18:04

@elliott-ruebush elliott-ruebush left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know that we need protection from SQL injection atm, but this is a cheap change, so seems reasonable to move ahead with it for now.

wheres = [f"fp.ak_datetime::date BETWEEN '{start_date}' AND '{end_date}'"] # start and end date are inclusive
# Use bound parameters for date values to prevent SQL injection
wheres = ["fp.ak_datetime::date BETWEEN :start_date AND :end_date"]
params = {"start_date": start_date, "end_date": end_date}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be out of scope for this PR, but should we change the type of these to datetime while we're editing the code here? Doing so may help with ensuring they're converted properly for use in this SQL query.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. The start/end values come in as strings from the config and get passed straight through to the query. Converting them to datetime objects at the boundary (right before the query) would catch format issues early and make the parameterized query more robust. I can add that in this PR if you'd like, or keep it scoped and handle the conversion separately.

ak_albers_mask = mask.to_crs(epsg=3338)
mask.geometry = ak_albers_mask.buffer(
mask_buffer_distance).to_crs(epsg=4326)
# Spatial filter uses internally-generated WKT from the mask geometry,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have external users inputting things into this software from anywhere at the moment, so theoretically you could argue we don't need the protection from SQL injection for any queries at the moment. However, I think it's pretty reasonable to use passed params just for best practice and in case we use this behind a user-facing application in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, no external user input today but parameterized queries are just good hygiene. Thanks for the approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants