Skip to content

fix: table_name validation accepts a trailing newline#317

Open
Andrew Chen (chuenchen309) wants to merge 1 commit into
langchain-ai:mainfrom
chuenchen309:fix/chat-history-table-name-fullmatch
Open

fix: table_name validation accepts a trailing newline#317
Andrew Chen (chuenchen309) wants to merge 1 commit into
langchain-ai:mainfrom
chuenchen309:fix/chat-history-table-name-fullmatch

Conversation

@chuenchen309

Copy link
Copy Markdown

Problem

PostgresChatMessageHistory.__init__ validates table_name with re.match(r"^\w+$", table_name). Without re.MULTILINE, $ matches either at the very end of the string or immediately before a single trailing "\n". So a table name like "chat_history\n" passes this check, even though the stated intent (per the raised error message) is "Table name must contain only alphanumeric characters and underscores."

Fix

Use re.fullmatch(r"\w+", table_name) instead, which requires the entire string to match with no trailing-newline exception.

table_name is later interpolated into DDL via psycopg's sql.Identifier (which still applies its own quoting), so this is primarily about the validation actually doing what its error message claims, not a raw SQL-injection vector — but a permissive validation regex like this is still worth tightening (CWE-625).

Testing

  • Added test_table_name_rejects_trailing_newline: __init__ validates table_name before ever touching the connection, so a dummy object() for sync_connection is enough to reach the check without needing a real DB.
  • Confirmed red→green: reverting only chat_message_histories.py makes the new test fail with DID NOT RAISE ValueError; reapplying the fix passes.
  • ruff check / ruff format --diff and mypy clean on both changed files.
  • The two pre-existing DB-backed tests in this file (test_sync_chat_history, test_async_chat_history) require a live Postgres connection unavailable in my sandbox; my change only touches validation that runs before any connection is used, so it doesn't affect their logic.

AI-Generated disclosure

Found via an AI-assisted code review pass (Claude Code) over langchain_postgres/. I personally reproduced the permissive-regex behavior, verified the fix, and ran the relevant test file plus lint/mypy before submitting.

… vs re.fullmatch)

`re.match(r"^\w+$", table_name)` accepts a table_name ending in a
single trailing newline, e.g. "chat_history\n": without re.MULTILINE,
`$` matches either at the very end of the string or immediately before
a trailing "\n". The intent (per the error message: "must contain only
alphanumeric characters and underscores") is a full-string match.

Fix: use re.fullmatch(r"\w+", table_name), which requires the entire
string to match with no such trailing-newline exception. table_name is
later interpolated into DDL via psycopg's sql.Identifier, so this
mostly affects error surfacing/consistency rather than a raw injection
(Identifier quoting still applies), but it's still not the validation
the code claims to perform (CWE-625: permissive regex).

Testing: added test_table_name_rejects_trailing_newline -- __init__
validates table_name before ever touching the connection, so a dummy
object() is enough to reach the check without a real DB. Confirmed
red->green (reverting only chat_message_histories.py makes the new
test fail with "DID NOT RAISE ValueError"; reapplying passes). ruff
check/format and mypy clean on the changed files. The two existing
DB-backed tests in this file require a live Postgres connection
unavailable in this environment; my change only affects validation
that runs before any connection is used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant