security: pip-audit + bandit gates that can fail; patch 29 known CVEs

security.yml previously ran safety/bandit/semgrep with `|| true` and could
not go red. Now: pip-audit on requirements.txt is a hard gate (dev deps
reported only), bandit HIGH fails (B104 bind-all skipped: intended behind
Cloudflare/Caddy), pip-licenses uploaded as a report. Weekly + on
dependency/source changes.

Running it locally found 29 advisories, all in pinned-and-forgotten
runtime deps: starlette 0.27 (7, incl. Host-header path confusion and
form DoS), fastapi 0.104, requests 2.31 (3), pymysql 1.1. Bumped to
current: fastapi 0.141.1 / starlette 1.6.0, pydantic 2.13.5, uvicorn
0.52.4, requests 2.34.2, pymysql 1.2.0; dev: pytest 9.1.1, black 26.5.1.
pip-audit is now clean. requires-python narrowed to 3.11 (the truth:
psycopg2-binary 2.9.9 fails on 3.13; pandas 2.0.3 has no 3.12 wheels).
Full suite passes; API smoke-tested (health, stations, forecast, history,
stats, docs, openapi) on the new stack. black 26 reformatted 8 files.
This commit is contained in:
2026-09-11 23:44:43 +02:00
parent 97a6694ab2
commit b03318210c
14 changed files with 285 additions and 1048 deletions
+3 -3
View File
@@ -207,9 +207,9 @@ def fill_from_hii(
"timestamp": missing["timestamp"],
"station_code": code,
"water_level": missing["wl_msl"] - offset,
"discharge": missing["discharge"]
if code in _HII_EXACT_MIRRORS
else float("nan"),
"discharge": (
missing["discharge"] if code in _HII_EXACT_MIRRORS else float("nan")
),
}
)
fills.append(fill)
+6 -6
View File
@@ -320,9 +320,9 @@ def train_station(
skipped_heads,
)
else:
skipped_heads[
head_key
] = f"only {n_pos} positives in train span (< {MIN_POSITIVES_FOR_CLASSIFIER})"
skipped_heads[head_key] = (
f"only {n_pos} positives in train span (< {MIN_POSITIVES_FOR_CLASSIFIER})"
)
heads[head_key] = clf
if not skip_eval:
@@ -417,9 +417,9 @@ def train_station(
if clf is not None:
skipped_heads.pop(head_key, None)
else:
skipped_heads[
head_key
] = f"only {n_pos} positives in train span (< {MIN_POSITIVES_FOR_CLASSIFIER})"
skipped_heads[head_key] = (
f"only {n_pos} positives in train span (< {MIN_POSITIVES_FOR_CLASSIFIER})"
)
final_heads[head_key] = None
# v4 = + Mae Ngat dam features; v3 = rise + rain; v2 = rise target only