diff --git a/backend/app/routers/tests.py b/backend/app/routers/tests.py index 2551b8b..11b4a28 100644 --- a/backend/app/routers/tests.py +++ b/backend/app/routers/tests.py @@ -163,46 +163,6 @@ from app.services.test_workflow_service import ( router = APIRouter(prefix="/tests", tags=["tests"]) -# --------------------------------------------------------------------------- -# Blind visibility — hide the other team's fields until both reviews pass -# --------------------------------------------------------------------------- - -_RED_ONLY_FIELDS = [ - "procedure_text", "tool_used", "attack_success", - "execution_start_time", "execution_end_time", "red_summary", - "red_validation_status", "red_validated_by", "red_validated_at", "red_validation_notes", -] -_BLUE_ONLY_FIELDS = [ - "detection_result", "containment_result", "detection_time", "containment_time", - "blue_summary", "blue_validation_status", "blue_validated_by", "blue_validated_at", - "blue_validation_notes", "system_gaps", -] -_BLIND_STATES = {"draft", "red_executing", "red_review", "blue_evaluating", "blue_review"} - - -def _mask_for_team_blindness(test_out: TestOut, *, viewer_role: str) -> TestOut: - """Null out the other team's fields while the test is still blind. - - admin and viewer are never blinded. Once the test reaches in_review or - beyond, both sides see everything (existing cross-validation behavior). - """ - if viewer_role in ("admin", "viewer"): - return test_out - - test_state = test_out.state.value if hasattr(test_out.state, "value") else str(test_out.state) - if test_state not in _BLIND_STATES: - return test_out - - if viewer_role in ("blue_tech", "blue_lead"): - hide_fields = _RED_ONLY_FIELDS - elif viewer_role in ("red_tech", "red_lead"): - hide_fields = _BLUE_ONLY_FIELDS - else: - return test_out - - return test_out.model_copy(update={f: None for f in hide_fields}) - - # --------------------------------------------------------------------------- # GET /tests — list with filters # --------------------------------------------------------------------------- @@ -504,12 +464,11 @@ def get_test( Returns: TestOut: Full test detail including split red/blue evidence lists. - Fields belonging to the other team are nulled out while the - test is blind (see :func:`_mask_for_team_blindness`). + Both teams see each other's fields regardless of state — Red + and Blue are meant to see each other's work, not test blind. """ test = crud_get_test_detail(db, test_id) - test_out = TestOut.model_validate(test) - return _mask_for_team_blindness(test_out, viewer_role=current_user.role) + return TestOut.model_validate(test) # --------------------------------------------------------------------------- diff --git a/backend/tests/test_blind_visibility.py b/backend/tests/test_blind_visibility.py index ea3f32a..4b8cf01 100644 --- a/backend/tests/test_blind_visibility.py +++ b/backend/tests/test_blind_visibility.py @@ -1,9 +1,10 @@ -"""Tests for red/blue blind visibility on GET /tests/{id}. +"""Regression: Red and Blue must see each other's fields at every stage. -Neither team should see the other team's data while a test is in -draft/red_executing/red_review/blue_evaluating/blue_review. Both sides -see everything once the test reaches in_review (and beyond). admin and -viewer are never blinded. +Blind visibility (hiding the other team's fields until both reviews pass) +was a deliberate design in an earlier phase, but the org decided both teams +should see each other's work throughout — detection testing isn't meant to +be blind here. This confirms neither the frontend nor backend hides +anything based on role/state anymore. """ import uuid @@ -46,11 +47,11 @@ def technique(api, auth_headers): @pytest.fixture -def blind_test(client, db, api, red_lead_headers, red_lead_user, red_tech_headers, technique): +def in_progress_test(client, db, api, red_lead_headers, red_lead_user, red_tech_headers, technique): """A test in red_executing with red-side content already filled in.""" resp = api( "post", "/api/v1/tests", red_lead_headers, - json={"technique_id": technique, "name": "Blind visibility test"}, + json={"technique_id": technique, "name": "Visibility test"}, ) test_id = resp.json()["id"] @@ -62,32 +63,21 @@ def blind_test(client, db, api, red_lead_headers, red_lead_user, red_tech_header return test_id -def test_blue_viewer_cannot_see_red_fields_during_red_executing( - client, db, api, blind_test, blue_tech_headers +def test_blue_viewer_sees_red_fields_during_red_executing( + client, db, api, in_progress_test, blue_tech_headers ): - resp = api("get", f"/api/v1/tests/{blind_test}", blue_tech_headers) + resp = api("get", f"/api/v1/tests/{in_progress_test}", blue_tech_headers) assert resp.status_code == 200 body = resp.json() - for field in _RED_FIELDS: - assert body[field] is None, f"{field} should be hidden from blue viewer" - - -def test_red_viewer_cannot_see_blue_fields_during_red_executing( - client, db, api, blind_test, red_tech_headers -): - resp = api("get", f"/api/v1/tests/{blind_test}", red_tech_headers) - assert resp.status_code == 200 - body = resp.json() - for field in _BLUE_FIELDS: - assert body[field] is None, f"{field} should be hidden from red viewer" - # Red's own fields ARE visible to red assert body["procedure_text"] == "run mimikatz" + assert body["tool_used"] == "mimikatz" + assert body["red_summary"] == "dumped creds" -def test_admin_sees_everything_during_blind_states( - client, db, api, blind_test, auth_headers +def test_red_viewer_sees_own_fields_during_red_executing( + client, db, api, in_progress_test, red_tech_headers ): - resp = api("get", f"/api/v1/tests/{blind_test}", auth_headers) + resp = api("get", f"/api/v1/tests/{in_progress_test}", red_tech_headers) assert resp.status_code == 200 body = resp.json() assert body["procedure_text"] == "run mimikatz" @@ -95,9 +85,9 @@ def test_admin_sees_everything_during_blind_states( def test_both_sides_see_everything_once_in_review( client, db, api, auth_headers, red_tech_headers, red_lead_headers, - blue_tech_headers, blue_lead_headers, red_lead_user, blue_lead_user, blind_test, + blue_tech_headers, blue_lead_headers, red_lead_user, blue_lead_user, in_progress_test, ): - test_id = blind_test + test_id = in_progress_test _add_evidence(db, test_id, TeamSide.red) submit_red = api("post", f"/api/v1/tests/{test_id}/submit-red", red_tech_headers) assert submit_red.status_code == 200, submit_red.text diff --git a/frontend/src/components/test-detail/TeamTabs.tsx b/frontend/src/components/test-detail/TeamTabs.tsx index 513e015..7633a76 100644 --- a/frontend/src/components/test-detail/TeamTabs.tsx +++ b/frontend/src/components/test-detail/TeamTabs.tsx @@ -177,13 +177,6 @@ export default function TeamTabs({ (role === "blue_lead" || (role === "blue_tech" && !!test.blue_work_started_at)); - // Blind visibility: neither side sees the other's data until both reviews - // pass (matches the backend's field-masking on GET /tests/{id}). - const BLIND_STATES = ["draft", "red_executing", "red_review", "blue_evaluating", "blue_review"]; - const isBlind = BLIND_STATES.includes(test.state) && role !== "admin" && role !== "viewer"; - const hideBlueFromMe = isBlind && (role === "red_tech" || role === "red_lead"); - const hideRedFromMe = isBlind && (role === "blue_tech" || role === "blue_lead"); - // Containment fields only visible when attack was detected (draft or saved value) const isDetected = canEditBlue ? blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected" @@ -211,17 +204,6 @@ export default function TeamTabs({ // ── Red Team Tab ───────────────────────────────────────────────── const renderRedTab = () => { - if (hideRedFromMe) { - return ( -
- -

- Red Team work is hidden until both teams' reviews are complete — this keeps - detection testing blind. -

-
- ); - } return (
{/* Locked hint for red_tech in draft state */} @@ -420,17 +402,6 @@ export default function TeamTabs({ // ── Blue Team Tab ──────────────────────────────────────────────── const renderBlueTab = () => { - if (hideBlueFromMe) { - return ( -
- -

- Blue Team work is hidden until both teams' reviews are complete — this keeps - detection testing blind. -

-
- ); - } return (
{/* Locked hint for blue_tech before Start Evaluation */}