Back to skill

Security audit

SQL to BI Builder

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but the dashboards it generates have review-worthy browser security risks before use with untrusted SQL or real BI data.

Install only if you are comfortable reviewing generated web assets before sharing or using them with untrusted SQL. Patch or avoid the generated service frontend until HTML injection is fixed, pin or vendor ECharts with integrity checking, restrict CORS before connecting real data, and review any commands that modify ~/.zshrc or install Python packages.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_service_bundle.py:500
Finding
Stored DOM-Based Cross-Site Scripting Through Dashboard Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/parse_sql_md.py:34-36, 79`; `scripts/build_dashboard_spec.py:123`; `scripts/generate_service_bundle.py:500-506` **Vulnerability Type**: Stored DOM-based cross-site scripting **Risk Level**: High ### Vulnerable Code Untrusted Markdown headings are accepted as query titles: ```python heading_match = HEADING_RE.match(line) if not in_sql and heading_match: level = len(heading_match.group(1)) title = heading_match.group(2).strip() section_stack = section_stack[: level - 1] section_stack.append(title) continue ``` The untrusted title is stored in the query catalog: ```python query = { "id": qid, "index": idx, "title": block_meta.get("title", section_title), "section": section_title, "datasource": block_meta.get("datasource", ""), "refresh": block_meta.get("refresh", ""), "chart_hint": block_meta.get("chart", "auto").strip().lower(), "filters": filters, "sql": sql_text, } ``` It is propagated into the dashboard specification without validation: ```python widget = { "id": f"widget_{qid}", "query_id": qid, "title": q.get("title") or qid, "chart": chart, ``` The generated service frontend inserts the title into an HTML parsing sink: ```javascript node.innerHTML = ` <div class="widget-head"> <div class="widget-title">${w.title || w.query_id}</div> <div class="widget-type">${w.chart || 'table'}</div> </div> <div class="widget-body"></div> `; ``` ### Technical Analysis The SQL Markdown file is an external input. Its headings and `title` metadata can contain arbitrary HTML because the parser does not validate or encode these fields. The resulting value passes through `query_catalog.json` and `dashboard.json`, is returned by the generated backend, and is interpolated directly into `Element.innerHTML`. Unlike `textContent`, `innerHTML` invokes the browser's HTML parser. Event-handler attributes or similar active marku ...[truncated 1724 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use HTML-string interpolation for artifact-controlled values. 2. Construct the widget header using DOM APIs and assign all dynamic text with `textContent`: ```javascript const head = document.createElement('div'); head.className = 'widget-head'; const title = document.createElement('div'); title.className = 'widget-title'; title.textContent = String(w.title || w.query_id || ''); const type = document.createElement('div'); type.className = 'widget-type'; type.textContent = String(w.chart || 'table'); head.appendChild(title); head.appendChild(type); node.appendChild(head); const body = document.createElement('div'); body.className = 'widget-body'; node.appendChild(body); ``` 3. Apply context-appropriate encoding to every dynamic value before using any unavoidable HTML sink. 4. Validate metadata fields at ingestion. Reject control characters and enforce reasonable length limits, but do not treat validation as a substitute for safe output handling. 5. Add a restrictive Content Security Policy that blocks inline scripts and event handlers, for example: ```http Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none' ``` 6. Add automated regression tests using payloads such as `<img src=x onerror=alert(1)>`, `<svg onload=alert(1)>`, and HTML-closing sequences in every Markdown metadata field. ]]>

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/generate_service_bundle.py:187
Finding
Generated Dashboards Retrieve and Execute Unpinned Remote JavaScript<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_service_bundle.py:160-162, 187`; `scripts/generate_ui_scaffold.py:17-19, 102` **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: Medium ### Vulnerable Code The generated service frontend loads remote fonts and a floating ECharts major version: ```html <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet"> ``` ```html <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> ``` The standalone scaffold generator emits the same executable dependency: ```html <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> ``` ### Technical Analysis The generated pages retrieve JavaScript from an external CDN every time the dashboard loads. The dependency uses the floating `@5` major-version selector rather than an exact reviewed release. Therefore, the effective JavaScript payload may change after the Skill itself has been audited. No Subresource Integrity hash is supplied, and no Content Security Policy restricts executable sources. The browser consequently trusts and executes whatever content the remote URL returns. This creates a remote code-execution channel within the browser context. It can be exploited if: - The CDN or its delivery infrastructure is compromised. - The upstream package or publishing account is compromised. - A future release resolving under `@5` introduces malicious or unsafe behavior. - Network or DNS controls in the deployment environment redirect the dependency request. The Google Fonts requests are not executable JavaScript, but they also introduce external availability and privacy dependencies by disclosing client request metadata. ### Attack Path 1. A user ge ...[truncated 1015 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Vendor the reviewed ECharts distribution into the generated frontend and serve it from the same origin. 2. If a CDN is necessary, pin an exact version rather than a floating major version: ```html <script src="https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js" integrity="sha384-REPLACE_WITH_VERIFIED_HASH" crossorigin="anonymous"></script> ``` 3. Generate and verify the integrity hash from the exact approved artifact. Update the version and hash only through a reviewed dependency-update process. 4. Add a Content Security Policy that limits scripts to `'self'` and, only if required, the exact approved CDN. 5. Consider vendoring fonts or using system fonts to eliminate unnecessary outbound requests and availability dependencies. 6. Maintain a dependency inventory and periodically scan the exact vendored or pinned versions for known vulnerabilities. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/generate_service_bundle.py:24
Finding
Generated Local Backend Uses Overly Permissive CORS Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_service_bundle.py:24-31` **Vulnerability Type**: Permissive cross-origin resource sharing and missing API access controls **Risk Level**: Low ### Vulnerable Code ```python app = FastAPI(title="SQL2BI Backend", version="0.1.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ``` ### Technical Analysis The generated FastAPI application allows all origins, HTTP methods, and request headers while also enabling credential support. The API does not implement authentication or authorization. Although the service binds to `127.0.0.1`, browser-delivered attacks can still target local services from an attacker-controlled page. A permissive CORS policy removes an important browser isolation boundary and allows unrelated web origins to interact with the generated API when browser private-network protections permit the request. Combining wildcard origins with credentials is also an unsafe and internally inconsistent policy. Credentialed CORS should only be enabled for explicitly trusted origins. ### Attack Path 1. The user starts the generated backend on `127.0.0.1:8000`. 2. While the backend remains active, the user visits an attacker-controlled website. 3. JavaScript on that website sends requests to the local API, such as: - `/api/dashboard` - `/api/filters` - `/api/query/{query_id}/data` 4. The generated backend accepts cross-origin requests because of its permissive CORS policy. 5. Where browser private-network and CORS checks allow it, the attacker-controlled page reads API responses and can invoke exposed routes. ### Impact Assessment In the current implementation, the exposed information consists primarily of generated dashboard metadata, inferred query semantics, filters, and synthetic query results. The attacker may enumerate dashboard structure and interact with all currently ...[truncated 576 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict allowed origins to the generated frontend: ```python app.add_middleware( CORSMiddleware, allow_origins=["http://127.0.0.1:5173"], allow_credentials=False, allow_methods=["GET"], allow_headers=["Accept", "Content-Type"], ) ``` 2. Include `http://localhost:5173` only if it is intentionally supported and tested. 3. Keep `allow_credentials=False` unless a documented authentication design explicitly requires credentialed cross-origin requests. 4. If authentication is added, use explicit trusted origins and add CSRF protections where cookies are involved. 5. Add authentication and endpoint-level authorization before connecting the service to real databases or exposing sensitive BI data. 6. Validate the `Origin` header for sensitive endpoints and reject unexpected origins. 7. Preserve loopback-only binding by default and clearly warn users before allowing non-loopback network exposure. 8. Add automated tests confirming that untrusted origins do not receive permissive CORS response headers. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill includes virtualenv creation, dependency installation, and command-line environment handling despite being presented as a content-transformation utility. Installing dependencies and modifying the runtime environment materially expand risk because they can execute package install hooks, alter local state, and introduce supply-chain exposure not implied by the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill includes virtualenv creation, dependency installation, and command-line environment handling despite being presented as a content-transformation utility. Installing dependencies and modifying the runtime environment materially expand risk because they can execute package install hooks, alter local state, and introduce supply-chain exposure not implied by the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill includes virtualenv creation, dependency installation, and command-line environment handling despite being presented as a content-transformation utility. Installing dependencies and modifying the runtime environment materially expand risk because they can execute package install hooks, alter local state, and introduce supply-chain exposure not implied by the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill includes virtualenv creation, dependency installation, and command-line environment handling despite being presented as a content-transformation utility. Installing dependencies and modifying the runtime environment materially expand risk because they can execute package install hooks, alter local state, and introduce supply-chain exposure not implied by the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill includes virtualenv creation, dependency installation, and command-line environment handling despite being presented as a content-transformation utility. Installing dependencies and modifying the runtime environment materially expand risk because they can execute package install hooks, alter local state, and introduce supply-chain exposure not implied by the declared purpose.

Ae1

High
Category
analysis-evasion
Content
python scripts/generate_ui_scaffold.py --dashboard /abs/path/out/dashboard.json --out /abs/path/out/ui
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises and instructs shell execution, file reads/writes, and service startup, but it does not declare any explicit tool scope or permission boundaries. In an agent setting, this increases the chance that the skill can invoke broader capabilities than a reviewer or orchestrator expects, which weakens least-privilege protections and makes misuse harder to contain.

Session Persistence

Medium
Category
Rogue Agent
Content
If you are on Intel Mac, the path may be `/usr/local/opt/python@3.11/bin`.

## Create Skill Venv
After `python3.11` is ready:
```bash
cd /Users/lyg/software/sql2bi/skills/sql-to-bi-builder
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The generated backend startup script creates a virtual environment, installs packages, and executes a shell script. Although this is consistent with a startup script's purpose, this generator file does not clearly warn users that running the produced scripts will modify the environment and execute commands.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code performs file creation and permission changes in the target output directory, including marking generated scripts executable. While the module docstring states it generates services, there is no user-facing warning, confirmation, or inline disclosure near the write/chmod operations that these filesystem changes will occur.

Tainted flow: 'result' from pathlib.Path.read_text (line 764, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
}

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")

    print(f"Inferred semantics for {len(semantic_queries)} queries -> {out_path}")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'result' from pathlib.Path.read_text (line 118, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
result = build_output(queries, in_path)

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")

    print(f"Parsed {len(queries)} SQL blocks -> {out_path}")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_step(cmd: list[str]) -> None:
    print("[RUN]", " ".join(cmd))
    subprocess.run(cmd, check=True)


def main() -> None:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The manifest describes converting SQL markdown into a BI dashboard specification and UI scaffold, but this script also offers an optional `--with-services` mode to generate a backend/frontend service bundle. Generating service code is broader than query parsing, dashboard spec creation, and UI scaffolding as stated in the skill description.

Context-Inappropriate Capability

Medium
Confidence
82% confidence
Finding
The skill's stated purpose is to build analytics dashboards, chart pages, and BI interfaces from SQL statements. Creating a `services` bundle introduces an additional capability—application/service generation—that is not an obvious requirement of producing a dashboard specification and UI scaffold.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This markdown file instructs the user to append exports and initialization commands to `~/.zshrc` and immediately source the file. That changes persistent shell configuration, but the document does not explicitly warn the user that it will modify their login environment or suggest reviewing existing config first.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The Homebrew option appends a new PATH export to `~/.zshrc` and reloads the shell, which affects future terminal sessions. The instructions do not disclose that this is a persistent configuration change or caution the user to verify the path for their system.

Static analysis

No suspicious patterns detected.