From d75e3310fa0384266f1e92c83374ab3b4841b474 Mon Sep 17 00:00:00 2001 From: Caleb Mabry Date: Wed, 7 Jan 2026 13:41:27 -0500 Subject: [PATCH 1/6] If password was encoded, it would double encode. Switched to only encode once --- src/postgres_mcp/utils/url.py | 6 ++++-- tests/unit/utils/test_url.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tests/unit/utils/test_url.py diff --git a/src/postgres_mcp/utils/url.py b/src/postgres_mcp/utils/url.py index 748fc73..c74e959 100644 --- a/src/postgres_mcp/utils/url.py +++ b/src/postgres_mcp/utils/url.py @@ -1,4 +1,4 @@ -from urllib.parse import quote +from urllib.parse import quote, unquote def fix_connection_url(url: str) -> str: @@ -10,7 +10,9 @@ def fix_connection_url(url: str) -> str: user_pass = url[scheme_end:at_pos] if ":" in user_pass: username, password = user_pass.split(":", 1) - encoded_password = quote(password, safe="") + # If password is already encoded, decode it. + plain_password = unquote(password) + encoded_password = quote(plain_password, safe="") return url[:scheme_end] + username + ":" + encoded_password + url[at_pos:] except Exception as e: print(e) diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py new file mode 100644 index 0000000..8c39476 --- /dev/null +++ b/tests/unit/utils/test_url.py @@ -0,0 +1,15 @@ +import pytest +from postgres_mcp.utils.url import fix_connection_url # Replace with actual import path + +@pytest.mark.parametrize("input_url, expected_output", [ + ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), + ("postgresql://user:pass%3Fword@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db") +]) +def test_fix_connection_url_encoding(input_url: str, expected_output: str) -> None: + """Verifies that passwords are encoded once and only once.""" + assert fix_connection_url(input_url) == expected_output + +def test_fix_connection_url_no_mutation(): + """Ensure a standard safe URL is not changed.""" + url = "postgresql://readonly:securepassword123@db.example.com:5432/postgres" + assert fix_connection_url(url) == url \ No newline at end of file From 97cea5cb859da0a67efc59b9c74c385dc78f1f1f Mon Sep 17 00:00:00 2001 From: Caleb Mabry Date: Wed, 7 Jan 2026 13:44:31 -0500 Subject: [PATCH 2/6] Replaced import path --- tests/unit/utils/test_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py index 8c39476..d6d7df4 100644 --- a/tests/unit/utils/test_url.py +++ b/tests/unit/utils/test_url.py @@ -1,5 +1,5 @@ import pytest -from postgres_mcp.utils.url import fix_connection_url # Replace with actual import path +from postgres_mcp.utils.url import fix_connection_url @pytest.mark.parametrize("input_url, expected_output", [ ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), From ad8cff702007bd07d2ecb2ca86ca2f39da32102f Mon Sep 17 00:00:00 2001 From: Caleb Mabry Date: Wed, 7 Jan 2026 13:46:47 -0500 Subject: [PATCH 3/6] Linting fix --- tests/unit/utils/test_url.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py index d6d7df4..a8db442 100644 --- a/tests/unit/utils/test_url.py +++ b/tests/unit/utils/test_url.py @@ -1,15 +1,20 @@ import pytest from postgres_mcp.utils.url import fix_connection_url -@pytest.mark.parametrize("input_url, expected_output", [ - ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), - ("postgresql://user:pass%3Fword@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db") -]) + +@pytest.mark.parametrize( + "input_url, expected_output", + [ + ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), + ("postgresql://user:pass%3Fword@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), + ], +) def test_fix_connection_url_encoding(input_url: str, expected_output: str) -> None: """Verifies that passwords are encoded once and only once.""" assert fix_connection_url(input_url) == expected_output + def test_fix_connection_url_no_mutation(): """Ensure a standard safe URL is not changed.""" url = "postgresql://readonly:securepassword123@db.example.com:5432/postgres" - assert fix_connection_url(url) == url \ No newline at end of file + assert fix_connection_url(url) == url From 74aeb2151aa4ba9d5edb739e99f705b16c67df8e Mon Sep 17 00:00:00 2001 From: Caleb Mabry Date: Wed, 7 Jan 2026 14:09:47 -0500 Subject: [PATCH 4/6] Add additional test cases --- tests/unit/utils/test_url.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py index a8db442..9aa73d4 100644 --- a/tests/unit/utils/test_url.py +++ b/tests/unit/utils/test_url.py @@ -7,6 +7,8 @@ [ ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), ("postgresql://user:pass%3Fword@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), + ("postgresql://user:pass%word@localhost:5432/db", "postgresql://user:pass%25word@localhost:5432/db"), + ("postgresql://user:?pass%25wo%rd@localhost:5432/db", "postgresql://user:%3Fpass%25wo%25rd@localhost:5432/db"), ], ) def test_fix_connection_url_encoding(input_url: str, expected_output: str) -> None: From e9976a5c328e67cb68174b8e3b5b75f1a690a9fe Mon Sep 17 00:00:00 2001 From: Caleb Mabry Date: Wed, 7 Jan 2026 15:01:59 -0500 Subject: [PATCH 5/6] Resolve formatting --- tests/unit/utils/test_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py index 9aa73d4..4f898af 100644 --- a/tests/unit/utils/test_url.py +++ b/tests/unit/utils/test_url.py @@ -8,7 +8,7 @@ ("postgresql://user:pass?word@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), ("postgresql://user:pass%3Fword@localhost:5432/db", "postgresql://user:pass%3Fword@localhost:5432/db"), ("postgresql://user:pass%word@localhost:5432/db", "postgresql://user:pass%25word@localhost:5432/db"), - ("postgresql://user:?pass%25wo%rd@localhost:5432/db", "postgresql://user:%3Fpass%25wo%25rd@localhost:5432/db"), + ("postgresql://user:?pass%25wo%%rd@localhost:5432/db", "postgresql://user:%3Fpass%25wo%25%25rd@localhost:5432/db"), ], ) def test_fix_connection_url_encoding(input_url: str, expected_output: str) -> None: From 08c84aeb0e433b35aaf9489b3b6ae02ea8d44dc8 Mon Sep 17 00:00:00 2001 From: Caleb Mabry Date: Wed, 7 Jan 2026 15:29:55 -0500 Subject: [PATCH 6/6] Lint fix --- src/postgres_mcp/server.py | 2 +- src/postgres_mcp/utils/url.py | 3 ++- tests/unit/utils/test_url.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/postgres_mcp/server.py b/src/postgres_mcp/server.py index 90017d6..f3e2c8f 100644 --- a/src/postgres_mcp/server.py +++ b/src/postgres_mcp/server.py @@ -26,9 +26,9 @@ from .index.llm_opt import LLMOptimizerTool from .index.presentation import TextPresentation from .moldes.model import AccessMode -from .resource import register_resource_templates from .resource import format_error_response from .resource import format_text_response +from .resource import register_resource_templates from .sql import SafeSqlDriver from .sql import check_hypopg_installation_status from .sql import obfuscate_password diff --git a/src/postgres_mcp/utils/url.py b/src/postgres_mcp/utils/url.py index c74e959..925f62b 100644 --- a/src/postgres_mcp/utils/url.py +++ b/src/postgres_mcp/utils/url.py @@ -1,4 +1,5 @@ -from urllib.parse import quote, unquote +from urllib.parse import quote +from urllib.parse import unquote def fix_connection_url(url: str) -> str: diff --git a/tests/unit/utils/test_url.py b/tests/unit/utils/test_url.py index 4f898af..beb38b0 100644 --- a/tests/unit/utils/test_url.py +++ b/tests/unit/utils/test_url.py @@ -1,4 +1,5 @@ import pytest + from postgres_mcp.utils.url import fix_connection_url