Back to skill

Security audit

rrragflow-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for managing RAGFlow data, but it can send API keys and uploaded documents over non-HTTPS endpoints if configured that way.

Review before installing. Use this only with a trusted RAGFlow endpoint, prefer HTTPS for any non-local service, and use a narrowly scoped API key. Confirm dataset and document IDs carefully before delete or update actions because the scripts will execute those API changes once called.

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/common.py:84
Finding

Bearer Credentials and Document Contents May Be Transmitted over Cleartext HTTP

Content
View full analysis

Vulnerability Details

File Location: scripts/common.py:84-92, 165-184; scripts/upload.py:62-80, 119-131
Vulnerability Type: Cleartext transmission of sensitive information
Risk Level: High

Complete Code Snippet

python
# scripts/common.py:84-92
def resolve_base_url(cli_base_url: str | None = None) -> str:
    base_url = (cli_base_url or "").strip() or _require_env_var(RAGFLOW_API_URL_ENV)

    parsed = urllib.parse.urlsplit(base_url)
    if not parsed.scheme or not parsed.netloc:
        raise ConfigError(
            f"Invalid {RAGFLOW_API_URL_ENV}. Use an absolute URL such as http://127.0.0.1:9380."
        )
    return base_url.rstrip("/")
python
# scripts/common.py:165-184
def request_json(
    url: str,
    api_key: str,
    *,
    method: str = "GET",
    body: bytes | None = None,
    content_type: str | None = None,
    accept: str = "application/json",
) -> dict[str, Any]:
    headers = {"Authorization": f"Bearer {api_key}"}
    if accept:
        headers["Accept"] = accept
    if content_type:
        headers["Content-Type"] = content_type

    request_obj = urllib.request.Request(url, headers=headers, data=body, method=method)

    try:
        with urllib.request.urlopen(request_obj, timeout=HTTP_TIMEOUT) as response:
            return decode_json_response(response.read())
python
# scripts/upload.py:62-80
def _build_multipart(file_paths: list[str]) -> tuple[str, bytes]:
    boundary = "----OpenClawBoundary" + uuid.uuid4().hex
    body = bytearray()

    for file_path in file_paths:
        filename = os.path.basename(file_path)
        mime = mimetypes.guess_type(filename)[0] or "application/octet-stream"
        with open(file_path, "rb") as file_obj:
            content = file_obj.read()

        body.extend(f"--{boundary}\r\n".encode())
        body.extend(
            f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode()
        )
        body.extend(f"Content-Type: {mime}\
...[truncated 3806 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require HTTPS for all non-loopback RAGFlow endpoints:
python
def resolve_base_url(cli_base_url: str | None = None) -> str:
    base_url = (cli_base_url or "").strip() or _require_env_var(RAGFLOW_API_URL_ENV)
    parsed = urllib.parse.urlsplit(base_url)

    if parsed.scheme not in {"https", "http"} or not parsed.netloc:
        raise ConfigError("RAGFLOW_API_URL must be an absolute HTTP(S) URL.")

    hostname = (parsed.hostname or "").lower()
    loopback_hosts = {"localhost", "127.0.0.1", "::1"}
    if parsed.scheme != "https" and hostname not in loopback_hosts:
        raise ConfigError("HTTPS is required for non-loopback RAGFlow endpoints.")

    return base_url.rstrip("/")
  1. Prefer HTTPS even for local deployments when the service crosses container, virtual-machine, or host boundaries. Treat container-network and private-network addresses as remote rather than loopback.

  2. If compatibility requires cleartext HTTP, place it behind an explicit option such as --allow-insecure-http, default it to disabled, and emit a prominent warning. Do not infer safety merely from RFC1918/private addressing.

  3. Update documentation and validation messages so the normal example uses an HTTPS URL. Clearly state that bearer credentials and uploaded documents are transmitted to the configured endpoint.

  4. Use short-lived, narrowly scoped API credentials where RAGFlow supports them. Separate read-only credentials for list/status/search operations from credentials capable of uploads, updates, and deletion.

  5. Ensure TLS certificate verification remains enabled. Do not introduce unverified SSL contexts as a workaround for private certificates; configure an appropriate trusted CA bundle instead.

  6. Consider adding automated tests that verify:

    • https:// endpoints are accepted.
    • non-loopback http:// endpoints are rejected by default.
    • loopback HTTP is accepted only under the documented policy.
    • unsupported schemes such as ...[truncated 37 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • 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 (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The supplied code is narrowly focused on dataset management in scripts/datasets.py. It supports listing datasets, retrieving info for a dataset by ID, creating a dataset, and deleting datasets via the /api/v1/datasets endpoint. The declared description, however, represents a much broader skill that also includes updating datasets, managing documents, controlling and checking parsing, retrieving chunks with search.py, and listing models. None of those additional capabilities appear in this code chunk. This is a description-to-behavior mismatch because the declared purpose materially overstates what this specific code actually does.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/upload.py DATASET_ID /path/to/file.pdf --json

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/upload.py DATASET_ID /path/to/file.pdf --json

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

The instruction to follow an external reference for all user-facing output and to surface fields like progress_msg directly can enable indirect prompt injection or untrusted content propagation if those sources contain attacker-controlled text. In a retrieval and parsing skill, backend document metadata, parse errors, or reference formatting rules may carry adversarial instructions that get relayed to the model or user without sanitization.

Content

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

md
- If a parse status result includes `progress_msg`, surface it directly. For `FAIL`, treat it as the primary error detail.
- Use `--retrieval-test` only for single-dataset debugging or when the user explicitly asks for that endpoint.

## Output Rules

- Follow `reference.md`.
- Use tables for 3+ items when possible.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill declares access to sensitive capabilities via required environment variables and bundled scripts that imply file and network operations, but it does not define an explicit tool/permission scope. That creates an authorization gap where an agent may invoke broader capabilities than reviewers or runtime policy expect, increasing the chance of unintended data access or outbound API actions using the RAGFLOW_API_KEY.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The delete_datasets function issues an HTTP DELETE request that can permanently remove datasets, but the code provides no confirmation prompt, pre-action warning, or explanatory comment/docstring disclosing the destructive behavior before execution. Although the command name is 'delete', this file itself lacks any explicit user warning for an irreversible operation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code sends the user's query and optional document/dataset identifiers to a remote HTTP endpoint via request_json, then prints retrieved chunk content back to stdout. Although network retrieval is the script's purpose, the file itself provides no confirmation prompt, warning, or explanatory comment/docstring disclosing that user-provided search text and retrieved content will be transmitted to an external service and echoed to output.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The delete_documents function issues a DELETE request that removes documents from a dataset, but the script provides no confirmation prompt, cautionary print/log message, or inline comment/docstring warning about this irreversible action. In this file, the deletion path is exposed directly through the command parser and executed immediately when invoked.

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/common.py (reported line 59)May include surrounding context.

python
return

    for stream_name in ("stdout", "stderr"):
        stream = getattr(sys, stream_name, None)
        if stream is None or not hasattr(stream, "reconfigure"):
            continue
        try:

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The request_json function sends authenticated HTTP requests using a Bearer token derived from the API key, which is a safety-relevant network operation. In this file there is no confirmation prompt, logging, comment, or docstring warning that the script will transmit credentials and request data to a remote server.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.