Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-38525

Hide plaintext password from SHOW CREATE SERVER (Spider Engine)

    XMLWordPrintable

Details

    • Q2/2026 Server Maintenance

    Description

      Subject: Mask password in SHOW CREATE SERVER output

      Previously, SHOW CREATE SERVER displayed the password in plaintext,
      which posed a security risk as users with FEDERATED ADMIN privilege
      could see credentials for remote database connections.

      This patch masks the password value as '********' in the output while
      keeping the actual password stored in mysql.servers for functional
      use.

      This change encourages the security best practice of using SERVER
      objects (instead of inline credentials in CREATE TABLE) for
      Spider/Federated tables, as the password is now protected from
      casual exposure.

      File: sql/sql_show.cc

      Location: mysql_show_create_server() function (lines 1530-1588)

      This function generates the output for SHOW CREATE SERVER command. It builds a string like:

           CREATE SERVER `srv` FOREIGN DATA WRAPPER mysql OPTIONS (host 'localhost', password 'xxx', ...);
      

      Original Code (lines 1564-1575):

           engine_option_value* option= server->option_list;
           bool first= true;
           while (option)
           {
             if (!first)
               buffer.append(STRING_WITH_LEN(", "));
             buffer.append(option->name);
             buffer.append(STRING_WITH_LEN(" "));
             append_unescaped(&buffer, option->value.str, option->value.length);
             first= false;
             option= option->next;
           }
      

      This loops through all server options (stored as a linked list) and outputs each one as name 'value'. The append_unescaped() function handles proper quoting/escaping of the value string.

      Modified Code:

           engine_option_value* option= server->option_list;
           bool first= true;
           static const LEX_CSTRING password_str= {STRING_WITH_LEN("password")};
           while (option)
           {
             if (!first)
               buffer.append(STRING_WITH_LEN(", "));
             buffer.append(option->name);
             buffer.append(STRING_WITH_LEN(" "));
             if (option->name.streq(password_str))
               buffer.append(STRING_WITH_LEN("'********'"));
             else
               append_unescaped(&buffer, option->value.str, option->value.length);
             first= false;
             option= option->next;
           }
      

      Changes Explained:

      • static const LEX_CSTRING password_str= {STRING_WITH_LEN("password")}

        ;

      • Declares a constant string "password" for comparison
      • LEX_CSTRING is MariaDB's length-prefixed string struct: {const char* str, size_t length}
      • STRING_WITH_LEN("password") is a macro that expands to "password", 8 (string and its length)
      • static avoids recreating it on each function call
      • if (option->name.streq(password_str))
      • option->name is of type engine_option_value::Name, which inherits from Lex_ident_ci (case-insensitive identifier)
      • streq() performs case-insensitive comparison using my_charset_utf8mb3_general1400_as_ci
      • This means PASSWORD, Password, password all match
      • buffer.append(STRING_WITH_LEN("'********'"));
      • If it's a password option, append the masked literal string
      • Already includes quotes, so no need for append_unescaped()
      • else append_unescaped(...)
      • For all other options, use the original behavior

      Data Structures Involved:

           // From sql/create_options.h
           class engine_option_value {
             Name name;           // Inherits from Lex_ident_ci (case-insensitive)
             Value value;         // The option value string  
             engine_option_value *next;  // Linked list pointer
             bool quoted_value;   // Was value quoted in SQL?
             ...
           };
           
           // From sql/lex_ident.h  
           class Lex_ident_ci {
             // Case-insensitive identifier comparison
             bool streq(const LEX_CSTRING &rhs) const {
               return my_charset_utf8mb3_general1400_as_ci.streq(*this, rhs);
             }
           };
      

      Flow:

      SHOW CREATE SERVER srv;


      mysql_show_create_server()


      Loop through server->option_list

      ├─► option->name == "host" → append "host 'localhost'"
      ├─► option->name == "password" → append "password '********'" ← MASKED
      ├─► option->name == "user" → append "user 'root'"
      └─► option->name == "port" → append "port '3306'"


      Return: CREATE SERVER `srv` ... OPTIONS (host 'localhost', password '********', ...);

      The actual password remains untouched in server->option_list and in mysql.servers table - only the display output is masked.

      Attachments

        Issue Links

          Activity

            People

              ycp Yuchen Pei
              claudio.nanni Claudio Nanni
              Votes:
              1 Vote for this issue
              Watchers:
              7 Start watching this issue

              Dates

                Created:
                Updated:

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.