Back to skill

Security audit

RAG 评估集构建器 (ragas + IR 指标)

Security checks for vulnerabilities and agentic risk

Overview

This RAG evaluation skill is mostly coherent, but its local command mode can turn evaluation queries into shell commands and its remote LLM data handling is under-warned.

Review before installing. Avoid make_runs.py --mode cmd unless the evalset and command template are fully trusted; prefer api or bm25 mode. Do not run this on proprietary or regulated corpora unless the configured OpenAI-compatible endpoint is approved for that data, because generation and scoring can send document chunks, queries, answers, and contexts to the provider. Keep API keys in environment variables only, as the skill instructs.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/make_runs.py:123
Finding

Shell Command Injection Through Evaluation Queries

Content
View full analysis

Vulnerability Details

File Location: scripts/make_runs.py:123-124
Vulnerability Type: OS command injection
Risk Level: High

Vulnerable code:

python
out = subprocess.run(a.command.replace("{query}", query), shell=True,
                     capture_output=True, text=True, timeout=a.timeout)

Technical Analysis

In cmd mode, query is read from an entry in evalset.jsonl, substituted directly into the user-supplied command template, and passed to subprocess.run with shell=True. Consequently, shell metacharacters in a query are interpreted as executable shell syntax rather than inert retrieval text.

Evaluation queries may be generated from imported document corpora through an LLM. A malicious corpus provider, compromised generated evaluation set, or independent producer able to modify evalset.jsonl can therefore influence the query inserted into the shell command. The documented practice of placing {query} inside quotes does not prevent injection because a crafted query can terminate the quoting context.

This crosses a trust boundary from evaluation data, which should be treated purely as text, into the command interpreter. The timeout only limits execution duration and does not prevent injected commands.

Attack Path

  1. An attacker controls or influences a document corpus used to generate the evaluation set, or directly supplies/modifies an evalset.jsonl file.
  2. A resulting query contains shell syntax designed to escape the command template's intended argument context.
  3. The user or agent invokes make_runs.py with --mode cmd and a command template containing {query}.
  4. Line 123 performs unescaped string replacement.
  5. shell=True causes the host shell to interpret the injected syntax.
  6. The attacker's command executes under the OS identity and permissions of the Skill runner.

Impact Assessment

