fix: A12–A24 — SSRF, auth routes legacy, uploads, N+1 et routes doublonnes (v7.3.3)
FlowDeck CI / docker (push) Successful in 1m44s
FlowDeck CI / lint (push) Successful in 1m53s
FlowDeck CI / test (push) Successful in 20m45s

- A12 — `og_fetcher` : GET sans `follow_redirects`, `_is_public_host` revérifié à
  chaque saut (max 5) ; `POST /board/api/og/metadata` → 400 sur hôte privé/loopback
- A13 — router automations sous `Depends(_require_session)` (CRUD, run,
  press-button) + `created_by` sans fallback ; action `webhook` validée par
  `_is_public_host` avant POST (SSRF)
- A15 — webhooks sortants : `_require_admin` sur GET/POST/DELETE + `_is_public_host`
  sur l'URL en création
- A17 — router legacy `/api` sous `Depends(_require_session_or_bearer)` (session ou
  Bearer `/api/v1`), allowlist explicite `/api/health` + `/api/frontend-error`
- A22 — les 2 uploads locales : session exigée (`_require_user_id`) + `validate_upload`
  branché (taille + extension) + `FLOWDECK_DATA_DIR` au lieu de `/data` codé en dur
- A23 — N+1 : COUNT→`GROUP BY` (dashboard), cards→`executemany` (board sync),
  duplicata de propriétés→`executemany` + remap des ids par SELECT (collections)
- A24 — 2 routes écrasées supprimées : `GET /api/projects` (api.py) et
  `GET /workspace` (workspace.py) + test « aucun doublon méthode+chemin »
- Tests : +9 dans `tests/test_audit_p0_fixes.py` (SSRF, 401s, validate_upload,
  doublons de routes) ; tests OG sur hôtes résolubles (la garde fait du DNS)
- suite **1025/1025** · `ruff check app tests` OK
This commit is contained in:
2026-09-30 23:12:20 -04:00
parent 8ab6569974
commit 5a537f5dc3
15 changed files with 265 additions and 67 deletions
+22 -7
View File
@@ -373,20 +373,35 @@ async def duplicate_collection_api(request: Request, collection_id: int):
"SELECT * FROM collection_properties WHERE collection_id=? ORDER BY position",
(collection_id,),
).fetchall()
for p in rows:
ncur = conn.execute(
# A23 : un seul executemany ; les rowid sont contigus (même transaction,
# insertion dans l'ordre de `rows`), donc le mappeur se fait par index.
tuples = [
(new_id, p["name"], p["prop_type"], p["options_json"], p["number_format"],
None, p["reverse_name"], None, None, p["rollup_function"],
p["formula_expression"], p["position"], p["required"],
p["visible_in_views"])
for p in rows
]
if tuples:
ncur = conn.executemany(
"""INSERT INTO collection_properties
(collection_id, name, prop_type, options_json, number_format,
related_collection_id, reverse_name, relation_property_id,
target_property_id, rollup_function, formula_expression,
position, required, visible_in_views)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(new_id, p["name"], p["prop_type"], p["options_json"], p["number_format"],
None, p["reverse_name"], None, None, p["rollup_function"],
p["formula_expression"], p["position"], p["required"],
p["visible_in_views"]),
tuples,
)
prop_map[p["id"]] = ncur.lastrowid
new_ids = [
r["id"]
for r in conn.execute(
"SELECT id FROM collection_properties WHERE collection_id=? ORDER BY id",
(new_id,),
).fetchall()
]
assert len(new_ids) == len(tuples), "remap des propriétés : effectif inattendu"
for p, new_pid in zip(rows, new_ids, strict=True):
prop_map[p["id"]] = new_pid
# Fix cross-property references after all rows exist (creates may target
# columns not inserted yet). Related collection remapped to the copy.