Back to skill

Security audit

Data Governance

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it connects to databases with sensitive credentials and has enough scoping and dependency weaknesses that users should review it carefully before installing.

Install only in a controlled environment, upgrade PyMySQL before use, and connect with a read-only database account limited to the specific schemas under review. Avoid passing database passwords in command-line connection strings, do not run the data-quality checker on highly sensitive tables unless the row-scope issue is fixed, and treat generated reports as potentially containing schema or field names that reveal sensitive business or personal-data structure.

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)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_metadata.py:167
Finding

Database Credentials Can Be Exposed Through Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_metadata.py, lines 167–179
Vulnerability Type: Credential exposure through process arguments
Risk Level: Medium

Vulnerable Code

python
parser.add_argument('--db', '--connection', dest='db',
                   help='数据库连接字符串')
parser.add_argument('--db-type', choices=['sqlite', 'mysql', 'postgresql'],
                   help='数据库类型(配合环境变量使用)')
parser.add_argument('--output', help='输出文件路径')
parser.add_argument('--format', choices=['json', 'markdown'], default='json',
                   help='输出格式')
args = parser.parse_args()

# 验证表名
if not validate_table_name(args.source):
    sys.exit(1)

conn = None
try:
    if args.db:
        conn = get_connection(args.db)

Technical Analysis

The script accepts a complete database connection URI through the --db or --connection command-line option. These URIs commonly contain plaintext usernames and passwords, such as:

text
mysql://username:password@database.example/app

Command-line arguments can be recorded in shell history, CI/CD logs, orchestration audit records, monitoring systems, and diagnostic output. On some systems, they may also be visible to other local users through process-inspection interfaces while the program is running.

This behavior conflicts with the Skill documentation, which instructs users not to place passwords directly on the command line and recommends environment variables. Database connectivity is necessary for metadata generation, but accepting credentials through process arguments is not necessary because the script already supports environment-based configuration.

