Back to skill

Security audit

sql-linker

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed database CRUD skill, but it needs Review because its database access controls and credential handling are weaker than its security claims.

Install only for local development or tightly controlled databases. Use a least-privilege database account, keep read_only enabled until needed, avoid direct SQLLinker use with untrusted input, do not rely on the table allowlist as a hard security boundary, and prefer a proper secret manager over the generated dbpw_key/environment setup.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/service_layer/db_bridge.py:674
Finding

Table Allowlist Bypass Through Incomplete SQL Object Extraction

Content
View full analysis
str: m = re.search( r"(?:FROM|UPDATE|INSERT\s+INTO|INTO|DELETE\s+FROM)\s+(\w+)", sql, re.IGNORECASE ) ``` ### Technical Analysis The bridge attempts to enforce a table allowlist by extracting one table name from the supplied SQL statement. The regular expression returns only the first matching table reference. The complete SQL statement is subsequently passed to the database without validating all referenced tables. SQL may reference multiple objects through joins, subqueries, common table expressions, or set operations. For example, a query can begin with an allowed table and subsequently access a blocked table: ```sql SELECT permitted.id, restricted.secret FROM permitted JOIN restricted ON restricted.id = permitted.id ``` A similar bypass may be constructed with `UNION` or a nested subquery. Because `_extract_table()` identifies `permitted` as the first table, the bridge authorizes the statement even though it also accesses `restricted`. The regular expression is not a security-grade SQL parser and cannot reliably account for comments, quoted identifiers, dialect-specific syntax, common table expressions, or multiple statements. The authorization dec ...[truncated 1297 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/controller_layer/sql_linker.py:361
Finding

SQL Injection Through Unvalidated Identifiers and Raw WHERE Clauses

Content
View full analysis
int: if self.is_read_only(): print("只读模式禁止插入 / Read-only mode: insert blocked") return 0 columns = list(data.keys()) values = list(data.values()) placeholders = ", ".join(["%s"] * len(values)) sql = f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({placeholders})" ``` ```python def update(self, table: str, data: dict, where: str, where_params: tuple = None) -> int: if self.is_read_only(): print("只读模式禁止更新 / Read-only mode: update blocked") return 0 set_clause = ", ".join([f"{k} = %s" for k in data.keys()]) sql = f"UPDATE {table} SET {set_clause} WHERE {where}" params = list(data.values()) + (list(where_params) if where_params else []) ``` ```python def delete(self, table: str, where: str, where_params: tuple = None) -> int: if self.is_read_only(): print("只读模式禁止删除 / Read-only mode: delete blocked") return 0 sql = f"DELETE FROM {table} WHERE {where}" params = where_params if where_params else () ``` ### Technical Analysis Although record values are parameterized, SQL identifiers and predicate structure are not. The following caller-controlled elements are inserted directly into SQL text: - `table` - Keys from `data`, used as column names - The complete `where` expression Parameterized placeholders protect only the associated values. They do not sanitize table names, column names, operators, keywords, comments, or arbitrary SQL included in `where`. The service-layer bridge applies some table and field restrictions for normal callers, but `SQLLinker` remains directly callable and does not enforce equivalent validation. In addition, raw `where` expressions remain str ...[truncated 1554 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/controller_layer/sql_linker.py:533
Finding

Database Credential Exposure Through Weak Custom Encryption and Error Disclosure

Content
View full analysis
str: import secrets key_padded = (key * 4)[:24] key_bytes = key_padded.encode('utf-8') password_bytes = password.encode('utf-8') # Generate random IV iv = secrets.token_bytes(16) # XOR encryption encrypted = bytearray() for i, byte in enumerate(password_bytes): key_byte = key_bytes[i % len(key_bytes)] iv_byte = iv[i % len(iv)] encrypted.append(byte ^ key_byte ^ iv_byte) # HMAC covers IV + encrypted data hmac_tag = hmac.new( key_bytes, iv + bytes(encrypted), hashlib.sha256 ).digest()[:16] combined = iv + bytes(encrypted) + hmac_tag return base64.b64encode(combined).decode('utf-8') ``` ```python def _decrypt_password(self, encrypted_pw: str, key: str) -> str: try: key_padded = (key * 4)[:24] key_bytes = key_padded.encode('utf-8') combined = base64.b64decode(encrypted_pw.encode('utf-8')) if len(combined) < 32: raise ValueError("Invalid encrypted data format") iv = combined[:16] encrypted = combined[16:-16] stored_hmac = combined[-16:] expected_hmac = hmac.new( key_bytes, iv + encrypted, hashlib.sha256 ).digest()[:16] if not hmac.compare_digest(stored_hmac, expected_hmac): raise ValueError( "HMAC verification failed - wrong dbpw_key or corrupted data" ) decrypted = bytearray() for i, byte in enumerate(encrypted): key_byte = key_bytes[i % len(key_bytes)] iv_byte = iv[i % len(iv)] ...[truncated 2906 chars]
Remediation
View remediation
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Self-Modification

