From ce6076f7f40fd4a5bf42904cb48c0913c0b270d4 Mon Sep 17 00:00:00 2001 From: Macky Date: Fri, 7 Aug 2026 16:09:42 +0700 Subject: [PATCH] [verified] Apply independent-reviewer suggestions: mask sales_kit/report to trainees, ready-gate get_persona Responding to the requesting-code-review independent reviewer (passed:true, zero blocking issues): - get_group: hide sales_kit + report from trainees (they contain latent pain analysis) - get_persona: add status==ready gate for trainees (parity with list_personas) - Extend test_security.py with latent-leak + non-ready-gate assertions; all suites pass --- backend/app/api/group_routes.py | 12 ++++++++---- backend/scripts/test_security.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/backend/app/api/group_routes.py b/backend/app/api/group_routes.py index 3e76a9b..cc56c93 100644 --- a/backend/app/api/group_routes.py +++ b/backend/app/api/group_routes.py @@ -195,10 +195,11 @@ def get_group(gid: str): view = dict(group) if actor.get("role") == "user": - # Trainee: hide latent persona fields + sales kit details they shouldn't see - view["personas"] = [ - revealable_view(p) for p in group.get("personas", []) - ] + # Trainee: hide latent persona fields + sales kit + report (they contain + # pain analysis / latent data a real seller wouldn't know before a result). + view["personas"] = [revealable_view(p) for p in group.get("personas", [])] + view["sales_kit"] = None + view["report"] = None return jsonify({"group": view}) @@ -234,6 +235,9 @@ def get_persona(gid: str, pid: str): if not p: raise ApiError("persona not found", 404) actor = current_user() + # Trainees may only view personas from ready groups (parity with list_personas). + if actor.get("role") == "user" and group.get("status") != "ready": + raise ApiError("group not ready", 403) ensure = ensure_persona_shape(p) if actor.get("role") == "user": return jsonify({"persona": revealable_view(ensure)}) diff --git a/backend/scripts/test_security.py b/backend/scripts/test_security.py index 9322016..aa2dcaf 100644 --- a/backend/scripts/test_security.py +++ b/backend/scripts/test_security.py @@ -96,6 +96,20 @@ def main(): assert my_gid not in ids, "t3 should not see t2's private group in listing" print("[ok] personal group hidden from other users' listing") + # Leak check: trainee get_group must NOT expose sales_kit/report (latent data). + # admin group `gid` is draft with full sales_kit? It has none yet, but report/sales_kit keys exist. + # We need a READY group to prove masking. t2's personal group (my_gid) is ready. + r = client.get(f"/api/groups/{my_gid}", headers=TH) + body = r.get_json()["group"] + assert body.get("sales_kit") is None and body.get("report") is None, "trainee get_group leaked sales_kit/report!" + print("[ok] trainee get_group masks sales_kit + report (no latent leak)") + + # Trainee cannot get persona from a NOT-ready group (draft admin group gid). + # 403 (ready-gate) or 404 (persona absent in draft group) both prevent data exposure. + r = client.get(f"/api/groups/{gid}/personas/persona-01", headers=TH) + assert r.status_code in (403, 404), f"trainee should not view non-ready group persona, got {r.status_code}" + print(f"[ok] trainee blocked from persona in non-ready group ({r.status_code})") + print("\nALL SECURITY TESTS PASSED")