From 40876cfa478ec2601f522ac13d85bf41d72dfce3 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:44:33 +0000 Subject: [PATCH 1/3] Harden Pydantic schemas against control characters --- backend/app/schemas.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index d7c6de77..0011c399 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -77,6 +77,7 @@ class ApplySqlIn(BaseModel): "Conservative PostgreSQL DDL subset with unquoted snake_case " "identifiers. Arbitrary SQL is rejected." ), + pattern=r"^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+$", ) # Default to a rolled-back pre-flight; the caller must opt in to persist. dry_run: bool = True @@ -190,7 +191,7 @@ class IndexRedundancyOut(BaseModel): class DiagramViewCreateIn(BaseModel): """Request body for saving an ERD canvas view.""" - name: str = Field(min_length=1, max_length=200) + name: str = Field(min_length=1, max_length=200, pattern=r"^[^\x00-\x1F\x7F]+$") # Opaque client layout (node positions, hidden tables, viewport). The API # bounds the serialized size in the endpoint to prevent abuse. layout_json: dict @@ -214,9 +215,9 @@ class DiagramViewDetailOut(DiagramViewOut): class TableAnnotationUpsertIn(BaseModel): """Request body for creating/updating a table annotation.""" - schema_name: str = Field(min_length=1, max_length=255) - relation_name: str = Field(min_length=1, max_length=255) - body: str = Field(min_length=1, max_length=10_000) + schema_name: str = Field(min_length=1, max_length=255, pattern=r"^[^\x00-\x1F\x7F]+$") + relation_name: str = Field(min_length=1, max_length=255, pattern=r"^[^\x00-\x1F\x7F]+$") + body: str = Field(min_length=1, max_length=10_000, pattern=r"^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+$") class TableAnnotationOut(BaseModel): @@ -285,7 +286,7 @@ class NamingLintOut(BaseModel): class DbmlConvertIn(BaseModel): """Request body for converting DBML text into a snapshot.""" - dbml: str = Field(min_length=1, max_length=524_288) + dbml: str = Field(min_length=1, max_length=524_288, pattern=r"^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+$") include_ddl: bool = True dialect: Literal["postgresql", "snowflake"] = "postgresql" @@ -302,7 +303,7 @@ class DbmlConvertOut(BaseModel): class ApiKeyCreateIn(BaseModel): """Request body for creating an API key.""" - key_name: str = Field(min_length=1, max_length=128) + key_name: str = Field(min_length=1, max_length=128, pattern=r"^[^\x00-\x1F\x7F]+$") class ApiKeyOut(BaseModel): From 3a087752cc7aef5fbca9a74854ba94c7b53294bd Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:52:33 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=EB=B3=B4=EC=95=88:=20Pydantic=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88=EC=9D=98=20=EC=A0=9C=EC=96=B4=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=20=EC=A3=BC=EC=9E=85=20=EC=B7=A8=EC=95=BD=EC=A0=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(=EC=A0=95=EA=B7=9C=EC=8B=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From bf84e88eacf92dd9d571ba52b6d53f6ed74f7540 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 8 Aug 2026 02:17:35 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=EB=B3=B4=EC=95=88:=20ConnectionCreateIn.ds?= =?UTF-8?q?n=20=ED=95=84=EB=93=9C=EC=97=90=20=EC=A0=9C=EC=96=B4=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=20=EC=A3=BC=EC=9E=85=20=EB=B0=A9=EC=A7=80=20?= =?UTF-8?q?=EC=A0=95=EA=B7=9C=EC=8B=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/schemas.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 0011c399..508c3c28 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -57,6 +57,7 @@ class ConnectionCreateIn(BaseModel): min_length=1, max_length=4096, description=("PostgreSQL or Snowflake connection string. Not logged."), + pattern=r"^[^\x00-\x1F\x7F]+$", )