Back to skill

Security audit

Sql Buddy

Security checks for vulnerabilities and agentic risk

Overview

This SQL helper matches its stated purpose, but it under-discloses serious risks around database credentials, read-only enforcement, and sending query results to an external model.

Review carefully before installing. Use only test or read-only database accounts, avoid production credentials, avoid saving passwords through this skill, and assume schema details, query text, and some result values may be sent to the configured model provider. Do not rely on the advertised read-only or parameterized-query protections without code fixes and database-side read-only permissions.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/connection_manager.py:85
Finding

Database Credentials Are Persisted in Plaintext Without Explicit Access Controls

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/connection_manager.py:20
Finding

Remote Database Credentials May Be Transmitted Without Enforced TLS

Content
View full analysis
Dict[str, Any]: result = urlparse(conn_str) config = { "type": result.scheme.replace("postgresql", "postgresql").replace("postgres", "postgresql"), "host": result.hostname or "localhost", "port": result.port, "database": result.path.lstrip("/") if result.path else "", "username": result.username or "", "password": result.password or "", "ssl": False, } ``` ```python elif package == "pymysql": import pymysql conn = pymysql.connect( host=config.get("host", "localhost"), port=config.get("port", 3306), database=config.get("database", ""), user=config.get("username", ""), password=config.get("password", ""), connect_timeout=5, ssl={"ca": None} if config.get("ssl") else None, ) conn.close() ``` Other execution and schema-discovery paths similarly pass usernames and passwords to database drivers without consistently requiring TLS or configuring certificate and hostname verification. ### Technical Analysis Sending database credentials to a user-selected database host is necessary for the declared remote database functionality. The network behavior is therefore not, by itself, evidence of unrelated credential exfiltration. The security problem is that parsed connections default to `"ssl": False`, and several connection paths do not enforce transport encryption. Even where an SSL argument exists, using `{"ca": None}` does not establish robust certificate-chain and hostname verification. A ...[truncated 1284 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/insight_generator.py:11
Finding

Raw Database Records Are Embedded in Model-Facing Insight Prompts

Content
View full analysis
str: """ Build an LLM prompt to interpret query results. """ result_text = "" for i, row in enumerate(rows[:5]): row_parts = [] for j, col in enumerate(columns): if j < len(row): row_parts.append(f"{col}={row[j]}") result_text += f" Row {i+1}: {', '.join(row_parts)}\n" if len(rows) > 5: result_text += f" ... and {len(rows) - 5} more rows\n" summary_text = "" for col, stats in summary.items(): summary_text += f" {col}: min={stats['min']}, max={stats['max']}, avg={stats['avg']:.2f}\n" prompt = f"""You are a data analyst. Interpret the following query results. User query: {query} Generated SQL: {sql} Columns: {', '.join(columns)} Total rows: {len(rows)} Sample results: {result_text} Numerical summary: {summary_text} Provide a brief interpretation (2-3 sentences) in {_detect_language(query)}: 1. What the key finding is 2. Any notable trends or anomalies 3. A practical takeaway or recommendation Keep it concise and actionable.""" return prompt ``` ### Technical Analysis The function serializes up to five complete result rows into a prompt intended for an external language model. It also includes column names, generated SQL, and derived statistics. This directly conflicts with the project documentation stating that the LLM receives schema information but never actual row data. No value-level redaction, sensitive-column filtering, consent check, or data-classification step is applied before constructing the prompt. Although this function only builds and returns a prompt, its state ...[truncated 1091 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/executor.py:8
Finding

Read-Only SQL Enforcement Is Broken and Fails Open for Unknown Statements

Content
View full analysis
QueryResult: """ Execute a SQL query safely. """ import time # Security: check if query is read-only if not allow_write and not security.is_read_only_query(sql): return QueryResult( columns=[], rows=[], execution_time_ms=0, error="Write operation blocked. Use --allow-write to enable INSERT/UPDATE/DELETE.", ) ``` The referenced validator contains the following fail-open logic: ```python WRITE_STATEMENTS = re.compile( r'^\s*(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|REPLACE|MERGE)\b', re.IGNORECASE ) READ_STATEMENTS = re.compile( r'^\s*(SELECT|EXPLAIN|PRAGMA|SHOW|DESCRIBE|WITH)\b', re.IGNORECASE ) def is_read_only_query(sql: str) -> bool: sql = sql.strip() # Remove comments sql = re.sub(r'--.*$', '', sql, flags=re.MULTILINE) sql = re.sub(r'/\*.*?\*/', '', sql, flags=re.DOTALL) # Check for write statements if WRITE_STATEMENTS.match(sql): return False # If it starts with a known read statement, it's safe if READ_STATEMENTS.match(sql): return True # For multi-statement queries, check each statement statements = _split_statements(sql) if len(statements) > 1: return all(is_read_only_query(s.strip()) for s in statements if s.strip()) # Default: treat unknown as read-only but flag it logger.warning("Unknown SQL statement type, treating as read-only: %.100s", sql) return True ``` ### Tech ...[truncated 2431 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/security.py:138
Finding

