Files
Aegis/backend/tests/test_blind_visibility.py
T
kitos a3f86c7b31
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
fix(permissions): admin can no longer operate tests — start/submit/edit/create, view only
Admin still bypassed require_any_role (non-strict) on the pure
operator actions: start-execution, submit-red, start-blue-work,
submit-blue, pause/resume-timer, and the red/blue field-edit + general
test create/update/remediation/import-rt/template endpoints all used
the loose dependency even where admin wasn't in the role tuple.
Switched every one of these to require_any_role_strict, matching the
review/validate/dispute/hold/assign endpoints already fixed earlier.

Frontend: removed every remaining role === "admin" disjunct gating
Start Execution, Submit to Blue Team, blue evaluating actions, timer
control, red/blue field editing, test creation, and template creation.
Team-blindness visibility (isBlind) deliberately keeps the admin
bypass — admin still sees both sides unmasked for oversight, since
that's visibility, not operating.

Updated ~20 tests whose fixtures used the admin account as a
convenience shortcut to create/drive tests through the workflow —
switched them to a red_lead account, fixing an incidental
fixture-resolution-order cookie bug along the way (client's cookie
jar prefers the last-logged-in role's cookie over any Bearer header
explicitly passed to a later request).
2026-07-13 09:27:43 +02:00

130 lines
4.6 KiB
Python

"""Tests for red/blue blind visibility on GET /tests/{id}.
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.
"""
import uuid
import pytest
from app.models.evidence import Evidence
from app.models.enums import TeamSide
_RED_FIELDS = [
"procedure_text", "tool_used", "attack_success",
"execution_start_time", "execution_end_time", "red_summary",
]
_BLUE_FIELDS = [
"detection_result", "containment_result", "detection_time",
"containment_time", "blue_summary", "system_gaps",
]
def _add_evidence(db, test_id, team: TeamSide):
ev = Evidence(
test_id=uuid.UUID(test_id),
file_name="proof.txt",
file_path="s3://bucket/proof.txt",
sha256_hash="a" * 64,
team=team,
)
db.add(ev)
db.commit()
@pytest.fixture
def technique(api, auth_headers):
resp = api(
"post", "/api/v1/techniques", auth_headers,
json={"mitre_id": "T1059.100", "name": "Command Line"},
)
assert resp.status_code == 201, resp.text
return resp.json()["id"]
@pytest.fixture
def blind_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"},
)
test_id = resp.json()["id"]
api("post", f"/api/v1/tests/{test_id}/start-execution", red_tech_headers)
api(
"patch", f"/api/v1/tests/{test_id}/red", red_tech_headers,
json={"procedure_text": "run mimikatz", "tool_used": "mimikatz", "red_summary": "dumped creds"},
)
return test_id
def test_blue_viewer_cannot_see_red_fields_during_red_executing(
client, db, api, blind_test, blue_tech_headers
):
resp = api("get", f"/api/v1/tests/{blind_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"
def test_admin_sees_everything_during_blind_states(
client, db, api, blind_test, auth_headers
):
resp = api("get", f"/api/v1/tests/{blind_test}", auth_headers)
assert resp.status_code == 200
body = resp.json()
assert body["procedure_text"] == "run mimikatz"
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,
):
test_id = blind_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
approve_red = api("post", f"/api/v1/tests/{test_id}/review-red", red_lead_headers, json={"decision": "approve"})
assert approve_red.status_code == 200, approve_red.text
api("post", f"/api/v1/tests/{test_id}/start-blue-work", blue_tech_headers)
api(
"patch", f"/api/v1/tests/{test_id}/blue", blue_tech_headers,
json={"detection_result": "detected", "blue_summary": "caught it"},
)
_add_evidence(db, test_id, TeamSide.blue)
submit_blue = api("post", f"/api/v1/tests/{test_id}/submit-blue", blue_tech_headers)
assert submit_blue.status_code == 200, submit_blue.text
approve_blue = api("post", f"/api/v1/tests/{test_id}/review-blue", blue_lead_headers, json={"decision": "approve"})
assert approve_blue.status_code == 200, approve_blue.text
assert approve_blue.json()["state"] == "in_review"
resp_as_red = api("get", f"/api/v1/tests/{test_id}", red_tech_headers)
body_red = resp_as_red.json()
assert body_red["detection_result"] == "detected"
assert body_red["blue_summary"] == "caught it"
resp_as_blue = api("get", f"/api/v1/tests/{test_id}", blue_tech_headers)
body_blue = resp_as_blue.json()
assert body_blue["procedure_text"] == "run mimikatz"
assert body_blue["red_summary"] == "dumped creds"