Skip to content

0.2.5 injects 'None' into username field #384

Description

@mattsq

Describe the bug

When DATABASE_URI has no password (e.g. Kerberos SSO with LOGMECH=KRB5, or JWT/browser auth), TDConn.init in teradata_mcp_server/tools/td_connect.py builds the SQLAlchemy URL with an f-string that stringifies a None password into the literal 4-character string "None":

password = parsed_url.password          # None when the URI carries no password
...
sqlalchemy_url = (
    f"teradatasql://{user}:{password}@{self._base_host}:{self._base_port}/{self._base_db}?{main_query}"
)
# -> "teradatasql://171776:None@host:1025/db?LOGMECH=KRB5"   (password is the literal "None")

The server therefore logs on with a bogus password "None", which breaks passwordless authentication. With the teradatasql 20.x driver this surfaces as a hard logon failure on Windows SSPI/Kerberos:

[Version 20.0.0.63] [Session 0] [Teradata Security] [Error 0xD0000/0xE3000259]
Error while passing an invalid handle.
  ... goteragss.(*sspiSecContext).gsspInitSecContext  (Gssp2Sspi.go:225)

Root cause: given an explicit (non-empty) password together with LOGMECH=KRB5, the Go driver attempts an explicit-credential SSPI logon instead of using the cached Kerberos ticket, and fails. The same defect affects any path building f"teradatasql://{user}:{secret}@..." when a component is None (e.g. _validate_jwt_token).

To Reproduce
Steps to reproduce the behavior:

  1. On a host with a valid Kerberos TGT, set DATABASE_URI=teradata://@:1025/?logmech=KRB5 (no password).
  2. Start the MCP server and invoke any tool that hits the database (e.g. dba_databaseVersion).
  3. See the logon fail with Error 0xD0000/0xE3000259 - Error while passing an invalid handle.

Minimal driver-level repro that isolates it from the MCP server:

import teradatasql
HOST = "<host>"
# passwordless SSO -> works
teradatasql.connect(host=HOST, user="<user>", logmech="KRB5").cursor().execute("SELECT 1")
# literal "None" password (what the server builds) -> fails with the invalid-handle error
teradatasql.connect(host=HOST, user="<user>", password="None", logmech="KRB5")
# empty-string password -> works
teradatasql.connect(host=HOST, user="<user>", password="", logmech="KRB5").cursor().execute("SELECT 1")
password value passed outcome
omitted (SSO) SELECT 1 OK
"None" (current server behaviour) invalid handle logon failure
"" (empty) SELECT 1 OK

Expected behavior
When the URI has no username and/or no password, the reconstructed connection string should omit those components (or pass empty strings) rather than emitting the literal "None", so passwordless mechanisms (KRB5, JWT, browser, etc.) authenticate correctly.

Suggested fix — use SQLAlchemy's URL builder, which handles None correctly, instead of an f-string:

from sqlalchemy import URL
sqlalchemy_url = URL.create(
    "teradatasql",
    username=user,                 # None -> omitted
    password=password,             # None -> omitted (not "None")
    host=self._base_host,
    port=self._base_port,
    database=self._base_db or None,
    query={"LOGMECH": self._default_basic_logmech, **self._extra_uri_params},
)

(and the equivalent for the _validate_basic_credentials / _validate_jwt_token URLs).

Screenshots
N/A

Desktop (please complete the following information):

  • OS: Windows 11
  • teradata-mcp-server version: 0.2.5
  • teradatasql: 20.0.0.63, teradatasqlalchemy: 20.0.0.9
  • SQLAlchemy: 2.0.51
  • Python: 3.14
  • Auth: Kerberos SSO, LOGMECH=KRB5, no stored password
  • Teradata server: 17.20.03.38

Smartphone (please complete the following information):
N/A

Additional context
Workaround for users (no code change): give the URI an explicit empty password by adding a colon after the username, so the f-string preserves it as empty instead of "None":

DATABASE_URI=teradata://<user>:@<host>:1025/<db>?logmech=KRB5

Verified this connects successfully via KRB5 SSO on the affected versions. Likely only manifests with the teradatasql 20.x Go driver; older C-based drivers appear to have ignored the stray password.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions