Skip to content

Add PostgreSQL psql meta-command support to admin interface#5367

Merged
renecannao merged 4 commits into
v3.0from
v3.0_pgsql-meta-cmd-admin-5365
Feb 12, 2026
Merged

Add PostgreSQL psql meta-command support to admin interface#5367
renecannao merged 4 commits into
v3.0from
v3.0_pgsql-meta-cmd-admin-5365

Conversation

@rahim-kanji

@rahim-kanji rahim-kanji commented Feb 11, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds support for common psql meta-commands (\dt, \di, \dv, \d, \l) in ProxySQL's PostgreSQL admin interface (port 6132). The expanded queries targeting PostgreSQL system catalogs (pg_catalog.pg_class, pg_catalog.pg_database) are now intercepted and translated to SQLite queries against the internal configuration database.

Closes #5365

Summary by CodeRabbit

  • New Features

    • Admin interface recognizes PostgreSQL psql meta-commands (\l, \d, \dt, \di, \dv) to list databases, relations, tables, indexes, and views with LIKE/regex-style pattern support and improved matching behavior.
    • Converts common psql pattern forms to SQLite-friendly filters and preserves existing behavior for non-meta commands.
  • Tests

    • Added end-to-end tests validating listings, pattern filtering, injection-like inputs, connection info, and buffer/robustness cases.

Implement handling for psql meta-commands (\dt, \di, \dv, \d, \l) in ProxySQL's PostgreSQL admin interface by intercepting expanded queries and converting them to appropriate SQLite queries.

Supported commands:
- \l, \l+: List databases (maps to PRAGMA DATABASE_LIST)
- \dt [pattern]: List tables with optional pattern matching
- \di [pattern]: List indexes with optional pattern matching
- \dv [pattern]: List views with optional pattern matching
- \d: List all relations (tables, views, indexes, triggers)

Pattern matching supports psql regex syntax like '\dt mytest*' by converting PostgreSQL regex patterns (^(pattern).*$) to SQLite LIKE patterns (pattern%).
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @rahim-kanji, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the usability of ProxySQL's PostgreSQL admin interface by enabling direct support for several common psql meta-commands. This allows PostgreSQL users to interact with the ProxySQL admin interface using familiar commands, as the system now intelligently intercepts and translates these commands into internal SQLite queries, providing a more seamless administrative experience.

Highlights

  • PostgreSQL Meta-Command Support: Added support for common psql meta-commands (\dt, \di, \dv, \d, \l) within ProxySQL's PostgreSQL admin interface.
  • Query Translation: Implemented interception and translation of PostgreSQL system catalog queries (e.g., pg_catalog.pg_class, pg_catalog.pg_database) into equivalent SQLite queries for the internal configuration database.
  • Pattern Matching and Conversion: Introduced helper functions (extract_psql_pattern, convert_regex_to_like) to extract patterns from psql queries and convert PostgreSQL regex syntax to SQLite LIKE patterns, including handling of special characters and SQL escaping.
  • New Test Suite: A new test file (pgsql_admin_metacmds-t.cpp) and corresponding test group entry were added to thoroughly validate the new meta-command functionality using an actual psql client.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • lib/Admin_Handler.cpp
    • Implemented helper functions extract_psql_pattern, convert_regex_to_like, and handle_psql_list_command to process and translate PostgreSQL meta-command patterns.
    • Modified admin_session_handler to intercept specific PostgreSQL system catalog queries and convert them into SQLite queries for database, table, index, and view listings.
  • test/tap/groups/groups.json
    • Added pgsql_admin_metacmds-t to the list of test groups.
  • test/tap/tests/pgsql_admin_metacmds-t.cpp
    • Added a new test file to validate the functionality of PostgreSQL psql meta-commands (\l, \dt, \di, \dv, \d, \conninfo) and SQL injection protection.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds translation of PostgreSQL psql meta-commands (\dt, \di, \dv, \d, \l) in the admin interface by parsing psql patterns, converting a subset of regex constructs to SQLite LIKE, routing translated queries to sqlite_master, and adds TAP tests exercising these commands.

Changes

Cohort / File(s) Summary
Admin handler (psql meta-command translation)
lib/Admin_Handler.cpp
Add extract_psql_pattern, convert_regex_to_like, and handle_psql_list_command; extend admin_session_handler to detect psql meta-commands, parse patterns, convert to SQLite LIKE, and build/execute sqlite_master queries. Review pattern parsing, escaping, and query-building logic.
Test groups config
test/tap/groups/groups.json
Add new test group pgsql_admin_metacmds-t with standard defaults for running the new TAP tests.
Integration tests (psql meta-commands)
test/tap/tests/pgsql_admin_metacmds-t.cpp
New TAP test validating \l, \dt (with/without patterns), \di, \dv, \d, \conninfo, SQL injection-like patterns, and long-quote edge case; includes helpers to run psql and assert expected outputs and skips when prerequisites are missing.

Sequence Diagram

sequenceDiagram
    participant psql as psql Client
    participant handler as Admin Handler
    participant parser as Pattern Parser
    participant converter as Regex Converter
    participant sqlite as SQLite/sqlite_master

    psql->>handler: Send meta-command (e.g., "\dt pattern*")
    activate handler
    handler->>handler: Detect psql meta-command
    handler->>parser: extract_psql_pattern()
    activate parser
    parser-->>handler: Return parsed pattern
    deactivate parser
    handler->>converter: convert_regex_to_like()
    activate converter
    converter-->>handler: Return LIKE clause
    deactivate converter
    handler->>handler: Build SQLite query with LIKE filter
    handler->>sqlite: Execute translated query
    activate sqlite
    sqlite-->>handler: Return metadata rows
    deactivate sqlite
    handler->>psql: Format and return results
    deactivate handler
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I sniffed the patterns, swapped stars for percent signs,
I hopped through \dt and \l and mapped their designs.
From regex to LIKE, I nudged queries in place,
Returning tidy rows with a twitch of my face.
A tiny rabbit cheers — admin commands embrace!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding PostgreSQL psql meta-command support to the admin interface.
Linked Issues check ✅ Passed The PR implementation addresses all coding objectives from issue #5365: enabling psql meta-commands (\dt, \di, \dv, \d, \l), intercepting PostgreSQL system catalog queries, translating them to SQLite queries, and supporting pattern matching.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the PR objectives. The implementation adds PostgreSQL meta-command support to the admin handler, test configuration groups, and comprehensive tests for the new functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v3.0_pgsql-meta-cmd-admin-5365

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
lib/Admin_Handler.cpp (2)

376-413: Unhandled regex constructs pass through literally.

convert_regex_to_like handles ^, $, (), .*, and ', but other common regex constructs like +, ?, [...], \d, |, etc. will be passed through as literal characters into the LIKE pattern. Since LIKE doesn't treat these as special, they'll become literal match characters, which may silently produce no results for patterns that psql's regex would have matched. This is acceptable for a "best-effort" translation, but consider adding a brief comment listing the known unsupported constructs to set expectations for future maintainers.


4238-4297: Fragile substring-based detection of psql meta-commands.

The interception logic relies on strcasestr substring matching against known psql-expanded query patterns (e.g., c.relkind IN ('r','p','v' for \d vs c.relkind IN ('r' for \dt). The ordering is currently correct — \d is checked before \dt to avoid the latter's substring matching the former's query.

However, this is fragile if psql changes its expanded query format across versions (e.g., reordering relkind values or changing whitespace). Consider adding a comment documenting which psql versions were tested, so future maintainers know when re-validation is needed.

test/tap/tests/pgsql_admin_metacmds-t.cpp (2)

113-125: SQL injection test is testing psql's behavior, not ProxySQL's directly.

The test at Line 113-125 sends \dt test' OR '1'='1 through the psql client. However, psql itself will parse and transform this pattern before sending the expanded SQL to ProxySQL. The actual query reaching extract_psql_pattern depends on how psql handles the malformed pattern — psql may reject it or escape it before it ever reaches ProxySQL's admin handler.

For a more targeted test of extract_psql_pattern/convert_regex_to_like, consider also testing with a direct SQL query that mimics what psql would send (e.g., sending a raw SELECT ... FROM pg_catalog.pg_class ... WHERE c.relname OPERATOR(pg_catalog.~) '...' with injection payload) via a PgSQL connection library, bypassing psql's own sanitization.


51-111: Test assertions are very lenient — could pass even when the feature is broken.

Several tests check for generic strings like "name" or "type" in the output (e.g., Lines 57, 69, 108-109). These strings could appear in error messages, psql headers, or other unexpected output, causing false passes. For example, an error message like "ERROR: relation name not found" would satisfy output.find("name") != std::string::npos.

Consider checking for more specific expected output, such as known ProxySQL table names (e.g., pgsql_servers, pgsql_users) that should appear in \dt output, or at least verifying the absence of "ERROR" alongside the positive checks.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for PostgreSQL psql meta-commands in the admin interface, which is a great enhancement for usability. The implementation involves intercepting specific SELECT queries from psql and translating them into corresponding SQLite queries against ProxySQL's internal database. The changes are well-structured with helper functions for pattern extraction and conversion.

My review has identified a critical buffer overflow vulnerability and a few other areas for improvement regarding code safety, readability, and testing. Addressing these points will make the new feature more robust and secure.

Comment thread lib/Admin_Handler.cpp Outdated
Comment thread lib/Admin_Handler.cpp
Comment thread lib/Admin_Handler.cpp
Comment on lines +401 to +408
else if (*src == '\'') {
// Escape single quotes for SQL safety (double them)
if (dst < dst_end - 1) {
*dst++ = '\'';
*dst++ = '\'';
}
src++;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check if (dst < dst_end - 1) inside the else if (*src == '\'') block is redundant. The outer while loop condition dst < dst_end - 1 already guarantees that there is enough space for at least two characters (one for the current position and one for the null terminator). When escaping a single quote, two characters are written. The loop condition ensures this is safe. Removing the redundant inner check simplifies the code.

		else if (*src == '\'') {
			// Escape single quotes for SQL safety (double them)
			*dst++ = '\'';
			*dst++ = '\'';
			src++;
		}

Comment thread lib/Admin_Handler.cpp
Comment thread test/tap/tests/pgsql_admin_metacmds-t.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/tap/tests/pgsql_admin_metacmds-t.cpp`:
- Around line 38-48: The build_psql_cmd function is concatenating raw
admin_password, pgsql_admin_host, pgsql_admin_port, admin_username and meta_cmd
into a shell command which breaks on spaces or shell metacharacters; update
build_psql_cmd to shell-escape each interpolated value (admin_password,
pgsql_admin_host, pgsql_admin_port, admin_username and meta_cmd) before
concatenation—e.g. wrap values in single quotes and replace any internal single
quote with '\'' (or use a helper escape_shell_arg(const std::string&)), then use
those escaped strings when building the final command so the psql invocation is
safe.
🧹 Nitpick comments (1)
test/tap/tests/pgsql_admin_metacmds-t.cpp (1)

73-79: Pattern test doesn’t assert filtering; consider a negative-pattern check.

The \dt runtime* assertion (Line 73–79) will pass even if the pattern is ignored. A negative-pattern probe (that should return “No matching relations”) makes the test resilient to regressions.

✅ Strengthen the pattern test (and update plan)
 	// Test \dt with pattern
 	cmd = build_psql_cmd(cl, "\\dt runtime*");
 	output = exec(cmd.c_str());

 	has_output = output.find("List of relations") != std::string::npos ||
 	             output.find("name") != std::string::npos;
 	ok(has_output, "\\dt runtime* (tables with pattern): returned valid output");
+
+	// Negative pattern should yield no matches if filtering works
+	cmd = build_psql_cmd(cl, "\\dt __no_such_table__*");
+	output = exec(cmd.c_str());
+	bool no_match = output.find("No matching relations") != std::string::npos ||
+	                output.find("Did not find any relations") != std::string::npos;
+	ok(no_match, "\\dt __no_such_table__*: pattern filter applied");
-	plan(9);
+	plan(10);

Comment on lines +38 to +48
// Build psql connection string
std::string build_psql_cmd(const CommandLine& cl, const char* meta_cmd) {
std::stringstream ss;
ss << "PGPASSWORD=" << cl.admin_password << " ";
ss << "psql -h " << cl.pgsql_admin_host;
ss << " -p " << cl.pgsql_admin_port;
ss << " -U " << cl.admin_username;
ss << " -d postgres";
ss << " -c \"" << meta_cmd << "\"";
ss << " 2>&1";
return ss.str();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Shell-escape connection parameters to avoid failures with special characters.

Right now Line 41–45 concatenate raw env values into a shell command; passwords/usernames containing spaces or shell metacharacters can break the test run. Consider simple shell-escaping for these fields.

🛠️ Suggested fix (shell-escape args)
+static std::string shell_escape(const std::string& s) {
+	std::string out = "'";
+	for (char c : s) {
+		if (c == '\'') out += "'\"'\"'";
+		else out += c;
+	}
+	out += "'";
+	return out;
+}
+
 std::string build_psql_cmd(const CommandLine& cl, const char* meta_cmd) {
 	std::stringstream ss;
-	ss << "PGPASSWORD=" << cl.admin_password << " ";
-	ss << "psql -h " << cl.pgsql_admin_host;
+	ss << "PGPASSWORD=" << shell_escape(cl.admin_password) << " ";
+	ss << "psql -h " << shell_escape(cl.pgsql_admin_host);
 	ss << " -p " << cl.pgsql_admin_port;
-	ss << " -U " << cl.admin_username;
+	ss << " -U " << shell_escape(cl.admin_username);
 	ss << " -d postgres";
 	ss << " -c \"" << meta_cmd << "\"";
 	ss << " 2>&1";
 	return ss.str();
 }
🤖 Prompt for AI Agents
In `@test/tap/tests/pgsql_admin_metacmds-t.cpp` around lines 38 - 48, The
build_psql_cmd function is concatenating raw admin_password, pgsql_admin_host,
pgsql_admin_port, admin_username and meta_cmd into a shell command which breaks
on spaces or shell metacharacters; update build_psql_cmd to shell-escape each
interpolated value (admin_password, pgsql_admin_host, pgsql_admin_port,
admin_username and meta_cmd) before concatenation—e.g. wrap values in single
quotes and replace any internal single quote with '\'' (or use a helper
escape_shell_arg(const std::string&)), then use those escaped strings when
building the final command so the psql invocation is safe.

@rahim-kanji
rahim-kanji force-pushed the v3.0_pgsql-meta-cmd-admin-5365 branch from c74a1ad to be8eea8 Compare February 11, 2026 09:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@lib/Admin_Handler.cpp`:
- Around line 317-373: extract_psql_pattern fails to extract values when the
LIKE branch lands on a quoted literal and the code is also leaving
converted_pattern vulnerable to SQL injection; update extract_psql_pattern to
detect and strip surrounding single quotes when the LIKE branch finds an opening
'\'' (advance pattern past the opening quote and stop at the matching closing
quote, not just the first quote-less char), and ensure the OPERATOR branch
similarly bounds the quoted value; then eliminate interpolation-based SQL in
handle_psql_list_command by using a parameterized/prepared query (or, if
preparing is not feasible, call a robust escape routine that doubles
single-quotes and also escapes LIKE metacharacters '%' and '_' consistently via
convert_regex_to_like) so converted_pattern cannot inject SQL (refer to
functions extract_psql_pattern, convert_regex_to_like, and
handle_psql_list_command).

In `@test/tap/tests/pgsql_admin_metacmds-t.cpp`:
- Around line 174-186: The test currently calls plan(10) then early-returns with
return exit_status() when the connection check (built via build_psql_cmd and
exec) fails, causing a TAP plan mismatch; replace the early return in the
connection-failure branch with a BAIL_OUT call (e.g. BAIL_OUT with a descriptive
message including test_output or connection info) so the TAP harness treats the
whole suite as aborted, or alternatively call skip_all to skip remaining tests —
make this change inside the block that uses ok(connected, ...) and diag(...)
that currently does return exit_status(), referencing build_psql_cmd, exec, ok,
diag and exit_status to locate the spot.
🧹 Nitpick comments (4)
lib/Admin_Handler.cpp (2)

374-411: convert_regex_to_like doesn't handle \ escapes or character classes.

The regex-to-LIKE converter handles ^, $, .*, (), and ', but PostgreSQL regex patterns can also contain:

  • \d, \w, \s and other backslash sequences
  • Character classes like [abc]
  • + and ? quantifiers
  • Literal . (without *)

A lone . is currently copied verbatim (Line 407), but in regex it means "any single character" (equivalent to _ in LIKE). This could cause incorrect matching. Since this is an admin-only interface, the practical risk is low, but it's worth noting.

Handle lone `.` as LIKE `_`
 	while (*src && dst < dst_end - 1) {
 		if (*src == '.' && *(src + 1) == '*') {
 			*dst++ = '%';
 			src += 2;
 		}
+		else if (*src == '.' && *(src + 1) != '*') {
+			// Lone dot in regex = any single character = _ in LIKE
+			*dst++ = '_';
+			src++;
+		}
 		else if (*src == '$' && (*(src + 1) == '\0' || *(src + 1) == '\'')) {

416-438: Stack-allocated buffers limit pattern length and silently truncate.

pattern_buf[128], converted_pattern[256], and buf[512] are all fixed-size stack buffers. snprintf will silently truncate if the combined query + pattern exceeds 512 bytes, and the 128-byte pattern limit is enforced by a hard-coded check (buf_size < 128). While unlikely to cause issues with typical psql patterns against an admin interface, an unexpectedly long table-name filter would silently produce a truncated/incorrect SQL query.

Consider using std::string or dynamically-sized buffers for robustness, especially since the rest of the codebase already uses std::string in many places.

test/tap/tests/pgsql_admin_metacmds-t.cpp (2)

113-125: SQL injection test may not exercise the intended code path.

The test at Line 115 passes \\dt test' OR '1'='1 as a psql meta-command. However, psql processes \dt client-side and expands it into a SELECT ... FROM pg_catalog.pg_class query. The injected ' OR '1'='1 becomes part of the pattern argument to \dt, and psql will embed it into its expanded query in a psql-specific way (likely as a regex operand inside single quotes). The test should verify what actually reaches ProxySQL's admin handler — consider adding a diag that prints the full output to confirm the pattern is being tested as intended.

Also, Line 115 embeds unescaped single quotes inside the double-quoted -c argument in the shell command. Depending on the shell, this could cause the command itself to fail before reaching psql, making this test pass vacuously (any error output matches the ERROR check at Line 121).


51-111: Test assertions are quite lenient — consider tightening expected output checks.

Most test functions check for very broad substrings (e.g., "name", "type", "database"). These could match error messages or unrelated output, causing false passes. For example, test_psql_list_databases checks for "name" which could appear in almost any error message. Consider checking for more specific psql output formatting or at least ensuring no "ERROR" is present alongside the positive checks.

Comment thread lib/Admin_Handler.cpp
Comment on lines +317 to +373
static bool extract_psql_pattern(const char* query, char* pattern_buf, size_t buf_size) {
if (!query || !pattern_buf || buf_size < 128) return false;

pattern_buf[0] = '\0';

// Look for pattern in c.relname
const char* relname_pos = strcasestr(query, "c.relname");
if (!relname_pos) return false;

// Skip to the operator or LIKE keyword
const char* value_pos = relname_pos + strlen("c.relname");
while (*value_pos && *value_pos == ' ') value_pos++;

// Safety check: ensure we haven't gone past end of string
if (!*value_pos) return false;

const char* pattern = nullptr;

// Check for LIKE operator first
const char* like_pos = strcasestr(value_pos, "LIKE");
if (like_pos) {
like_pos += 4; // Skip "LIKE"
while (*like_pos && *like_pos == ' ') like_pos++;
pattern = like_pos;
}
// Check for OPERATOR operator
else if (strcasestr(value_pos, "OPERATOR")) {
const char* op_pos = strcasestr(value_pos, "OPERATOR");
if (op_pos) {
const char* value_start = strchr(op_pos, '\'');
if (value_start) {
value_start++;
pattern = value_start;
}
}
}

if (!pattern) return false;

// Skip leading spaces
while (*pattern && *pattern == ' ') pattern++;

// Find end of pattern (first quote or newline)
const char* end = pattern;
size_t max_len = buf_size - 1; // Reserve one byte for null terminator
while (*end && *end != '\'' && *end != '\n' && (size_t)(end - pattern) < max_len) end++;

// Copy raw pattern to buffer (no escaping here - happens in convert_regex_to_like)
size_t len = end - pattern;
if (len > 0 && len < buf_size) {
memcpy(pattern_buf, pattern, len);
pattern_buf[len] = '\0';
return true;
}

return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

SQL injection via converted_pattern and broken LIKE-path extraction.

Two issues in extract_psql_pattern:

  1. LIKE path doesn't strip quotes – When psql (or any caller) sends c.relname LIKE 'pattern', the code skips past LIKE and whitespace (Line 338-340), landing on the opening '. The scan loop at Line 362 immediately stops at that quote, yielding len == 0 and returning false. The LIKE branch never successfully extracts a pattern.

  2. SQL injectionconverted_pattern is interpolated directly into a SQL string via snprintf in handle_psql_list_command (Line 427). While convert_regex_to_like doubles single quotes, % and _ from user input pass through unescaped as LIKE wildcards. More importantly, if any future code path feeds a pattern containing an unmatched ' that slips past the current extraction, it becomes a SQL injection vector. Consider using parameterized queries or at minimum a more defensive escaping strategy.

Fix for the LIKE-path quote stripping
 	// Check for LIKE operator first
 	const char* like_pos = strcasestr(value_pos, "LIKE");
 	if (like_pos) {
 		like_pos += 4; // Skip "LIKE"
 		while (*like_pos && *like_pos == ' ') like_pos++;
+		// Skip opening quote if present
+		if (*like_pos == '\'') like_pos++;
 		pattern = like_pos;
 	}
🤖 Prompt for AI Agents
In `@lib/Admin_Handler.cpp` around lines 317 - 373, extract_psql_pattern fails to
extract values when the LIKE branch lands on a quoted literal and the code is
also leaving converted_pattern vulnerable to SQL injection; update
extract_psql_pattern to detect and strip surrounding single quotes when the LIKE
branch finds an opening '\'' (advance pattern past the opening quote and stop at
the matching closing quote, not just the first quote-less char), and ensure the
OPERATOR branch similarly bounds the quoted value; then eliminate
interpolation-based SQL in handle_psql_list_command by using a
parameterized/prepared query (or, if preparing is not feasible, call a robust
escape routine that doubles single-quotes and also escapes LIKE metacharacters
'%' and '_' consistently via convert_regex_to_like) so converted_pattern cannot
inject SQL (refer to functions extract_psql_pattern, convert_regex_to_like, and
handle_psql_list_command).

Comment on lines +174 to +186
plan(10);

// Test connection first
std::string test_cmd = build_psql_cmd(cl, "\\conninfo");
std::string test_output = exec(test_cmd.c_str());
bool connected = test_output.find("connected") != std::string::npos ||
test_output.find("database") != std::string::npos;
ok(connected, "Connected to PostgreSQL admin interface");

if (!connected) {
diag("Failed to connect to PostgreSQL admin interface");
return exit_status();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

TAP plan mismatch on early exit when connection fails.

plan(10) is declared at Line 174, but if the connection check fails at Line 183, return exit_status() is called after only 1 test has been emitted. The TAP harness will report a plan mismatch (planned 10, ran 1). Use BAIL_OUT or skip the remaining tests instead.

Proposed fix
 	if (!connected) {
 		diag("Failed to connect to PostgreSQL admin interface");
-		return exit_status();
+		// Skip remaining tests to match plan(10)
+		skip(9, "Cannot connect to PostgreSQL admin interface");
+		return exit_status();
 	}
🤖 Prompt for AI Agents
In `@test/tap/tests/pgsql_admin_metacmds-t.cpp` around lines 174 - 186, The test
currently calls plan(10) then early-returns with return exit_status() when the
connection check (built via build_psql_cmd and exec) fails, causing a TAP plan
mismatch; replace the early return in the connection-failure branch with a
BAIL_OUT call (e.g. BAIL_OUT with a descriptive message including test_output or
connection info) so the TAP harness treats the whole suite as aborted, or
alternatively call skip_all to skip remaining tests — make this change inside
the block that uses ok(connected, ...) and diag(...) that currently does return
exit_status(), referencing build_psql_cmd, exec, ok, diag and exit_status to
locate the spot.

@rahim-kanji
rahim-kanji force-pushed the v3.0_pgsql-meta-cmd-admin-5365 branch from be8eea8 to 9460520 Compare February 11, 2026 13:33
@sonarqubecloud

Copy link
Copy Markdown

@mirostauder

Copy link
Copy Markdown
Contributor

retest this please

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.

Add support for PostgreSQL psql meta-commands (\dt, \di, \dv, \d, \l) in admin interface

3 participants