Back to skill

Security audit

rag-ingest

Security checks for vulnerabilities and agentic risk

Overview

The skill performs the intended vector ingestion, but its defaults can send document text and an OpenAI API key to a non-OpenAI embedding service without clear warning.

Review before installing. Use only dedicated, low-privilege embedding keys, explicitly set both EMBED_BASE_URL and the matching API key, avoid running it in environments containing unrelated OPENAI_API_KEY values, and do not ingest confidential data unless you accept sending chunks to the configured embedding service and storing them in Qdrant.

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/ingest.mjs:29
Finding
OpenAI API Credential Disclosed to an Unrelated Default Embedding Provider<![CDATA[ ## Vulnerability Details **File Location**: `scripts/ingest.mjs`, lines 29–35 and 99–109 **Vulnerability Type**: Credential destination confusion and sensitive-data disclosure **Risk Level**: High ### Vulnerable Code ```js const EMBED_BASE_URL = process.env.EMBED_BASE_URL || "https://api.vectorengine.ai/v1"; const EMBED_API_KEY = process.env.VECTORENGINE_API_KEY || process.env.EMBED_API_KEY || process.env.OPENAI_API_KEY; const EMBEDDING_MODEL = process.env.RAG_INGEST_EMBED_MODEL || process.env.OPENAI_EMBEDDING_MODEL || "text-embedding-3-large"; ``` ```js async function embed(texts) { const resp = await fetch(`${EMBED_BASE_URL}/embeddings`, { method: "POST", headers: { Authorization: `Bearer ${EMBED_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: EMBEDDING_MODEL, input: texts }), }); if (!resp.ok) { const t = await resp.text(); throw new Error(`Embedding 失败: ${resp.status} ${t}`); } const data = await resp.json(); return data.data.map((d) => d.embedding); } ``` ### Technical Analysis The embedding service URL and API credential are selected independently. Unless `EMBED_BASE_URL` is explicitly configured, the script sends requests to `https://api.vectorengine.ai/v1`. However, if neither `VECTORENGINE_API_KEY` nor `EMBED_API_KEY` exists, it silently uses `OPENAI_API_KEY`. Consequently, a credential named and ordinarily scoped for OpenAI is placed in an HTTP `Authorization` header and transmitted to the unrelated `api.vectorengine.ai` host. The remote host necessarily receives the bearer credential before it can accept or reject the request. The request body also contains every generated document chunk in the `input` property. Sending document content to an embedding provider is necessary for remote embedding, but using an unrelated default endpoint and silently forwarding another provider's credential is not necessary. The behavior is not fully disclosed ...[truncated 1989 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `OPENAI_API_KEY` fallback when the default endpoint is not an OpenAI-controlled endpoint: ```js const EMBED_API_KEY = process.env.VECTORENGINE_API_KEY || process.env.EMBED_API_KEY; ``` 2. Prefer an explicit, provider-bound configuration. For example, require both `EMBED_BASE_URL` and `EMBED_API_KEY`, and terminate before processing content if either is absent. 3. If multiple providers must be supported, select each endpoint and credential as an inseparable pair: ```js const provider = process.env.EMBED_PROVIDER; const providers = { openai: { baseUrl: "https://api.openai.com/v1", apiKey: process.env.OPENAI_API_KEY, }, vectorengine: { baseUrl: "https://api.vectorengine.ai/v1", apiKey: process.env.VECTORENGINE_API_KEY, }, }; const config = providers[provider]; if (!config?.apiKey) { throw new Error("A valid embedding provider and matching API key are required"); } ``` 4. Reject ambiguous configurations rather than silently combining an endpoint from one provider with a credential from another. 5. Apply an HTTPS destination allowlist and validate the parsed URL before transmitting credentials or document content. Reject plaintext HTTP embedding endpoints and unexpected hostnames. 6. Update `SKILL.md` to disclose every external service contacted, the data sent to it, and the exact required environment variables. Users should be warned that document chunks leave the local system when a remote embedding service is configured. 7. Use dedicated, least-privilege API credentials with restricted quotas and separate credentials per environment. Do not reuse general-purpose provider keys. 8. Rotate any `OPENAI_API_KEY` that may already have been used while the default `api.vectorengine.ai` endpoint was active, and review relevant provider usage and billing logs for unauthorized activity. 9. Consider adding an explicit confir ...[truncated 130 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares required environment variables and, by its purpose, performs networked writes to Qdrant and an embedding service, but it does not declare an explicit tool/permission scope. This weakens reviewability and consent boundaries because an agent or operator cannot easily see from the manifest that the skill exfiltrates provided content to external services and uses network capabilities.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The description says the skill writes processed text into Qdrant and requires an embedding API key, but it does not clearly warn users that supplied content will be transmitted to external services and stored in a vector database. This creates a real risk of unintentional disclosure of sensitive, proprietary, or regulated data because users may provide content without understanding the storage and third-party processing implications.

External Transmission

Medium
Category
Data Exfiltration
Content
const QDRANT_URL = process.env.QDRANT_URL || "http://127.0.0.1:6333";
const EMBED_BASE_URL =
  process.env.EMBED_BASE_URL || "https://api.vectorengine.ai/v1";
const EMBED_API_KEY =
  process.env.VECTORENGINE_API_KEY ||
  process.env.EMBED_API_KEY ||
Confidence
91% confidence
Finding
The code hardcodes an external embedding API as the default endpoint, making third-party transmission the out-of-box behavior. This increases the chance of sensitive text being exfiltrated outside the local environment, particularly because the skill description emphasizes ingestion into Qdrant and may not make remote processing obvious to operators.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script sends document content chunks to an external embedding service by default, which can expose sensitive or proprietary data to a third party without any runtime disclosure, consent gate, or policy enforcement. In an ingestion skill, this is especially relevant because users may assume the tool only writes to local Qdrant, while the actual design transmits full text off-host for embedding.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script automatically deletes all existing points matching the provided doc_id before upserting new ones, with no confirmation or dry-run mode. If a user supplies the wrong doc_id or invokes the tool unexpectedly in automation, this can cause silent data loss or unauthorized overwrite of knowledge-base contents.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
The file-level documentation at L006-L007 says the tool only chunks, embeds, and writes provided正文, and explicitly says it does not perform refinement. However, the stored payload sets text_type to "summary", which semantically represents refined/summarized content rather than raw正文 chunks. This is an active documentation-to-code contradiction because downstream consumers will be told the stored text is a summary when the code is actually storing chunked source text.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The natural-language instructions and parameter descriptions are presented exclusively in Chinese, which can impose a language requirement on users without opt-in. The file does not indicate that the skill is intentionally region-specific or provide an alternative language option.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/ingest.mjs:30