Attack Path

  1. An operator runs the metadata script with a credential-bearing connection URI:
    bash
    python scripts/generate_metadata.py --source users \
      --db mysql://admin:secret@example.internal/production
    
  2. The shell records the command in ...[truncated 871 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the --db and --connection options and require environment variables or a protected credential provider.
  2. If connection URIs must remain supported, reject URIs containing usernames or passwords and permit only non-secret connection parameters.
  3. Add an interactive password prompt using getpass.getpass() when interactive authentication is required.
  4. Integrate with an operating-system keyring or secrets-management service for production use.
  5. Update usage examples so they never demonstrate credential-bearing command-line URIs.
  6. Require a least-privilege, read-only database account limited to the schemas needed for metadata inspection.
  7. Ensure exception and diagnostic messages redact usernames, passwords, and complete connection URIs.

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/data_quality_check.py:197
Finding

User-Specified Record Limit Is Ignored During Data Quality Analysis

Content
View full analysis

Vulnerability Details

File Location: scripts/data_quality_check.py, lines 113–119, 146–149, and 197–215
Vulnerability Type: Excessive collection of database records
Risk Level: Low

Vulnerable Code

python
def get_table_data(conn, table_name: str, limit: int = 10000) -> List[Dict]:
    """获取表数据(使用参数化查询)"""
    cursor = conn.cursor()

    try:
        # 使用参数化查询(虽然 LIMIT 不是用户输入,但这是好习惯)
        cursor.execute(f"SELECT * FROM {table_name} LIMIT ?", (limit,))
python
def check_data_quality(conn, table_name: str) -> Dict[str, Any]:
    """检查数据质量"""
    schema = get_table_schema(conn, table_name)
    data = get_table_data(conn, table_name)
python
parser.add_argument('--limit', type=int, default=10000, help='最大记录数')

args = parser.parse_args()

# 验证表名
if not validate_table_name(args.table):
    sys.exit(1)

# 获取连接 - 仅从环境变量
conn = None
try:
    conn = get_connection_from_env(args.db_type)
    if not conn:
        print("❌ 请设置环境变量: DB_HOST, DB_USER, DB_PASS, DB_NAME", file=sys.stderr)
        print("SQLite 需要: DB_PATH", file=sys.stderr)
        sys.exit(1)

    print(f"✅ 已连接到数据库")

    # 检查质量
    result = check_data_quality(conn, args.table)

Technical Analysis

The command-line parser exposes a --limit option, but args.limit is never passed to check_data_quality() or get_table_data(). Consequently, get_table_data() always uses its default limit of 10,000 records.

An operator may explicitly request a smaller scope, such as --limit 10, expecting only ten records to be accessed. The script nevertheless attempts to retrieve up to 10,000 complete rows using SELECT *. Because all columns are selected, the retrieved data may include passwords, tokens, personal information, financial data, health data, or other sensitive values even though quality calculations may not require retaining every field simultaneously.

The records remain local ...[truncated 1391 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pass the limit through the complete call chain:
    python
    def check_data_quality(conn, table_name: str, limit: int) -> Dict[str, Any]:
        schema = get_table_schema(conn, table_name)
        data = get_table_data(conn, table_name, limit)
    
    result = check_data_quality(conn, args.table, args.limit)
    
  2. Validate that the limit is positive and enforce a conservative upper bound.
  3. Replace SELECT * with queries that retrieve only the columns and aggregate values required for each quality check.
  4. Prefer database-side aggregate queries for null counts and duplicate detection so raw records do not need to be loaded into application memory.
  5. Document the exact amount and type of data each check accesses.
  6. Continue using a read-only database account restricted to the specific schemas and tables under review.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/lineage_analysis.py:100
Finding

Cyclic Lineage Relationships Cause Unbounded Recursive Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/lineage_analysis.py, lines 100–124
Vulnerability Type: Uncontrolled recursion and denial of service
Risk Level: Low

Vulnerable Code

python
def get_upstream(self, table: str, depth: int = -1) -> Set[str]:
    if depth == 0:
        return set()

    result = set()
    for parent in self.upstream.get(table, set()):
        result.add(parent)
        if depth != 1:
            result.update(self.get_upstream(parent, depth - 1))

    return result

def get_downstream(self, table: str, depth: int = -1) -> Set[str]:
    if depth == 0:
        return set()

    result = set()
    for child in self.downstream.get(table, set()):
        result.add(child)
        if depth != 1:
            result.update(self.get_downstream(child, depth - 1))

    return result

Technical Analysis

Both lineage traversal functions recurse without maintaining a visited-node set. A self-referential relationship or a cycle involving multiple tables therefore causes the traversal to revisit the same nodes repeatedly.

The default depth is -1, which is treated as unlimited. During recursion it becomes -2, -3, and so forth and never reaches the depth == 0 termination condition. The traversal eventually exceeds Python's recursion limit and raises RecursionError. Before termination, it can consume unnecessary CPU time and stack memory.

Cycles are legitimate in real database schemas, particularly where tables contain self-referencing foreign keys or mutually related records. Therefore, this condition does not require malformed input.

Attack Path

  1. A database schema contains a cyclic relationship, such as table employees referencing itself, or table a referencing b while b references a.
  2. The lineage builder adds the corresponding graph edges.
  3. The script calls get_full_lineage(), which invokes upstream and downstream traversa ...[truncated 763 chars]
Remediation
View remediation

Remediation Suggestions

  1. Maintain a visited-node set for each traversal and stop recursion when a node has already been visited.
  2. Prefer iterative breadth-first or depth-first traversal to avoid Python stack exhaustion.
  3. Enforce a safe maximum depth even when the user requests unlimited traversal.
  4. Validate --depth and reject unsupported negative values other than a documented sentinel.
  5. Add tests for self-references, two-node cycles, larger cycles, and deeply nested acyclic graphs.
  6. Report detected cycles in the output rather than silently revisiting them or terminating analysis.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Known Vulnerable Dependency: pymysql==1.1.0 — 2 advisory(ies): CVE-2024-36039 (PyMySQL SQL Injection vulnerability); CVE-2024-36039 (PyMySQL SQL Injection vulnerability)

Critical
Category
Supply Chain
Confidence
97% confidence
Finding

The requirements file pins PyMySQL to version 1.1.0, which is flagged with a known SQL injection vulnerability (CVE-2024-36039). In a data-governance skill, database access is a core function, so using a vulnerable DB connector materially increases the risk that attacker-controlled input could influence query handling and lead to unauthorized data access or manipulation.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The skill claims broad governance capabilities, but the documented behavior relies on CLI scripts, database connectivity, and metadata access that are not cleanly bounded in the declaration. This mismatch can cause users or orchestrators to trust the skill for benign reporting while it actually performs direct database interaction, increasing the risk of over-privileged execution against sensitive systems.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill documents use of environment variables, database connections, package installation, and scripts that can read from external databases, but it does not declare any explicit tool scope or permission boundaries. In an agent setting, missing scope metadata increases the chance the skill is invoked with broader file, network, or environment access than intended, which can expose credentials or sensitive metadata.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The manifest description and the substantive operating instructions are written in Chinese, with no indication that the skill can respond in the user's preferred language or that Chinese is a justified locale constraint. This creates a language policy issue because the skill appears to assume a fixed language without explicit user opt-in.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The activation guidance is broad enough to trigger on many general topics like data security, privacy, metadata, and governance without exclusion criteria. Over-broad routing can cause the skill to be selected in contexts involving sensitive datasets or compliance decisions, leading an agent to access databases or process regulated data when a narrower or safer skill should have been used.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This Python skill includes its primary natural-language description in Chinese and later emits user-facing status/error/report text in Chinese, but it does not provide any user opt-in or locale selection. Under the policy, forcing a specific language without user choice is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
78% confidence
Finding

This script loads DB_HOST, DB_PORT, DB_USER, DB_PASS, and DB_NAME from the process environment to establish external database connections. While schema inspection is aligned with compliance checking, reaching into ambient environment state for credentials is a broader credential-access capability that is not stated in the manifest description.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The file-level description and all user-facing messages are written only in Chinese, indicating the skill is designed to operate in a single language without offering a user choice. This matches the language/locale policy concern because the skill does not document that it is region-specific or provide any opt-in for the enforced locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file’s human-facing description is entirely in Chinese and does not offer any language or locale choice. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is clearly documented and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code file contains user-facing natural-language content such as the module description, CLI help text, and status/error messages in Chinese. Because the skill does not provide any language/locale opt-in or explain that it is intended only for a Chinese-speaking context, it violates the language-choice policy for natural-language behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest describes data governance tasks such as lineage tracing and metadata/compliance work, but this code additionally pulls DB_HOST, DB_PORT, DB_USER, DB_PASS, and DB_NAME from the process environment. Accessing environment-stored secrets is a sensitive capability that is not declared in the skill description and is not obviously required from the manifest alone.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script reads DB_HOST, DB_PORT, DB_USER, DB_PASS, and DB_NAME from environment variables and uses them to open live database connections. Although it prints a success message after connecting, there is no prior user-facing disclosure, comment, or docstring warning that sensitive credentials will be consumed from the environment.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The docstring shows example invocations using --db sqlite:///... and --db mysql://..., implying callers can provide a connection string directly. In the actual code, the parser only defines --table, --db-type, and --check-type, and the database connection is sourced from environment variables instead, so the documentation actively misstates how the script operates.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The doc block documents invocations like '--db sqlite:///data.db' and full MySQL/PostgreSQL connection URLs, implying direct connection-string input. In actual code, argparse defines no '--db' option and main() only connects via '--db-type' and environment variables, so the documentation actively contradicts the implemented behavior.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The docstring says the function provides usable example lineage data when no database is available. However, LineageGraph.init takes no table_name argument and add_lineage requires both source and target, so the implementation contradicts the stated intent and would fail instead of producing example data.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.