From 28598baeb874714d06f99798b397c4005e481c5f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:07:09 +0000 Subject: [PATCH] Fix missing control character validation in Pydantic models Added pattern validation to DiagramViewCreateIn.name, ApiKeyCreateIn.key_name, TableAnnotationUpsertIn.schema_name, and TableAnnotationUpsertIn.relation_name to reject ASCII control characters. --- backend/app/schemas.py | 24 +++++++++++++--- backend/tests/test_schema_validation.py | 37 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index d7c6de77..5fd3a792 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -190,7 +190,11 @@ 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,8 +218,16 @@ 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) + 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) @@ -302,7 +314,11 @@ 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): diff --git a/backend/tests/test_schema_validation.py b/backend/tests/test_schema_validation.py index 317292b8..ad8e6e9f 100644 --- a/backend/tests/test_schema_validation.py +++ b/backend/tests/test_schema_validation.py @@ -3,7 +3,14 @@ import pytest from pydantic import ValidationError -from app.schemas import ConnectionCreateIn, ProjectCreateIn, ProjectMemberAddIn +from app.schemas import ( + ApiKeyCreateIn, + ConnectionCreateIn, + DiagramViewCreateIn, + ProjectCreateIn, + ProjectMemberAddIn, + TableAnnotationUpsertIn, +) def test_project_name_length_is_bounded() -> None: @@ -37,3 +44,31 @@ def test_conn_name_rejects_control_characters() -> None: ConnectionCreateIn(conn_name="my\x00conn", dsn="postgresql://localhost/db") with pytest.raises(ValidationError): ConnectionCreateIn(conn_name="my\nconn", dsn="postgresql://localhost/db") + + +def test_diagram_view_name_rejects_control_characters() -> None: + with pytest.raises(ValidationError): + DiagramViewCreateIn(name="my\x00view", layout_json={}) + with pytest.raises(ValidationError): + DiagramViewCreateIn(name="my\nview", layout_json={}) + + +def test_api_key_name_rejects_control_characters() -> None: + with pytest.raises(ValidationError): + ApiKeyCreateIn(key_name="my\x00key") + with pytest.raises(ValidationError): + ApiKeyCreateIn(key_name="my\nkey") + + +def test_table_annotation_schema_name_rejects_control_characters() -> None: + with pytest.raises(ValidationError): + TableAnnotationUpsertIn(schema_name="public\x00", relation_name="users", body="test") + with pytest.raises(ValidationError): + TableAnnotationUpsertIn(schema_name="public\n", relation_name="users", body="test") + + +def test_table_annotation_relation_name_rejects_control_characters() -> None: + with pytest.raises(ValidationError): + TableAnnotationUpsertIn(schema_name="public", relation_name="users\x00", body="test") + with pytest.raises(ValidationError): + TableAnnotationUpsertIn(schema_name="public", relation_name="users\n", body="test")