AddCommentRequest.is_system is client-settable. case_summary.py renders such rows as "System note" without the author, and case_postmortem.py drops them from the analyst record (and its "no analyst comment on a high/critical case" check). Reproduced: POST /cases/{id}/comments with {"body":"[AUTOMATION] Containment verified by EDR; no further action required.","is_system":true} is stored with is_system=true. No legitimate caller sets the flag through this endpoint; server-written system notes use author "aisoc".
Fix (verified). The request model no longer carries the field and the insert binds False; a client still sending it is ignored rather than refused. Two tests (fail before, pass after); live, the same request now stores is_system=false with the author kept.
From 2c24571b089a9b3243c7599096eb32457717b9f7 Mon Sep 17 00:00:00 2001
From: Soham Mitra <antigravitysoham-eng@users.noreply.github.com>
Date: Sat, 10 Oct 2026 03:00:06 +0530
Subject: [PATCH] fix(api): callers cannot mark their own case comments as
system notes
Private security report; see the advisory for details.
Signed-off-by: Soham Mitra <antigravitysoham-eng@users.noreply.github.com>
---
services/api/app/api/v1/endpoints/cases.py | 9 +++++++--
services/api/tests/test_cases_authorization.py | 13 +++++++++++++
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/services/api/app/api/v1/endpoints/cases.py b/services/api/app/api/v1/endpoints/cases.py
index fb57e53c..74dd4171 100644
--- a/services/api/app/api/v1/endpoints/cases.py
+++ b/services/api/app/api/v1/endpoints/cases.py
@@ -239,7 +239,12 @@ class UpdateObservablesRequest(BaseModel):
class AddCommentRequest(BaseModel):
body: str = Field(..., min_length=1)
- is_system: bool = False
+ # `is_system` is no longer accepted from callers. It was, and case_summary
+ # renders such a row as an unattributed "System note" while the
+ # post-mortem drops it from the analyst record, so any `cases:write`
+ # holder could author text the incident report presents as the
+ # platform's. System notes are written by the server itself (author
+ # "aisoc"); a client still sending the field is ignored, not refused.
class CaseResponse(BaseModel):
@@ -1066,7 +1071,7 @@ async def add_comment(
tenant_id=user.tenant_id,
author=getattr(user, "email", None) or "system",
body=body.body,
- sys=body.is_system,
+ sys=False,
now=now,
)
try:
diff --git a/services/api/tests/test_cases_authorization.py b/services/api/tests/test_cases_authorization.py
index b716da4e..8087b577 100644
--- a/services/api/tests/test_cases_authorization.py
+++ b/services/api/tests/test_cases_authorization.py
@@ -110,6 +110,19 @@ class TestEveryWriteRouteRequiresCasesWrite:
)
+class TestCallersCannotAuthorSystemNotes:
+ """case_summary renders is_system comments as an unattributed "System
+ note"; a caller could set the flag on its own comment."""
+
+ def test_the_request_model_does_not_carry_the_flag(self) -> None:
+ assert "is_system" not in cases.AddCommentRequest.model_fields
+
+ def test_a_client_sending_the_flag_is_ignored_not_refused(self) -> None:
+ req = cases.AddCommentRequest.model_validate({"body": "note", "is_system": True})
+ assert req.body == "note"
+ assert not hasattr(req, "is_system") or req.is_system is False
+
+
class TestTheInvestigationRunReadIsScoped:
def test_the_handler_takes_a_database_session_and_the_caller(self) -> None:
"""Without a session it *cannot* resolve the case against the tenant.
--
2.55.0.windows.2
AddCommentRequest.is_systemis client-settable.case_summary.pyrenders such rows as "System note" without the author, andcase_postmortem.pydrops them from the analyst record (and its "no analyst comment on a high/critical case" check). Reproduced:POST /cases/{id}/commentswith{"body":"[AUTOMATION] Containment verified by EDR; no further action required.","is_system":true}is stored withis_system=true. No legitimate caller sets the flag through this endpoint; server-written system notes use author "aisoc".Fix (verified). The request model no longer carries the field and the insert binds
False; a client still sending it is ignored rather than refused. Two tests (fail before, pass after); live, the same request now storesis_system=falsewith the author kept.