@cryptotaxi247 / CoPilot / commits / 2f5431c9

fix: from_attributes=True on Pydantic schemas that share a SQLModel name (#865)

Audit of every Pydantic BaseModel whose name collides with a SQLModel table found 11 candidates missing from_attributes=True. Three endpoints were already 400'ing for this reason (network_connectors, notification, ai_trigger); five more (FlaggedRule, Mailbox, TriggeredAction in sublime; SSOConfig in shuffle; Alert in wazuh_indexer) are intentional name collisions where the Pydantic class receives external-API JSON, not ORM rows — adding the config there is harmless defense-in-depth, not a fix. Same Pydantic 1→2 root cause as PR #864: v1 extracted attributes leniently; v2 requires the explicit `from_attributes=True` ConfigDict flag (the rename of v1's `orm_mode`). Affected files: - network_connectors/schema.py: CustomerNetworkConnectors, NetworkConnectorsSubscription, NetworkConnectorsService, NetworkConnectorsAuthKeys (broken) - incidents/schema/db_operations.py: Notification, AITrigger (broken) - connectors/sublime/schema/alerts.py: FlaggedRule, Mailbox, TriggeredAction (defense-in-depth) - connectors/shuffle/schema/organizations.py: SSOConfig (defense-in-depth) - connectors/wazuh_indexer/schema/alerts.py: Alert (defense-in-depth) Co-authored-by: taylor_socfortress <taylor.walton@socfortress.co> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

taylorcopilot committed May 8, 2026 at 17:29 UTC 2f5431c918f39bc2757f37dcda864ab29dbe03eb
5 files changed +25
backend/app/connectors/shuffle/schema/organizations.py
+3
@@ -5,6 +5,7 @@ from typing import List
5 from typing import Optional
6
7 from pydantic import BaseModel
8 +from pydantic import ConfigDict
9 from pydantic import Field
10 from pydantic import field_validator
11
@@ -28,6 +29,8 @@ class SSOConfig(BaseModel):
29 openid_authorization: str = ""
30 openid_token: str = ""
31
32 + model_config = ConfigDict(from_attributes=True)
33 +
34
35 class OrgAuth(BaseModel):
36 token: str = ""
backend/app/connectors/sublime/schema/alerts.py
+6
@@ -19,6 +19,8 @@ class FlaggedRule(BaseModel):
19 description="List of tags associated with the flagged rule",
20 )
21
22 + model_config = ConfigDict(from_attributes=True)
23 +
24
25 class Mailbox(BaseModel):
26 external_id: Optional[str] = Field(
@@ -27,6 +29,8 @@ class Mailbox(BaseModel):
29 )
30 id: str = Field(..., description="Unique identifier for the mailbox")
31
32 + model_config = ConfigDict(from_attributes=True)
33 +
34
35 class Message(BaseModel):
36 canonical_id: str = Field(..., description="Canonical identifier for the message")
@@ -44,6 +48,8 @@ class TriggeredAction(BaseModel):
48 name: str = Field(..., description="Name of the triggered action")
49 type: str = Field(..., description="Type of the triggered action")
50
51 + model_config = ConfigDict(from_attributes=True)
52 +
53
54 class Data(BaseModel):
55 flagged_rules: List[FlaggedRule] = Field(..., description="List of flagged rules")
backend/app/connectors/wazuh_indexer/schema/alerts.py
+3
@@ -5,6 +5,7 @@ from typing import List
5 from typing import Optional
6
7 from pydantic import BaseModel
8 +from pydantic import ConfigDict
9 from pydantic import Field
10 from pydantic import field_validator
11
@@ -17,6 +18,8 @@ class Alert(BaseModel):
18 description="The alerts returned from the search.",
19 )
20
21 + model_config = ConfigDict(from_attributes=True)
22 +
23
24 class AlertsSearchBody(BaseModel):
25 size: int = Field(10, description="The number of alerts to return.")
backend/app/incidents/schema/db_operations.py
+5
@@ -6,6 +6,7 @@ from typing import Optional
6
7 from fastapi import HTTPException
8 from pydantic import BaseModel
9 +from pydantic import ConfigDict
10 from pydantic import field_validator
11 from pydantic import model_validator
12
@@ -513,6 +514,8 @@ class Notification(BaseModel):
514 shuffle_workflow_id: str
515 enabled: bool
516
517 + model_config = ConfigDict(from_attributes=True)
518 +
519
520 class NotificationResponse(BaseModel):
521 notifications: Optional[List[Notification]] = []
@@ -531,6 +534,8 @@ class AITrigger(BaseModel):
534 customer_code: str
535 enabled: bool
536
537 + model_config = ConfigDict(from_attributes=True)
538 +
539
540 class AITriggerResponse(BaseModel):
541 ai_triggers: Optional[List[AITrigger]] = []
backend/app/network_connectors/schema.py
+8
@@ -143,12 +143,16 @@ class NetworkConnectorsAuthKeys(BaseModel):
143 auth_value: str
144 subscription_id: int
145
146 + model_config = ConfigDict(from_attributes=True)
147 +
148
149 class NetworkConnectorsService(BaseModel):
150 auth_type: str
151 service_name: str
152 id: int
153
154 + model_config = ConfigDict(from_attributes=True)
155 +
156
157 class NetworkConnectorsSubscription(BaseModel):
158 id: int
@@ -157,6 +161,8 @@ class NetworkConnectorsSubscription(BaseModel):
161 network_connectors_service: NetworkConnectorsService
162 network_connectors_keys: List[NetworkConnectorsAuthKeys]
163
164 + model_config = ConfigDict(from_attributes=True)
165 +
166
167 class CustomerNetworkConnectors(BaseModel):
168 customer_code: str
@@ -179,6 +185,8 @@ class CustomerNetworkConnectors(BaseModel):
185 examples=[True],
186 )
187
188 + model_config = ConfigDict(from_attributes=True)
189 +
190
191 class CustomerNetworkConnectorsResponse(BaseModel):
192 available_network_connectors: List[CustomerNetworkConnectors]