Back to skill

Security audit

postgresql-skill

Security checks for vulnerabilities and agentic risk

Overview

This PostgreSQL skill is purpose-aligned, but it gives broad database read/write power and has under-disclosed data exposure and weak SQL safety controls.

Install only with a dedicated least-privilege PostgreSQL role. Avoid production databases unless you are comfortable with an agent seeing query results and sample rows. Prefer environment-injected secrets over config.yaml, do not commit credentials, and require explicit human review before any INSERT, UPDATE, or DELETE. Treat the advertised SQL safety checks as incomplete.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pgsql_skill.py:162
Finding

SQL Safety Validation Can Be Bypassed with Multiple Statements and Comment Tokens

Content
View full analysis
Optional[str]: """Validate SQL for safety""" import re upper = sql.strip().upper() first_word = upper.split()[0] if upper.split() else "" # Forbidden operations for forbidden in ("DROP", "TRUNCATE", "ALTER"): if re.match(rf"^{forbidden}\b", upper): return f"Forbidden operation: {forbidden}" # Only allow SELECT/INSERT/UPDATE/DELETE if first_word not in ("SELECT", "INSERT", "UPDATE", "DELETE"): return f"Unsupported operation: {first_word} (only SELECT/INSERT/UPDATE/DELETE allowed)" # INSERT: single row only if first_word == "INSERT": values_count = len(re.findall(r"\bVALUES\b", upper)) if values_count != 1: return "INSERT must contain exactly one VALUES clause" # Check for multiple value groups values_pos = upper.index("VALUES") after_values = upper[values_pos + 6:] paren_groups = re.findall(r"\([^)]+\)", after_values) if len(paren_groups) > 1: return "INSERT allows only single row insertion" # UPDATE/DELETE: must have WHERE if first_word in ("UPDATE", "DELETE"): if not re.search(r"\bWHERE\b", upper): return f"{first_word} must include WHERE clause to prevent full table operation" return None ``` ### Technical Analysis The validator treats untrusted SQL as plain text rather than parsing its statement structure. It only checks the first keyword for forbidden operations and does not establish that the input contains exactly one statement. The accepted string is subsequently passed directly to `cursor.execute()`. Consequently, a str ...[truncated 2068 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pgsql_skill.py:139
Finding

Database-Controlled Table Names Are Interpolated into SQL Without Identifier Escaping

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/pgsql_skill.py:117
Finding

Schema Summary Exposes Sample Rows from Every Public Table

Content
View full analysis
str: """Get full schema summary with sample data""" conn = self._ensure_connected() cur = conn.cursor() # Get all tables cur.execute(""" SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name """) tables = [row[0] for row in cur.fetchall()] lines = ["数据库表结构:", ""] for table in tables: # Get columns cur.execute(""" SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = %s ORDER BY ordinal_position """, (table,)) columns = cur.fetchall() lines.append(f"表: {table}") for col_name, data_type, nullable, default in columns: null_str = "NULL" if nullable == "YES" else "NOT NULL" def_str = f" DEFAULT {default}" if default else "" lines.append(f" - {col_name}: {data_type} {null_str}{def_str}") # Get row count cur.execute(f'SELECT count(*) FROM "{table}"') count = cur.fetchone()[0] lines.append(f" (约 {count} 行)") # Get sample data cur.execute(f'SELECT * FROM "{table}" LIMIT 3') samples = cur.fetchall() if samples: lines.append(" 样例数据:") col_names = [desc[0] for desc in cur.description] for row in samples: row_dict = dict(zip(col_names, row)) sample_str = ", ".join([ f'"{k}": "{self._serialize_value(v)}"' for k, v in row_dict.items() ]) ...[truncated 2011 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
config.example.yaml:3
Finding

Documentation Encourages Plaintext Storage of Database Credentials

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill documents access to environment variables for database credentials and reliance on reading local configuration files, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an authorization gap where an agent may use broader file or environment access than a reviewer expects, increasing the chance of credential exposure or unintended local file reads during execution.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This YAML file's natural-language content is entirely in Chinese, including the title and inline guidance comments. For a general example configuration, that imposes a specific language/locale on users without opt-in or any documented reason that the skill is region-specific.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill description says it executes PostgreSQL operations and mentions SQL queries, but it does not clearly disclose that arbitrary data-modifying statements (INSERT, UPDATE, DELETE) are supported. This creates a capability-transparency problem: users or higher-level agents may invoke the skill assuming read-only behavior, increasing the risk of unintended destructive or integrity-impacting actions.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The schema-summary feature does more than summarize schema: it queries row counts and extracts live sample rows from every public table. Because this behavior is not declared in the skill description, callers may unknowingly expose sensitive production data during what appears to be a metadata-only inspection operation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The schema summary automatically selects and prints up to three rows from every table in the public schema without warning, confirmation, masking, or sensitivity checks. This can leak credentials, personal data, tokens, financial records, or other confidential content simply by invoking a discovery-oriented command, making the feature especially dangerous in production environments.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The schema summary strings are fixed in Chinese (for example, table structure and sample data labels), which imposes a specific language on users. The file does not offer language selection or explain a justified region-specific constraint, so this is a natural-language locale policy issue.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

This Python file reads and sets sensitive environment variables such as DB_USER and DB_PASSWORD as part of the tests. Although this is test code, there is no user-facing warning, logging, or explanatory comment specifically disclosing that credentials are being accessed and manipulated.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.