Back to skill

Security audit

SQL Guard Copilot

Security checks for vulnerabilities and agentic risk

Overview

The skill is a useful SQL helper, but its advertised read-only safety model is undermined by write-bypass options and under-disclosed external LLM data sharing.

Review this skill before installing. Use only read-only database credentials, avoid --allow-write especially with ask, keep --dry-run for generated SQL until manually reviewed, and do not set OPENAI_BASE_URL to an untrusted or plaintext endpoint. Treat schema names and user questions as potentially sensitive data that may be sent to the configured LLM service.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sql_easy.py:580
Finding
API Credential and Database Schema Disclosure Through an Unrestricted LLM Endpoint## Vulnerability Details **File Location**: `scripts/sql_easy.py`, lines 115-116 and 580-605 **Vulnerability Type**: Unrestricted transmission of sensitive information to a configurable network endpoint **Risk Level**: High ### Vulnerable Code ```python p_ask.add_argument("--api-key", default=os.getenv("OPENAI_API_KEY", ""), help="LLM API key.") p_ask.add_argument("--base-url", default=os.getenv("OPENAI_BASE_URL", "https://api.openai.com"), help="LLM API base URL.") ``` ```python def call_openai_chat(prompt: str, model: str, api_key: str, base_url: str) -> str: if not api_key: raise SqlEasyError( "Missing OPENAI_API_KEY. Set environment variable or pass --api-key for `ask` command." ) url = base_url.rstrip("/") + "/v1/chat/completions" payload = { "model": model, "temperature": 0, "messages": [ {"role": "system", "content": "Generate safe SQL in JSON output only."}, {"role": "user", "content": prompt}, ], } body = json.dumps(payload).encode("utf-8") req = urlrequest.Request( url=url, data=body, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, method="POST", ) try: with urlrequest.urlopen(req, timeout=60) as resp: resp_payload = json.loads(resp.read().decode("utf-8")) ``` The transmitted prompt is populated with database schema metadata and the user’s question: ```python schema_context = build_schema_context(client, max_tables=max_tables, max_columns=max_columns) if not schema_context.strip(): raise SqlEasyError("Schema discovery returned empty result; cannot generate SQL safely.") prompt = build_nl2sql_prompt(question=question, dialect=client.dialect, schema_context=schema_context) raw = call_openai_chat(prompt=prompt, model=model, a ...[truncated 2540 chars]
Remediation
## Remediation Suggestions 1. Require an `https://` URL and reject plaintext HTTP or unsupported schemes before constructing the request. 2. Allowlist trusted LLM hosts by default, such as the documented provider endpoint. 3. Do not send an `OPENAI_API_KEY` to a non-OpenAI host. Custom providers should use separate, provider-specific credentials. 4. Require explicit user confirmation before sending schema metadata to a custom endpoint. 5. Display the destination host and a concise description of the data being transmitted before the first request. 6. Document clearly in `SKILL.md` that `ask` transmits table names, column names/types, SQL dialect, and the user’s question to an external service. 7. Provide options to redact identifiers, select specific tables, or operate with a manually supplied minimal schema. 8. Consider certificate pinning or organization-controlled proxies in higher-assurance deployments. 9. Avoid passing API keys on the command line where they may be exposed through shell history or process listings; prefer protected environment variables or a credential manager.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sql_easy.py:951
Finding
Remote Model Output Can Be Executed With Database Write Privileges## Vulnerability Details **File Location**: `scripts/sql_easy.py`, lines 125 and 951-999 **Vulnerability Type**: Unsafe execution of untrusted model-generated SQL when the read-only guard is disabled **Risk Level**: High ### Vulnerable Code ```python p_ask.add_argument("--allow-write", action="store_true", help="Disable read-only guard (not recommended).") ``` ```python if args.command == "ask": question = args.q.strip() if not question: raise SqlEasyError("Question is empty.") sql_from_model, assumptions, prompt = generate_sql_from_question( client=client, question=question, model=args.model, api_key=args.api_key, base_url=args.base_url, max_tables=args.max_tables, max_columns=args.max_columns, ) sql = normalize_sql(sql_from_model) if not args.allow_write: ensure_safe_readonly(sql) sql = maybe_apply_limit(sql, args.limit) print("-- generated sql --") print(sql) if assumptions: print("\n-- assumptions --") for idx, item in enumerate(assumptions, start=1): print(f"{idx}. {item}") if args.show_prompt: print("\n-- llm prompt --") print(prompt) warnings = [] if args.no_lint else lint_sql(sql) if warnings: print("\n[sql_easy] lint warnings:", file=sys.stderr) print(render_table(warnings), file=sys.stderr) if args.strict_lint and warnings: raise SqlEasyError("Lint warnings found in strict mode. Fix SQL or disable --strict-lint.") audit_event["generated_sql"] = sql audit_event["lint_warnings"] = len(warnings) if assumptions: audit_event["assumptions"] = assumptions if args.dry_run: return 0 rows, elapsed_ms = timed_query(client, sql) ``` The execution sink runs SQL directly on the configured connection: ```python def timed_query ...[truncated 3127 chars]
Remediation
## Remediation Suggestions 1. Remove `--allow-write` from the `ask` command. Model-generated SQL should never be executed automatically with write capability. 2. If write generation is a required feature, return the proposed statement as dry-run output only and require a separate, explicit human approval process. 3. Use a dedicated database account that has technically enforced read-only permissions, rather than relying exclusively on SQL text filtering. 4. Separate read-only and write-capable DSNs, and prohibit the LLM execution path from accessing the write-capable credential. 5. Parse SQL into an abstract syntax tree and enforce an allowlist of read-only statement types. Regex and token checks should not be treated as a complete SQL security boundary. 6. Require transaction-based previews and rollback by default for any separately implemented write workflow. 7. Show the exact proposed SQL, affected objects, and estimated affected-row count before approval. 8. Restrict or sanitize schema identifiers before placing them in an LLM prompt, and delimit schema content as untrusted data. 9. Retain audit records for approvals while protecting generated SQL and error messages from disclosing sensitive literals. 10. Document that `--allow-write` must not be used in unattended automation if it cannot be removed for compatibility reasons.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/sql_easy.py tables
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The tool is presented as a safe read-only SQL helper, but the `query` command exposes `--allow-write`, which disables the only meaningful guard preventing modification statements. In practice, this lets any caller use the skill to execute destructive or state-changing SQL against connected databases, contradicting the advertised safety boundary and increasing the chance of accidental or unauthorized data modification.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The natural-language `ask` path also includes `--allow-write`, so LLM-generated SQL can bypass the read-only guard and then be executed directly. This is especially risky because it combines model generation uncertainty with write permissions, making destructive SQL more likely to be produced or socially engineered through prompts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill exposes capabilities that involve environment access, network use, and potential file output, but it does not declare any explicit tool scope or permission boundaries. In a skill that accepts natural-language input and can generate SQL, missing scope declarations increase the risk of overbroad execution, accidental credential use via environment variables, unintended network access to databases, and uncontrolled audit-log/file writes.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring and command descriptions frame the tool as 'Safe SQL helper' with read-only execution, yet the implementation intentionally includes bypasses that allow writes. This misleading trust signal can cause operators or downstream agents to treat the tool as low-risk and grant it access it should not have, increasing the chance of misuse.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The `ask` command transmits user questions and discovered database schema to an external LLM service without a just-in-time warning or consent step at execution time. Because schemas often contain sensitive identifiers, this can leak internal metadata to third parties in ways users may not expect from a SQL helper.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The `ask` feature sends discovered schema information and the user's natural-language question to an external LLM API, but this data-sharing behavior is not clearly disclosed in the skill description. Schema names, column names, and user questions can reveal sensitive internal structure or business context, creating confidentiality and compliance risk even if row data is not directly transmitted.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The outbound API call sends prompt content directly to a remote endpoint specified by `base_url`, with no execution-path disclosure to the user and no trust restrictions on the destination. This creates confidentiality risk from third-party transmission and expands the attack surface if the endpoint is changed to an untrusted host.

Missing User Warnings

Low
Confidence
88% confidence
Finding
Reading credentials from environment variables is a sensitive operation under this rubric when there is no visible disclosure. Here the skill silently sources the API key from `OPENAI_API_KEY`, and the surrounding code does not notify the user that an external credential will be used for remote API access.

Static analysis

No suspicious patterns detected.