Skip to content

Any cases:write holder can author case comments rendered as unattributed "System note"

Low
beenuar published GHSA-9mc5-hqgh-j6wf Oct 21, 2026

Software

beenuar/AiSOC

Affected versions

<= 19.0.0

Patched versions

a57ab3afdc3ef638273aa22f073467d853b48611

Description

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

Severity

Low

CVE ID

No known CVE

Weaknesses

External Control of Assumed-Immutable Web Parameter

The web application does not sufficiently verify inputs that are assumed to be immutable but are actually externally controllable, such as hidden form fields. Learn more on MITRE.