fix(nspd-geo): replace f-string bulk INSERT with parametrized batch execute (#123)

Per code review audit (May 14): real SQL injection vector в `enqueue_geo_job`.
Manual `chr(39)*2` quote escape НЕ injection-safe — не защищает от backslash
escape, Unicode quote tricks. Если cad_num когда-либо придёт из user input —
exploitable.

## Change

`backend/app/workers/tasks/nspd_geo.py:298-310` — заменил f-string VALUES
concatenation на SQLAlchemy `text(...)` + list of dicts (psycopg v3 handles
batch executemany под капотом).

Pattern consistent с остальными INSERT'ами в этом же файле (_log,
_start_job, _save_quarter, _save_parcel, _save_building).

## Preserved

- ON CONFLICT (job_id, cad_num, thematic_id) DO NOTHING — verbatim
- Return value (int(job_id))
- Surrounding tx/commit/close logic
- DB schema, function signature, caller interface — unchanged

## Tests

- No test_nspd_geo.py существует — skipped
- ruff check passed

## Vault

`fixes/Bug_Nspd_Geo_Sql_Injection_May14.md` — created (status: resolved).

No backward compat / migration concerns — pure in-place query rewrite.

Co-authored-by: lekss361 <claudestars@proton.me>
This commit is contained in:
lekss361 2026-05-14 23:29:12 +03:00 committed by GitHub
parent 7b32edc34d
commit 8dfab7613d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -295,17 +295,18 @@ def enqueue_geo_job(
}, },
).scalar_one() ).scalar_one()
# Bulk INSERT targets # Bulk INSERT targets — parametrized to prevent SQL injection
if cad_nums_with_thematic: if cad_nums_with_thematic:
values = ",".join(
f"({job_id}, '{c.replace(chr(39), chr(39)*2)}', {t}, 'pending')"
for c, t in cad_nums_with_thematic
)
db.execute( db.execute(
text( text(
f"INSERT INTO nspd_geo_targets (job_id, cad_num, thematic_id, status) " "INSERT INTO nspd_geo_targets (job_id, cad_num, thematic_id, status) "
f"VALUES {values} ON CONFLICT (job_id, cad_num, thematic_id) DO NOTHING" "VALUES (:job_id, :cad_num, :thematic_id, 'pending') "
) "ON CONFLICT (job_id, cad_num, thematic_id) DO NOTHING"
),
[
{"job_id": job_id, "cad_num": c, "thematic_id": t}
for c, t in cad_nums_with_thematic
],
) )
db.commit() db.commit()
return int(job_id) return int(job_id)