High
Category
Rogue Agent
Confidence
90% confidence
Finding

The skill explicitly supports creating configuration files and setup scripts in the user's home directory on first use. Even if described as idempotent and not overwriting existing files, this is still agent-initiated filesystem modification and can persist sensitive configuration or create execution surfaces such as shell/profile changes via generated setup scripts.

Content

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

md
### English

On first use or when config files are missing, the system automatically generates default templates (idempotent, will not overwrite existing files):

> ⚠️ **Bootstrap auto-creates config**: On first use, config files (`config.yaml`, `audit_config.json`, etc.) are auto-created in `~/.sql_linker/`. Use `set_env.ps1` to set password to Windows env. `bootstrap()` is idempotent and won't overwrite existing files, but will create missing ones.

Self-Modification

High
Category
Rogue Agent
Confidence
90% confidence
Finding

This line reinforces that the skill auto-creates files under ~/.sql_linker/, which introduces persistent host modification from a skill invocation. In the context of an agent skill, persistence in the home directory can be abused to plant configuration, influence later runs, or leave behind sensitive material even when the immediate task did not require durable changes.

Content

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

On first use or when config files are missing, the system automatically generates default templates (idempotent, will not overwrite existing files):

⚠️ Bootstrap auto-creates config: On first use, config files (config.yaml, audit_config.json, etc.) are auto-created in ~/.sql_linker/. Use set_env.ps1 to set password to Windows env. bootstrap() is idempotent and won't overwrite existing files, but will create missing ones.

python
from db_bridge import DBBridge

Credential Access

High
Category
Privilege Escalation
Confidence
86% confidence
Finding

The bootstrap logic generates helper scripts that implement home-grown password 'encryption' using a 6-character key stored in config.yaml, then persist the result in user environment or shell startup files. Because the key is low-entropy and stored alongside the configuration, anyone with access to the workspace and user profile can feasibly recover the database password, defeating the protection and exposing credentials for database compromise.

Content

Scanner excerpt · scripts/service_layer/db_bridge.py (reported line 20)May include surrounding context.

python
Security Changes v1.1.1:
- bootstrap() adds dry_run parameter to preview files without writing
- .env placeholder comment clarifies it's a placeholder, not a real credential
- _load_linker prints connection info (host/database, masked) on init
"""

Session Persistence

Medium
Category
Rogue Agent
Confidence
95% confidence
Finding

The skill documents that set_env.sh modifies ~/.bashrc or ~/.zshrc to persist environment variables, which is a classic persistence mechanism. Because the same skill also discusses storing encrypted database credentials and local decryption keys within reachable trust boundaries, persistent shell modification increases the chance of long-lived secret exposure or future unintended execution context changes.

Content

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

md
⚠️ **Linux/macOS Security Warning**: `set_env.sh` will modify `~/.bashrc` or `~/.zshrc` to persist the environment variable. Review the script content before running to understand the changes.

For full manual control, create these files before invoking the skill. `bootstrap()` is idempotent but prints a safety warning before creating files.

***

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

Automatic creation of durable config under ~/.sql_linker/ constitutes session persistence because state survives beyond the current run and influences future behavior. In this skill's context, the persisted state includes database connection and credential-related material, making the persistence more security-relevant than ordinary harmless caching.

Content

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

md
### English

On first use or when config files are missing, the system automatically generates default templates (idempotent, will not overwrite existing files):

> ⚠️ **Bootstrap auto-creates config**: On first use, config files (`config.yaml`, `audit_config.json`, etc.) are auto-created in `~/.sql_linker/`. Use `set_env.ps1` to set password to Windows env. `bootstrap()` is idempotent and won't overwrite existing files, but will create missing ones.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The docstrings and inline guidance for explicit_credential_approval() and _check_credential_approval() are written primarily in Chinese, while the rest of the file is largely English. This imposes a language choice on users and maintainers without offering an opt-in, fallback, or documented region-specific justification.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The _resolve_password docstring states that password_env values are decrypted with dbpw_key, implying encrypted-at-rest handling for env-provided secrets. However, when password_env is set but dbpw_key is absent, the implementation returns the environment variable value directly as plaintext for backward compatibility. This is an active contradiction in the inline documentation about how the feature behaves.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The document is consistently structured as mandatory Chinese and English sections throughout the skill, which can imply a fixed language/locale presentation rather than adapting to user preference. Under the policy rule, forcing a specific language or locale without opt-in is a natural-language policy concern unless the skill explicitly offers language choice or justifies the constraint.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
50% confidence
Finding

The docstring asserts a specific security-relevant behavior change: 'init no longer triggers any network I/O'. In this file, DBBridge.init still calls _load_linker() during construction, so the documentation guarantees depend on behavior in another module rather than being enforced here; that makes the claim stronger than what this code itself proves.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The bootstrap docstring is presented primarily in Chinese for key usage and safety instructions, while other parts of the file are in English. This imposes a language choice on users/readers without any opt-in or indication that the skill is intentionally region-specific.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.