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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/sql_easy.py tables

Description-Behavior Mismatch

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.