Back to skill

Security audit

ragflow-runbook

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real RAGFlow operations runbook, but it deserves review because it can deploy mutable upstream Docker content and use API tokens in weakly constrained network checks.

Review this before installing on a production or sensitive host. Use a pinned RAGFlow release or commit, inspect Compose files before starting containers, prefer HTTPS or loopback-only HTTP for bearer-token checks, use a low-privilege monitoring API key, avoid putting secrets directly in cron or launchd files, and require explicit confirmation before restore or scheduled-task setup.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ragflow_ping.py:33
Finding
Bearer token can be transmitted over plaintext HTTP or forwarded during redirects## Vulnerability Details **File Location**: `scripts/ragflow_ping.py:33-39, 47-58`; `scripts/ragflow_smoke.py:34-41, 48-56`; `scripts/ragflow_status.py:31-38, 45-48`; `examples/api-examples.sh:19, 34-48` **Vulnerability Type**: Unprotected transmission of credentials and insufficient destination validation **Risk Level**: High ### Vulnerable Code `scripts/ragflow_ping.py:33-39`: ```python def http_get(url: str, api_key: str | None = None, timeout: int = 10) -> tuple[int, bytes]: headers = {"Accept": "application/json"} if api_key: headers["Authorization"] = f"Bearer {api_key}" req = urllib.request.Request(url, headers=headers, method="GET") try: with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.status, resp.read() ``` `scripts/ragflow_ping.py:47-58`: ```python base_url = get_env("RAGFLOW_BASE_URL").rstrip("/") api_key = os.environ.get("RAGFLOW_API_KEY", "").strip() or None st, _ = http_get(f"{base_url}/openapi.json", api_key=None) if st != 200: print(f"LIVENESS_FAIL openapi.json status={st}") return 2 if not api_key: print("OK_LIVE (no api key set)") return 0 st2, body2 = http_get(f"{base_url}/v1/system/status", api_key=api_key) ``` `scripts/ragflow_smoke.py:34-41`: ```python def http_get(url: str, api_key: str, timeout: int = 15) -> tuple[int, bytes]: headers = { "Accept": "application/json", "Authorization": f"Bearer {api_key}", } req = urllib.request.Request(url, headers=headers, method="GET") try: with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.status, resp.read() ``` `scripts/ragflow_status.py:31-38`: ```python def http_get(url: str, api_key: str, timeout: int = 15) -> tuple[int, bytes]: headers = { "Accept": "application/json", "Authorization": f"Bearer {api_key}", } re ...[truncated 3183 chars]
Remediation
## Remediation Suggestions 1. Parse `RAGFLOW_BASE_URL` before making requests and permit only `https` by default. 2. Allow plaintext HTTP only for explicit loopback destinations such as `127.0.0.1`, `::1`, or `localhost`, or behind a clearly named opt-in such as `RAGFLOW_ALLOW_INSECURE_HTTP=1`. 3. Reject URLs containing embedded user information, fragments, unexpected schemes, or malformed hostnames. 4. Disable redirects for authenticated requests, or implement a redirect handler that permits only same-scheme, same-host, and same-port redirects. 5. Never downgrade an authenticated request from HTTPS to HTTP. 6. Configure certificate verification with the system trust store or an explicitly supplied private CA. Do not introduce a global TLS-verification bypass. 7. Apply the same validation to all Python helpers, shell examples, and documented curl commands. 8. Use a narrowly scoped monitoring credential with short lifetime and straightforward revocation. 9. Add automated tests covering plaintext remote URLs, cross-origin redirects, HTTPS-to-HTTP redirects, URLs with user information, and loopback exceptions.

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/deploy.sh:110
Finding
Mutable upstream deployment code is retrieved and executed without integrity verification## Vulnerability Details **File Location**: `scripts/deploy.sh:68-91, 110-145, 171-178` **Vulnerability Type**: Remote payload retrieval and execution without immutable pinning or integrity validation **Risk Level**: Medium ### Vulnerable Code `scripts/deploy.sh:68-91`: ```bash UPSTREAM_REPO_URL="https://github.com/infiniflow/ragflow.git" UPSTREAM_DIR="$DEPLOY_ROOT/ragflow" DOCKER_DIR="$UPSTREAM_DIR/docker" have_git=0 if command -v git >/dev/null 2>&1; then have_git=1 fi if [[ $have_git -eq 1 ]]; then echo "3) Fetch upstream via git clone (preferred)" if [[ -d "$UPSTREAM_DIR/.git" ]]; then echo " - Found existing repo: $UPSTREAM_DIR" echo " - Updating..." (cd "$UPSTREAM_DIR" && git fetch --all --prune) elif [[ -e "$UPSTREAM_DIR" ]]; then echo " ERROR: $UPSTREAM_DIR exists but is not a git repo" echo " Move it aside or choose a different deploy_root" exit 2 else echo " - Cloning: $UPSTREAM_REPO_URL" git clone "$UPSTREAM_REPO_URL" "$UPSTREAM_DIR" fi ``` `scripts/deploy.sh:110-145`: ```bash echo " - Download docker files" FILES=( "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/docker-compose.yml" "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/docker-compose-base.yml" "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/.env" "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/service_conf.yaml.template" "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/entrypoint.sh" "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/README.md" ) dl() { local url="$1" local name name=$(basename "$url") echo " - $name" wget -q "$url" || curl -fsSLO "$url" } for url in "${FILES[@]}"; do dl "$url" done echo " - Download nginx configuration" mkdir -p nginx cd nginx NGINX_FILES=( "https://r ...[truncated 3447 chars]
Remediation
## Remediation Suggestions 1. Pin deployment content to a reviewed release tag and full commit SHA instead of `main` or an unqualified default branch. 2. After cloning, explicitly check out the expected commit and verify that `git rev-parse HEAD` exactly matches the configured SHA. 3. Prefer signed upstream releases or signed commits and fail closed when signature verification fails. 4. For fallback downloads, use immutable commit-based raw URLs and verify every file against a checksum manifest embedded in or securely distributed with the Skill. 5. Pin container images by digest rather than mutable tags. 6. Separate retrieval, verification, review, and execution into distinct commands. Do not permit startup until verification succeeds. 7. Display the selected commit, image digests, and verification result before requesting start authorization. 8. Validate Compose configuration for dangerous options, including privileged mode, host PID/network namespaces, Docker socket mounts, broad host filesystem mounts, added capabilities, and untrusted build contexts. 9. Run deployment through a least-privileged or rootless Docker environment where operationally possible. 10. Maintain an approved-version manifest so updates require an explicit review and manifest change rather than silently consuming new upstream content.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (36)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The declared description promises a broad end-to-end runtime operations runbook covering deployment, operation, troubleshooting, and monitoring. The supplied code chunk is much narrower: it is only an example shell script for hitting a few system endpoints and printing HTTP status codes. It does not deploy anything, does not provide substantive troubleshooting logic, and does not implement monitoring beyond minimal one-off checks. This is a material difference in primary purpose and scope, so the description does not accurately represent the code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description says this skill is an end-to-end runbook for RAGFlow runtime operations, which implies documentation or operational procedures for deployment, operation, troubleshooting, and monitoring. The supplied code instead implements a concrete alert-sending utility that performs outbound messaging to Telegram via OpenClaw. That is a materially different primary purpose from a runbook. It also introduces undeclared capabilities: sending external notifications, invoking an external command, and using environment-based routing configuration. While alerting could support monitoring/operations, this code chunk is not itself a runbook and its active behavior is not accurately represented by the declared description.