Successful exploitation permits arbitrary command executio ...[truncated 510 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove shell=True and execute a structured argument vector with shell=False.
  • Parse or define the command template as discrete arguments, then replace {query} only within individual argument values.
  • Prefer passing the query to a fixed retrieval executable through standard input or a dedicated argument rather than interpolating it into command text.
  • Do not rely on quote wrapping or shell escaping as the primary defense.
  • Reject configurations that require shell operators, redirections, pipelines, or command substitution.
  • Add regression tests using queries containing quotes, semicolons, command substitutions, newlines, and other shell metacharacters, verifying that they remain literal data.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

声明描述的是一个“评估集构建器/双层评测集生成器”,并强调同一数据集既支持 IR 指标又支持 ragas 指标;但提供的代码仅是后处理评分工具,针对已有 response 和 evalset 跑 ragas LLM 指标。代码没有文档语料读取、chunk/gold chunk ID 生成、测试问题生成、评估集构建、或 IR 排序指标计算逻辑。虽然代码确实覆盖了声明中的一部分 ragas LLM 裁判指标,但缺失了声明中的核心主目的与关键能力,因此构成明显描述与行为不一致。

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documented cmd mode explicitly allows executing an arbitrary user-supplied command for every query, which materially expands the skill beyond RAG evalset generation into general code execution. In practice, this means anyone controlling the command template or query content can run arbitrary programs on the local machine during evaluation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The implementation uses subprocess.run(..., shell=True) on a command template containing {query}, so untrusted input is interpreted by the shell rather than treated as data. This permits arbitrary command execution, file access, exfiltration, or environment manipulation unrelated to the skill's stated purpose.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README is written entirely in Chinese and does not indicate that users may choose another language or locale. Under the policy, forcing a specific language without user opt-in is a natural-language policy concern unless the locale restriction is explicitly documented and justified.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README explicitly instructs users to configure external OpenAI-compatible endpoints and use LLM-based evaluation, but it does not clearly warn that document contents, queries, answers, and evaluation artifacts may be transmitted to third-party services. In a RAG evaluation workflow, those inputs often contain proprietary or sensitive corpus data, so the omission can lead to unintended data disclosure or compliance violations.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill instructs the agent to use shell, file I/O, environment variables, and network access, but it does not declare any tool scope or permission boundaries. This weakens least-privilege controls and can let a caller trigger broader capabilities than reviewers or policy systems expect, increasing the blast radius if the skill is misused or modified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest description is entirely in Chinese and frames the skill's usage conditions only in Chinese, which effectively imposes a language/locale constraint on users. The file does not offer a language choice or document that the skill is intentionally limited to a Chinese-only audience or region.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The file title and content are written entirely in Chinese, and the guidance at L38 explicitly says to use the target language to control persona prefix language while avoiding English contamination. This indicates a language preference policy baked into the skill documentation without any user choice or opt-in, which can violate language/locale neutrality requirements.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The module docstring and subsequent user-facing messages are entirely in Chinese, establishing a fixed language expectation for users. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified, which is not documented here.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script transmits corpus chunk text to external LLM and embedding endpoints via AsyncOpenAI/OpenAIEmbeddings, but provides no explicit consent gate, privacy warning, or redaction safeguard before potentially sensitive documents are uploaded. In a production evaluation-set builder, users may run this against proprietary or regulated corpora, making unintended data disclosure to third-party services a realistic risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The file’s user-facing description, diagnostics, and CLI help strings are written only in Chinese, which imposes a specific language/locale on users. Under the stated policy, language constraints should be opt-in or clearly justified as region-specific; this file provides no such choice or justification.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

In cmd mode, the script builds a shell command by directly substituting untrusted query text into a.command and executes it with shell=True. This creates a classic command injection sink: a crafted query containing shell metacharacters can execute arbitrary OS commands on the host running the evaluation job.

Content

Scanner excerpt · scripts/make_runs.py (reported line 123)May include surrounding context.

python
top = [h[a.id_field] for h in hits][:a.k]
            texts = [h.get(a.text_field, id2text.get(t, "")) for h, t in zip(hits, top)]
            return qid, top, texts[:a.k]
        out = subprocess.run(a.command.replace("{query}", query), shell=True,
                             capture_output=True, text=True, timeout=a.timeout)
        if out.returncode != 0:
            raise RuntimeError(f"cmd 退出码 {out.returncode}: {out.stderr[:200]}")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script offers a dangerous execution path that runs shell commands derived from query input but provides no explicit warning, confirmation, or guardrails to inform operators they are enabling arbitrary code execution. This increases the likelihood of accidental unsafe use, especially in CI or automated evaluation pipelines where query content may come from external datasets.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script sends queries, model responses, reference answers, and retrieved contexts to an external OpenAI-compatible API for scoring, but it does not prominently warn users that potentially sensitive data leaves the local environment. In RAG evaluation workflows, retrieved contexts often contain proprietary or regulated content, so silent transmission can cause unintended data disclosure and compliance violations.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/build_evalset.py (reported line 308)May include surrounding context.

python
manifest = {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "entry_mode": "external" if a.chunks_from else "internal",
        "params": {k: getattr(a, k) for k in
                   ("size", "chunk_chars", "overlap", "llm_model", "embed_model", "base_url",
                    "model_kwargs", "cache_dir", "seed", "workers", "transform_model", "kg_cache",
                    "query_language", "llm_context", "personas",

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

When --query-language is set, the script injects an instruction that all generated questions and answers must use that language. This is a locale-forcing behavior implemented via natural language, and the file does not document any policy justification or explicit opt-in constraints beyond the raw parameter itself.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The module docstring, error messages, and argument help text are presented exclusively in Chinese, which constitutes a language/locale constraint in natural-language content. The file does not offer an alternative language or indicate that the tool is intentionally region-specific.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/ragas_eval.py (reported line 89)May include surrounding context.

python
kwargs = {"llm": llm}
        if needs_emb:
            kwargs["embeddings"] = embeddings
        metrics[name] = getattr(mc, cls)(**kwargs)

    sem = asyncio.Semaphore(a.concurrency)
    results = {name: [] for name in chosen}

Static analysis

No suspicious patterns detected.