Sensitive Schema Masking Is Unused and Can Preserve Sensitive Names

Content
View full analysis
list: """ Mask sensitive column names in schema info before sending to LLM. """ masked = [] for table in schema_info: table_copy = dict(table) columns = table_copy.get("columns", []) masked_columns = [] for col in columns: col_copy = dict(col) col_name = col.get("column_name", col.get("name", "")) for pattern in SENSITIVE_COLUMN_PATTERNS: if pattern.search(col_name): col_copy["column_name"] = col_copy.get("name", "***") col_copy["masked"] = True col_copy["type"] = "***" break masked_columns.append(col_copy) table_copy["columns"] = masked_columns masked.append(table_copy) return masked ``` The model-facing path does not invoke this function: ```python def generate_sql(query: str, schema_text: str, dialect: str = "sqlite", intent: Optional[Dict] = None) -> Dict: if not intent: intent = nl_parser.parse_intent(query) prompt = nl_parser.build_nl_prompt(query, schema_text, dialect) ``` Schema formatting includes metadata directly: ```python col_line = f" - {col['column_name']} ({col.get('type', 'unknown')}) {nullable}" if pk: col_line += " [PK]" if default: col_line += f" default={default}" if hint and hint != "unknown": col_line += f" // {hint}" ``` ### Technical Analysis The project contains a masking helper but no reviewed call site applies it before model-facing schema formatting. As a result, table names, column names, types, defaults, relationshi ...[truncated 1482 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/nl_parser.py:134
Finding

Untrusted Queries and Database Metadata Can Inject Instructions Into LLM Prompts

Content
View full analysis
str: """ Build the LLM prompt for NL → SQL generation. """ # Few-shot examples omitted for brevity in this excerpt. example_text = examples.get(dialect, examples["sqlite"]) prompt = f"""You are a SQL expert. Given a database schema and a natural language query, generate the correct SQL. Target dialect: {dialect} {schema_text} {example_text} Rules: - Generate ONLY the SQL statement, no explanations unless asked - Use the correct {dialect} dialect syntax - Always use AS aliases for computed columns - Use table aliases for JOIN queries - Add LIMIT clause for SELECT queries if not specified - The query MUST be read-only (SELECT only) - Do NOT generate INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE Natural Language Query: {query} SQL:""" return prompt ``` The insight prompt similarly interpolates untrusted material: ```python prompt = f"""You are a data analyst. Interpret the following query results. User query: {query} Generated SQL: {sql} Columns: {', '.join(columns)} Total rows: {len(rows)} Sample results: {result_text} Numerical summary: {summary_text} ... """ ``` ### Technical Analysis The natural-language query, database schema text, generated SQL, column names, and row values are inserted directly into instruction-bearing prompts. The prompts do not establish strong trust boundaries or clearly label embedded content as inert data whose instructions must never be followed. Database metadata is attacker-controllable in environments where an attacker can create or rename tables, columns, indexes, comments, or d ...[truncated 1763 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (28)

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding

Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Undeclared live connectivity to local or remote databases expands the attack surface and can expose internal services, credentials, or metadata, especially when users believe the skill is mostly an offline SQL assistant. If safe execution and NL→SQL are also unimplemented, remote access combined with false assurances raises operational risk.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/connection_manager.py (reported line 89)May include surrounding context.

python
"""
    Save a named connection configuration.
    Passwords are stored with basic obfuscation (not encryption).
    For production, use environment variables or system keychain.
    """
    os.makedirs(DATA_DIR, exist_ok=True)

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module advertises 'safe', 'read-only by default', and 'parameterized queries', but the implementation does not reliably enforce those guarantees. In particular, the read-only gate is broken because the code imports is_read_only_query directly but calls security.is_read_only_query(sql), which would fail and undermine the intended protection, while other paths still execute raw SQL against live databases.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The documentation claims parameterized queries, but all execution paths pass raw SQL strings directly to cursor.execute(sql), and the EXPLAIN helpers interpolate user-controlled SQL into f"EXPLAIN ... {sql}". In a natural-language-to-SQL skill, this materially increases the risk that untrusted model- or user-generated SQL is executed directly, enabling injection of stacked statements or dangerous commands depending on driver and database behavior.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/insight_generator.py (reported line 62)May include surrounding context.

python
Keep it concise and actionable."""
    
    return prompt


def _detect_language(query: str) -> str:

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
92% confidence
Finding

The function constructs and returns a raw LLM prompt that directly embeds untrusted user input (query) and schema_text into the instruction context. In an NL-to-SQL skill, this creates a prompt-injection surface where a crafted natural-language query can try to override the read-only rules, exfiltrate schema details, or induce unsafe SQL generation if downstream controls rely mainly on the model following prompt instructions.

Content

Scanner excerpt · scripts/nl_parser.py (reported line 205)May include surrounding context.

python
SQL:"""
    
    return prompt


def extract_query_summary(query: str) -> str:

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module claims read-only enforcement, but is_read_only_query() returns True for any statement that is neither explicitly matched as write nor recognized as read. That fail-open behavior can permit dangerous dialect-specific or procedural statements, especially in a tool that converts natural language into SQL and may execute the result automatically.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
81% confidence
Finding

The skill advertises capabilities that imply file access, local credential storage, and remote database/network connectivity, but it declares no explicit tool scope or permission boundaries. In an agent environment, missing scope declarations can cause over-privileged execution or make reviewers unable to verify whether database, filesystem, and network access are intentionally constrained.

Content

No source excerpt is available for this finding.

External Model or Provider Selection

Medium
Category
Excessive Agency
Confidence
90% confidence
Finding

Selecting an external model/provider introduces data-governance and privacy risk because prompts may include schema metadata, connection details, or operational context sent to a third party. In a SQL assistant, even if row data is excluded, table names, column names, and query text can still reveal sensitive business structure or security-relevant information.

Content

Scanner excerpt · SKILL.md (reported line 8)May include surrounding context.

md
author: Golden Bean (coder)
category: Developer Tools
description: Convert natural language to SQL, explore database schemas, execute queries safely, and get optimization suggestions.
model: deepseek/deepseek-v4-flash
input_schema: schemas/input.schema.json
output_schema: schemas/output.schema.json
---

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The options table sets --language default to zh-CN, which means the skill will prefer a specific locale unless the user overrides it. This is a natural-language policy concern because the file does not indicate that the user is first asked to choose a language or explicitly opt in to Chinese as the default.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The schema restricts the language to zh-CN or en-US and defaults to zh-CN, which imposes a specific locale when the user does not explicitly choose one. This is a natural-language policy concern because the file does not indicate user opt-in or a justified region-specific requirement.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The docstring claims passwords are stored with basic obfuscation, but the implementation writes the full connection config, including plaintext passwords, directly to a JSON file under the user's home directory. This creates a credential exposure risk if local files are read by other local users, malware, backups, or support tooling, and the misleading documentation may cause users to underestimate that risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The function silently persists connection configurations, including credentials, to disk with no warning or explicit opt-in at the point of storage. In a tool designed to handle database access, this is more dangerous because users may paste production credentials expecting ephemeral use, while the skill retains them locally, increasing the chance of unintended credential retention and later compromise.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

This code file performs live database operations in the _explain_* helpers by connecting to SQLite, PostgreSQL, and MySQL and executing EXPLAIN statements, but these functions have no warning, confirmation, or user-facing log indicating that a real database will be contacted. While EXPLAIN is typically read-only, it still touches external systems and may have operational impact, so the absence of disclosure meets the missing-warning criterion for code files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The prompt embeds raw user query text, generated SQL, column names, sample row values, and numeric summaries directly into content intended for an LLM. In a SQL assistant context, this can expose sensitive database contents or internal query details to a downstream model or logging pipeline without minimization, redaction, or explicit disclosure, increasing data leakage risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The function explicitly serializes database schema metadata into prompt text for an LLM, including table names, column names, defaults, comments, foreign keys, and indexes. Even without row data, schema details can disclose sensitive business structure, PII-related fields, secrets-adjacent columns, or internal system design, and in this skill context the output is specifically intended to leave the database boundary and be sent to a model.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Unknown SQL statements are allowed by default with only a server-side warning log, not a blocking control or clear user-facing rejection. In this skill's context, where LLM-generated SQL may vary by dialect and include uncommon commands, this increases the chance that unsafe statements slip past the supposed read-only guardrail.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code constructs a prompt from the raw natural-language query and full schema text for transmission to an external LLM, but provides no consent flow, redaction, minimization, or user-visible warning that potentially sensitive schema and query contents may leave the local trust boundary. In a SQL assistant, schema metadata can reveal table names, business structure, and sensitive domains, while user queries may contain confidential identifiers or investigative context, making this a real data-exposure risk even if no direct code execution occurs.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.