Credential Access

High
Category
Privilege Escalation
Content
# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env

docker compose up -d
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env

docker compose up -d
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env

docker compose up -d
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env

docker compose up -d
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Chaining Abuse

High
Category
Tool Misuse
Content
sudo sysctl -w vm.max_map_count=262144

# Permanent (Linux)
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Credential Access

High
Category
Privilege Escalation
Content
FILES=(
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/docker-compose.yml"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/docker-compose-base.yml"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/.env"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/service_conf.yaml.template"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/entrypoint.sh"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/README.md"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
FILES=(
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/docker-compose.yml"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/docker-compose-base.yml"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/.env"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/service_conf.yaml.template"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/entrypoint.sh"
    "https://raw.githubusercontent.com/infiniflow/ragflow/main/docker/README.md"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill explicitly instructs agents to use shell commands, network access, and environment variables, but it does not declare any tool scope or allowed-tools boundary. That creates an avoidable over-privilege condition where an agent may execute powerful actions without an explicit permission contract, increasing the chance of unintended command execution or network access in a runtime-ops context.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
cd ragflow/docker

# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
cd ragflow/docker

# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
cd ragflow/docker

# Common requirement for some document engine profiles
sudo sysctl -w vm.max_map_count=262144 || true

# Default .env = elasticsearch + cpu
# To change ports/passwords/image versions: edit docker/.env
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding
The backup example uses `docker run --rm ... alpine` without pinning an image tag or digest, which can pull a mutable image at execution time. This creates supply-chain risk because future image changes or a compromised upstream image could alter behavior unexpectedly during sensitive backup operations.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The restore procedure instructs users to extract backup data directly into a target volume after `docker compose down` without an explicit warning that existing data may be overwritten or destroyed. In an agent-executed context, this can lead to irreversible data loss if the wrong volume, environment, or backup archive is used.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding
The restore example also invokes an unpinned `alpine` container via `docker run --rm`, again relying on a mutable external image during a sensitive data operation. Because restore actions touch persistent volumes, compromised or changed container content could corrupt, overwrite, or exfiltrate backed-up data.

Session Persistence

Medium
Category
Rogue Agent
Content
#### 12.5.2 launchd (macOS)

Create two plist files (one for ping, one for smoke) and load them with `launchctl`.

Ping (every 10 minutes):
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#### 12.5.2 launchd (macOS)

Create two plist files (one for ping, one for smoke) and load them with `launchctl`.

Ping (every 10 minutes):
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#### 12.5.2 launchd (macOS)

Create two plist files (one for ping, one for smoke) and load them with `launchctl`.

Ping (every 10 minutes):
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#### 12.5.2 launchd (macOS)

Create two plist files (one for ping, one for smoke) and load them with `launchctl`.

Ping (every 10 minutes):
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#### 12.5.2 launchd (macOS)

Create two plist files (one for ping, one for smoke) and load them with `launchctl`.

Ping (every 10 minutes):
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
#### 12.5.2 launchd (macOS)

Create two plist files (one for ping, one for smoke) and load them with `launchctl`.

Ping (every 10 minutes):
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
Confidence
90% confidence
Finding
This finding points to the same plist block, where long-lived persistence is combined with inline secret material. Even though the example is for benign monitoring, storing authentication data in a persistent launch configuration broadens the blast radius if the host or user account is compromised.

Session Persistence

Medium
Category
Rogue Agent
Content
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
Confidence
90% confidence
Finding
This finding points to the same plist block, where long-lived persistence is combined with inline secret material. Even though the example is for benign monitoring, storing authentication data in a persistent launch configuration broadens the blast radius if the host or user account is compromised.

Session Persistence

Medium
Category
Rogue Agent
Content
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
Confidence
90% confidence
Finding
This duplicate finding maps to the same smoke-check plist and carries the same risk: persistent scheduled execution plus inline bearer token storage. Because bearer tokens are sufficient for API access, disclosure can enable unauthorized health/status queries or broader actions depending on server-side privileges.

Static analysis

No suspicious patterns detected.