feat(tests): make Expected Detection editable when creating a test from template
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
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
Leads can now edit Expected Detection in the create-from-template form, same as Suggested Attack Procedure — the edit seeds the new test's detect_procedure via a detect_procedure_override, without touching the template itself. The template only updates later through the existing procedure-suggestion approval flow, once a round is actually submitted — same mechanism as the red side, no new bypass.
This commit is contained in:
@@ -402,6 +402,7 @@ def create_test_from_template(
|
|||||||
platform_override=payload.platform,
|
platform_override=payload.platform,
|
||||||
procedure_text_override=payload.procedure_text,
|
procedure_text_override=payload.procedure_text,
|
||||||
tool_used_override=payload.tool_used,
|
tool_used_override=payload.tool_used,
|
||||||
|
detect_procedure_override=payload.detect_procedure,
|
||||||
)
|
)
|
||||||
# Call log_action()
|
# Call log_action()
|
||||||
log_action(
|
log_action(
|
||||||
|
|||||||
@@ -130,3 +130,4 @@ class TestTemplateInstantiate(BaseModel):
|
|||||||
platform: str | None = None
|
platform: str | None = None
|
||||||
procedure_text: str | None = None
|
procedure_text: str | None = None
|
||||||
tool_used: str | None = None
|
tool_used: str | None = None
|
||||||
|
detect_procedure: str | None = None
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ def create_test_from_template(
|
|||||||
platform_override: str | None = None,
|
platform_override: str | None = None,
|
||||||
procedure_text_override: str | None = None,
|
procedure_text_override: str | None = None,
|
||||||
tool_used_override: str | None = None,
|
tool_used_override: str | None = None,
|
||||||
|
detect_procedure_override: str | None = None,
|
||||||
) -> Test:
|
) -> Test:
|
||||||
"""Instantiate a Test from a TestTemplate.
|
"""Instantiate a Test from a TestTemplate.
|
||||||
|
|
||||||
@@ -364,7 +365,7 @@ def create_test_from_template(
|
|||||||
platform=platform_override if platform_override is not None else template.platform,
|
platform=platform_override if platform_override is not None else template.platform,
|
||||||
procedure_text=procedure_text_override if procedure_text_override is not None else template.attack_procedure,
|
procedure_text=procedure_text_override if procedure_text_override is not None else template.attack_procedure,
|
||||||
tool_used=tool_used_override if tool_used_override is not None else template.tool_suggested,
|
tool_used=tool_used_override if tool_used_override is not None else template.tool_suggested,
|
||||||
detect_procedure=template.expected_detection,
|
detect_procedure=detect_procedure_override if detect_procedure_override is not None else template.expected_detection,
|
||||||
remediation_steps=template.suggested_remediation,
|
remediation_steps=template.suggested_remediation,
|
||||||
# Keyword argument: created_by
|
# Keyword argument: created_by
|
||||||
created_by=creator_id,
|
created_by=creator_id,
|
||||||
|
|||||||
@@ -272,3 +272,37 @@ def test_duplicate_submission_does_not_create_a_second_pending_suggestion(
|
|||||||
|
|
||||||
listed = api("get", "/api/v1/procedure-suggestions", red_lead_headers).json()
|
listed = api("get", "/api/v1/procedure-suggestions", red_lead_headers).json()
|
||||||
assert len(listed) == 1
|
assert len(listed) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_lead_can_override_detect_procedure_when_creating_from_template(
|
||||||
|
client, db, api, red_lead_headers, technique, template,
|
||||||
|
):
|
||||||
|
"""A lead editing Expected Detection in the create-from-template form
|
||||||
|
seeds the new test's detect_procedure with their edit — same mechanism
|
||||||
|
as procedure_text_override on the red side — without touching the
|
||||||
|
template itself. The template only updates later, through the normal
|
||||||
|
procedure-suggestion approval flow once a round is actually submitted."""
|
||||||
|
resp = api(
|
||||||
|
"post", "/api/v1/tests/from-template", red_lead_headers,
|
||||||
|
json={
|
||||||
|
"template_id": template["id"],
|
||||||
|
"technique_id": technique,
|
||||||
|
"detect_procedure": "Check Sysmon Event ID 1 for mimikatz.exe process creation.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.status_code == 201, resp.text
|
||||||
|
assert resp.json()["detect_procedure"] == "Check Sysmon Event ID 1 for mimikatz.exe process creation."
|
||||||
|
|
||||||
|
reloaded_template = api("get", f"/api/v1/test-templates/{template['id']}", red_lead_headers)
|
||||||
|
assert reloaded_template.json()["expected_detection"] == "Check process creation logs."
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_procedure_defaults_to_template_expected_detection(
|
||||||
|
client, db, api, red_lead_headers, technique, template,
|
||||||
|
):
|
||||||
|
resp = api(
|
||||||
|
"post", "/api/v1/tests/from-template", red_lead_headers,
|
||||||
|
json={"template_id": template["id"], "technique_id": technique},
|
||||||
|
)
|
||||||
|
assert resp.status_code == 201, resp.text
|
||||||
|
assert resp.json()["detect_procedure"] == "Check process creation logs."
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ export async function createTestFromTemplate(
|
|||||||
platform?: string;
|
platform?: string;
|
||||||
procedure_text?: string;
|
procedure_text?: string;
|
||||||
tool_used?: string;
|
tool_used?: string;
|
||||||
|
detect_procedure?: string;
|
||||||
},
|
},
|
||||||
): Promise<Test> {
|
): Promise<Test> {
|
||||||
const { data } = await client.post<Test>("/tests/from-template", {
|
const { data } = await client.post<Test>("/tests/from-template", {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export default function TestFromTemplateForm({
|
|||||||
platform: platform.trim() || undefined,
|
platform: platform.trim() || undefined,
|
||||||
procedure_text: procedureText.trim() || undefined,
|
procedure_text: procedureText.trim() || undefined,
|
||||||
tool_used: toolUsed.trim() || undefined,
|
tool_used: toolUsed.trim() || undefined,
|
||||||
|
detect_procedure: expectedDetection.trim() || undefined,
|
||||||
}),
|
}),
|
||||||
onSuccess: (test) => {
|
onSuccess: (test) => {
|
||||||
navigate(`/tests/${test.id}`);
|
navigate(`/tests/${test.id}`);
|
||||||
@@ -208,17 +209,18 @@ export default function TestFromTemplateForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Expected Detection (read-only reference for Blue Team) */}
|
{/* Expected Detection */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||||
Expected Detection
|
Expected Detection
|
||||||
<span className="ml-2 text-xs text-gray-500">(read-only reference for Blue Team)</span>
|
|
||||||
</label>
|
</label>
|
||||||
<div className="rounded-lg border border-gray-700 bg-gray-800/50 p-3">
|
<textarea
|
||||||
<p className="whitespace-pre-wrap font-mono text-sm text-gray-400">
|
value={expectedDetection}
|
||||||
{expectedDetection || "No detection guidance provided in template."}
|
onChange={(e) => setExpectedDetection(e.target.value)}
|
||||||
</p>
|
rows={5}
|
||||||
</div>
|
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 font-mono text-sm text-gray-200 placeholder-gray-500 focus:border-cyan-500 focus:outline-none focus:ring-1 focus:ring-cyan-500"
|
||||||
|
placeholder="What the blue team should detect, and how..."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error */}
|
{/* Error */}
|
||||||
|
|||||||
Reference in New Issue
Block a user