From 1ed064ca65615da1d4fd39aebc2766ebca471716 Mon Sep 17 00:00:00 2001 From: Scott Nemes Date: Tue, 20 Jan 2026 10:57:21 -0800 Subject: [PATCH 1/4] Added new connection scheme check function, and updated password check logic to use it. --- mycli/main.py | 11 +++++++++-- mycli/packages/parseutils.py | 13 ++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mycli/main.py b/mycli/main.py index 5d8faa08..f00af3af 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -56,7 +56,7 @@ from mycli.packages import special from mycli.packages.filepaths import dir_path_exists, guess_socket_location from mycli.packages.hybrid_redirection import get_redirect_components, is_redirect_command -from mycli.packages.parseutils import is_destructive, is_dropping_database +from mycli.packages.parseutils import is_destructive, is_dropping_database, is_valid_connection_scheme from mycli.packages.prompt_utils import confirm, confirm_destructive_query from mycli.packages.special.favoritequeries import FavoriteQueries from mycli.packages.special.main import ArgType @@ -1584,7 +1584,14 @@ def cli( password = click.prompt("Enter password", hide_input=True, show_default=False, default='', type=str, err=True) # if the password value looks like a DSN, treat it as such and # prompt for password - elif database is None and password is not None and password.startswith("mysql://"): + elif database is None and password is not None and "://" in password: + # check if the scheme is valid. We do not actually have any logic for these, but + # it will most usefully catch the case where we erroneously catch someone's + # password, and give them an easy error message to follow / report + is_valid_scheme, scheme = is_valid_connection_scheme(password) + if not is_valid_scheme: + click.secho(f"Error: Unknown connection scheme provided for DSN URI ({scheme}://)", err=True, fg="red") + sys.exit(1) database = password password = click.prompt("Enter password", hide_input=True, show_default=False, default='', type=str, err=True) # getting the envvar ourselves because the envvar from a click diff --git a/mycli/packages/parseutils.py b/mycli/packages/parseutils.py index b29e7cbd..a913043f 100644 --- a/mycli/packages/parseutils.py +++ b/mycli/packages/parseutils.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from typing import Any, Generator +from typing import Any, Generator, Tuple import sqlglot import sqlparse @@ -23,6 +23,17 @@ } +def is_valid_connection_scheme(text: str) -> Tuple[bool, str | None]: + # exit early if the text does not resemble a DSN URI + if "://" not in text: + return False, None + scheme = text.split("://")[0] + if scheme not in ("mysql", "mysqlx", "tcp", "socket", "ssh"): + return False, scheme + else: + return True, None + + def last_word(text: str, include: str = "alphanum_underscore") -> str: r""" Find the last word in a sentence. From c5a4bd61da1a02eb0666db4049ad637361a99594 Mon Sep 17 00:00:00 2001 From: Scott Nemes Date: Tue, 20 Jan 2026 11:14:53 -0800 Subject: [PATCH 2/4] Added test case --- test/test_main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/test_main.py b/test/test_main.py index fec23cb9..07ff3ca9 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -11,7 +11,7 @@ from click.testing import CliRunner from pymysql.err import OperationalError -from mycli.main import MyCli, cli, thanks_picker +from mycli.main import MyCli, cli, thanks_picker, is_valid_connection_scheme import mycli.packages.special from mycli.packages.special.main import COMMANDS as SPECIAL_COMMANDS from mycli.sqlexecute import ServerInfo, SQLExecute @@ -40,6 +40,16 @@ ] +def test_is_valid_connection_scheme_valid(executor, capsys): + is_valid, scheme = is_valid_connection_scheme("mysql://test@localhost:3306/dev") + assert is_valid + + +def test_is_valid_connection_scheme_invalid(executor, capsys): + is_valid, scheme = is_valid_connection_scheme("nope://test@localhost:3306/dev") + assert not is_valid + + @dbtest def test_ssl_mode_on(executor, capsys): runner = CliRunner() From 4b547002535cd9dbde1464efacc921cddb1a3e8a Mon Sep 17 00:00:00 2001 From: Scott Nemes Date: Tue, 20 Jan 2026 11:16:10 -0800 Subject: [PATCH 3/4] Reordered imports --- test/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_main.py b/test/test_main.py index 07ff3ca9..ebbed6c7 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -11,7 +11,7 @@ from click.testing import CliRunner from pymysql.err import OperationalError -from mycli.main import MyCli, cli, thanks_picker, is_valid_connection_scheme +from mycli.main import MyCli, cli, is_valid_connection_scheme, thanks_picker import mycli.packages.special from mycli.packages.special.main import COMMANDS as SPECIAL_COMMANDS from mycli.sqlexecute import ServerInfo, SQLExecute From 868ff5305cf4c484040f8e4ec402f6aba6ab20a3 Mon Sep 17 00:00:00 2001 From: Scott Nemes Date: Tue, 20 Jan 2026 11:18:31 -0800 Subject: [PATCH 4/4] Moved to built-in tuple typing --- mycli/packages/parseutils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mycli/packages/parseutils.py b/mycli/packages/parseutils.py index a913043f..c47f9472 100644 --- a/mycli/packages/parseutils.py +++ b/mycli/packages/parseutils.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from typing import Any, Generator, Tuple +from typing import Any, Generator import sqlglot import sqlparse @@ -23,7 +23,7 @@ } -def is_valid_connection_scheme(text: str) -> Tuple[bool, str | None]: +def is_valid_connection_scheme(text: str) -> tuple[bool, str | None]: # exit early if the text does not resemble a DSN URI if "://" not in text: return False, None