Back to skill

Security audit

Manus on OpenClaw

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate Manus bridge purpose, but several scripts handle credentials, URLs, and file writes too broadly for safe installation without review.

Review before installing. Use only a trusted, locked-down config file, verify MANUS_API_BASE is the real Manus endpoint, avoid broad automatic chat triggers, and do not process untrusted slide JSON until the downloader and path handling are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/manus_slides_json_to_pptx.mjs:20
Finding
Unrestricted Slide Asset Downloader Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/manus_slides_json_to_pptx.mjs`, lines 20-40 and 55-65 **Vulnerability Type**: Server-Side Request Forgery and unsafe file download **Risk Level**: High ### Vulnerable Code ```javascript function download(url, dest) { const client = url.startsWith('https:') ? https : http; return new Promise((resolve, reject) => { const req = client.get(url, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { res.resume(); return resolve(download(res.headers.location, dest)); } if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); } const file = fs.createWriteStream(dest); res.pipe(file); file.on('finish', () => file.close(() => resolve(dest))); file.on('error', reject); }); req.on('error', reject); req.setTimeout(120000, () => req.destroy(new Error('timeout'))); }); } ``` The untrusted URL is obtained from the slide JSON and passed directly to the downloader: ```javascript const slides = Array.isArray(obj.slide_ids) ? obj.slide_ids : []; for (let i = 0; i < slides.length; i++) { const slideId = slides[i]; const slide = pptx.addSlide(); const imgUrl = obj.images?.[slideId]; const outline = Array.isArray(obj.outline) ? obj.outline.find((x) => x.id === slideId) : undefined; if (imgUrl) { const imgPath = path.join(outDir, `${String(i + 1).padStart(2, '0')}_${slideId}.png`); if (!fs.existsSync(imgPath)) { await download(imgUrl, imgPath); } ``` ### Technical Analysis The downloader accepts asset URLs directly from an input JSON document. It does not enforce HTTPS, restrict destination hosts to Manus-controlled domains, reject private or link-local IP addresses, restrict destination ports, or limit redirect depth. Any URL not beginning with `https:` is handled by the HTTP client. Redirect destinations ...[truncated 1759 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `https:` for every initial and redirected URL. - Restrict hosts to an exact, centrally maintained allowlist of Manus-controlled domains. - Reject URLs containing user information, unexpected ports, malformed hostnames, or ambiguous IP representations. - Resolve hostnames and reject loopback, private, link-local, multicast, and reserved IPv4 and IPv6 addresses. - Revalidate every redirect destination before following it. - Set a small maximum redirect count. - Stream downloads with a strict byte limit instead of accepting unlimited responses. - Verify the response content type and image format before using the asset. - Write to a temporary file and atomically rename it only after validation succeeds. - Remove partial files after timeout, size-limit, network, or validation failures. - Reuse one common downloader implementation so that the Python collector and JavaScript converter enforce the same security policy. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/manus_wait_and_collect.py:113
Finding
Unsanitized Task Identifier Allows Output Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/manus_wait_and_collect.py`, lines 113-118 and 134-143 **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code ```python def main(): load_env() if len(sys.argv) < 2: raise SystemExit('Usage: manus_wait_and_collect.py <task_id> [timeout_seconds]') task_id = sys.argv[1] timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 900 ``` The task identifier is subsequently incorporated into the destination path without validation: ```python files = collect_files(task) downloaded = [] for index, file_info in enumerate(files, 1): name = safe_name(file_info['name']) target = OUT_DIR / f'{task_id}_{index}_{name}' try: download(file_info['url'], target) downloaded.append({**file_info, 'saved_path': str(target)}) except Exception as exc: downloaded.append({**file_info, 'download_error': str(exc)}) ``` The final write occurs in the downloader: ```python def download(url: str, path: Path): validate_url(url) req = urllib.request.Request(url, headers={'User-Agent': 'manus-openclaw-bridge/1.0'}) with urllib.request.urlopen(req, timeout=120) as resp: final_url = resp.geturl() validate_url(final_url) data = resp.read() path.write_bytes(data) return path ``` ### Technical Analysis The remote filename is passed through `safe_name`, but `task_id` is accepted directly from the command line and used as part of the filesystem path. `pathlib` resolves absolute path components and traversal components according to normal filesystem semantics. The code does not resolve the completed path and verify that it remains beneath `OUT_DIR`. An absolute task identifier can cause the `/` operator to discard the intended output directory. Traversal components may also escape the output directory when a usable path structure is supplied. The final `write_bytes` call overwrites ...[truncated 1402 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate `task_id` against the exact identifier grammar defined by the Manus API, preferably a conservative allowlist such as ASCII letters, digits, underscores, and hyphens with a strict length limit. - Reject path separators, `.` and `..` path components, control characters, and absolute paths. - Apply sanitization to every component used to form a local filename, not only the remote filename. - Resolve the proposed target path and verify that it is a descendant of the resolved `OUT_DIR` before opening it. - Use a generated local identifier rather than embedding an untrusted task identifier into the filename. - Consider exclusive file creation to avoid silently overwriting existing files. - Download into a newly created temporary file and atomically rename it after successful validation. - Validate or safely encode the task identifier separately before incorporating it into the API URL. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/manus_submit.sh:18
Finding
API Credentials and User Prompts Can Be Sent to Arbitrary HTTPS Hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/manus_submit.sh`, lines 18-25 and 37-43; `scripts/manus_get_task.sh`, lines 16-24 and 32-36 **Vulnerability Type**: Insufficient endpoint validation causing credential and data disclosure **Risk Level**: High ### Vulnerable Code From `scripts/manus_submit.sh`: ```bash if [[ -z "${MANUS_API_BASE:-}" ]]; then echo "MANUS_API_BASE is not set. Put it in $CONFIG_FILE" >&2 exit 1 fi if [[ ! "$MANUS_API_BASE" =~ ^https:// ]]; then echo "MANUS_API_BASE must start with https://" >&2 exit 1 fi ``` ```bash curl --silent --show-error --fail \ --request POST \ --url "$MANUS_API_BASE/v1/tasks" \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --header "API_KEY: ${MANUS_API_KEY}" \ --data "{\"prompt\":\"${PROMPT//\"/\\\"}\",\"agentProfile\":\"${AGENT_PROFILE}\",\"taskMode\":\"${TASK_MODE}\"}" ``` From `scripts/manus_get_task.sh`: ```bash if [[ -z "${MANUS_API_BASE:-}" ]]; then echo "MANUS_API_BASE is not set. Put it in $CONFIG_FILE" >&2 exit 1 fi if [[ ! "$MANUS_API_BASE" =~ ^https:// ]]; then echo "MANUS_API_BASE must start with https://" >&2 exit 1 fi ``` ```bash curl --silent --show-error --fail \ --request GET \ --url "$MANUS_API_BASE/v1/tasks/${TASK_ID}" \ --header 'accept: application/json' \ --header "API_KEY: ${MANUS_API_KEY}" ``` ### Technical Analysis Both shell clients verify only that `MANUS_API_BASE` begins with the string `https://`. They do not parse the URL or verify that its hostname is controlled by Manus. The Python collector performs a Manus-domain allowlist check, but the submit and task-fetch scripts do not enforce the same restriction. The API key is attached to every request made to the configured endpoint. The submission script also sends the user's complete prompt. Therefore, a modified or incorrectly configured API base can redirect sensitive credentials and task content to any server with HTTPS support. Th ...[truncated 1285 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse `MANUS_API_BASE` with a proper URL parser instead of using a string-prefix check. - Restrict the hostname to an exact allowlist of approved Manus API domains. - Reject embedded credentials, fragments, unexpected ports, malformed hostnames, and ambiguous hostname forms. - Normalize and validate the URL before appending API paths. - Disable redirects for authenticated API requests unless they are strictly required. - If redirects are required, revalidate every destination and never forward credentials across origins. - Centralize endpoint validation so all Bash and Python clients enforce identical rules. - Use narrowly scoped and revocable API credentials where supported. - Document that configuration files must have restrictive permissions and trusted ownership. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/manus_submit.sh:6
Finding
Shell Configuration Files Are Executed as Arbitrary Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/manus_submit.sh`, lines 6-10; `scripts/manus_get_task.sh`, lines 4-8 **Vulnerability Type**: Arbitrary command execution through unsafe configuration loading **Risk Level**: High ### Vulnerable Code From `scripts/manus_submit.sh`: ```bash CONFIG_FILE="${MANUS_CONFIG_FILE:-$HOME/.config/manus-openclaw-bridge/manus.env}" if [[ -f "$CONFIG_FILE" ]]; then # shellcheck disable=SC1090 source "$CONFIG_FILE" fi ``` From `scripts/manus_get_task.sh`: ```bash CONFIG_FILE="${MANUS_CONFIG_FILE:-$HOME/.config/manus-openclaw-bridge/manus.env}" if [[ -f "$CONFIG_FILE" ]]; then # shellcheck disable=SC1090 source "$CONFIG_FILE" fi ``` ### Technical Analysis The `source` built-in evaluates the complete contents of the selected file as shell code. The expected file format is presented as environment-variable assignments, but no parser restricts the file to assignments or to recognized keys. Command substitutions, function definitions, redirections, shell commands, and other executable syntax are all accepted. In addition, `MANUS_CONFIG_FILE` allows the caller's environment to select a different file. The scripts do not verify file ownership, permissions, file type, or whether the path is a symbolic link. The Python collector demonstrates a safer approach by parsing key/value lines without executing them, although its parser should also enforce an explicit key allowlist. ### Attack Path 1. An attacker gains the ability to create or modify the expected configuration file, manipulate a symlink at that path, or influence `MANUS_CONFIG_FILE`. 2. The attacker inserts shell commands or command substitution syntax into the selected file. 3. The victim invokes `manus_submit.sh` or `manus_get_task.sh`. 4. The script executes `source "$CONFIG_FILE"` before making any API request. 5. The injected commands execute with all operating-system privileges of the invoking process. ### Impact Assessment An attacker ca ...[truncated 433 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use `source` for a data-only configuration file. - Parse the file as a strict key/value format using a non-evaluating parser. - Permit only explicitly supported keys such as `MANUS_API_KEY`, `MANUS_API_BASE`, `MANUS_AGENT_PROFILE`, and `MANUS_TASK_MODE`. - Reject duplicate keys, malformed lines, command substitutions, control characters, and unexpected variable names. - Check that the configuration is a regular file, is owned by the expected user, and is not group- or world-writable. - Consider refusing symbolic links for security-sensitive configuration. - Restrict configuration permissions to the owner, such as mode `0600`. - Treat environment overrides such as `MANUS_CONFIG_FILE` as privileged configuration and validate the selected path before reading it. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/manus_submit.sh:28
Finding
Manual JSON Construction Permits Request-Structure Injection and Malformed Payloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/manus_submit.sh`, lines 28-43 **Vulnerability Type**: Improper JSON encoding and structured-data injection **Risk Level**: Medium ### Vulnerable Code ```bash PROMPT="${1:-}" if [[ -z "$PROMPT" ]]; then echo "Usage: $0 \"<prompt>\"" >&2 exit 1 fi AGENT_PROFILE="${MANUS_AGENT_PROFILE:-manus-1.6}" TASK_MODE="${MANUS_TASK_MODE:-agent}" curl --silent --show-error --fail \ --request POST \ --url "$MANUS_API_BASE/v1/tasks" \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --header "API_KEY: ${MANUS_API_KEY}" \ --data "{\"prompt\":\"${PROMPT//\"/\\\"}\",\"agentProfile\":\"${AGENT_PROFILE}\",\"taskMode\":\"${TASK_MODE}\"}" ``` ### Technical Analysis The request body is assembled by interpolating shell variables into a JSON string. The prompt transformation escapes quotation marks only. It does not correctly encode backslashes, newlines, carriage returns, tabs, null bytes, or other JSON control characters. `AGENT_PROFILE` and `TASK_MODE` are inserted without any JSON escaping. Structured formats must be generated using a format-aware encoder. Ad hoc escaping can allow input to terminate a string or modify the meaning of subsequent characters. At minimum, crafted input can produce invalid JSON and prevent legitimate task submission. Where an attacker can control the environment-backed profile or mode fields, they may inject additional JSON properties or alter request parameters accepted by the remote API. ### Attack Path 1. An attacker supplies a prompt containing crafted backslash and control-character sequences, or controls `MANUS_AGENT_PROFILE` or `MANUS_TASK_MODE`. 2. The shell script interpolates the value directly into the request-body string. 3. The generated body becomes malformed or acquires attacker-controlled JSON structure. 4. The body is sent to the Manus API. 5. The API rejects the request, interprets altered parameters, or accepts injecte ...[truncated 463 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate the request body using a standard JSON encoder rather than string interpolation. - In Bash, use a tool such as `jq` with `--arg` for every string field, or move request generation to Python or Node.js and call `json.dumps` or `JSON.stringify`. - Validate `MANUS_AGENT_PROFILE` and `MANUS_TASK_MODE` against explicit accepted-value allowlists. - Apply reasonable length limits to prompts and configuration fields. - Add tests covering quotation marks, backslashes, Unicode, newlines, tabs, and other control characters. - Keep transport options and encoded request data separate to reduce accidental interpretation or quoting errors. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (17)

Tainted flow: 'req' from os.environ.get (line 54, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
f'{api_base}/v1/tasks/{task_id}',
        headers={'accept': 'application/json', 'API_KEY': key},
    )
    with urllib.request.urlopen(req, timeout=60) as resp:
        return json.loads(resp.read().decode('utf-8'))
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 54, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
def download(url: str, path: Path):
    validate_url(url)
    req = urllib.request.Request(url, headers={'User-Agent': 'manus-openclaw-bridge/1.0'})
    with urllib.request.urlopen(req, timeout=120) as resp:
        final_url = resp.geturl()
        validate_url(final_url)
        data = resp.read()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code only implements initial task submission to the Manus tasks endpoint. While that is one subset of the declared purpose ('connect ... to Manus task APIs'), the description claims a broader workflow including chat-driven image generation, document/slides jobs, polling for task status, collecting outputs, and returning results through messaging surfaces. None of those additional behaviors are present in this code chunk. This is therefore a description-behavior mismatch in scope: the implemented behavior is materially narrower than the declared functionality.

Ae1

High
Category
analysis-evasion
Content
5. If the result is a slides JSON bundle, convert it with `scripts/manus_slides_json_to_pptx.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
5. If the result is a slides JSON bundle, convert it with `scripts/manus_slides_json_to_pptx.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Session Persistence

Medium
Category
Rogue Agent
Content
## Required configuration

Create this file on each machine:

`~/.config/manus-openclaw-bridge/manus.env`
Confidence
60% 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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill documents capabilities that involve environment variables, local file I/O, shell execution, and outbound network access, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an overly broad trust boundary: an agent may invoke sensitive capabilities implicitly, making review, sandboxing, and policy enforcement harder and increasing the risk of misuse or accidental data exposure.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The guidance to trigger jobs from a broad natural-language prefix like 'Manus,' or 'manus:' can overlap with ordinary conversation in direct messages, causing unintended task submission to an external API. In this skill context, accidental launches may send user content to Manus, consume API quota, and potentially expose sensitive chat text or generate unintended outputs without clear user confirmation.

External Transmission

Medium
Category
Data Exfiltration
Content
## Minimal API call

```bash
curl --request POST \
  --url 'https://api.manus.ai/v1/tasks' \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
curl --request POST \
  --url 'https://api.manus.ai/v1/tasks' \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --header "API_KEY: $MANUS_API_KEY" \
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script loads a user-controlled config file with `source`, which executes arbitrary shell code rather than parsing simple key/value settings. If an attacker can modify that file, run the script with a malicious `MANUS_CONFIG_FILE`, or trick an operator into using an untrusted config, they can execute commands in the context of the user running the bridge and potentially steal the Manus API key or pivot to other local actions.

External Transmission

Medium
Category
Data Exfiltration
Content
AGENT_PROFILE="${MANUS_AGENT_PROFILE:-manus-1.6}"
TASK_MODE="${MANUS_TASK_MODE:-agent}"

curl --silent --show-error --fail \
  --request POST \
  --url "$MANUS_API_BASE/v1/tasks" \
  --header 'accept: application/json' \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This shell script sends the supplied prompt and selected profile/mode to a remote API via curl, which is a safety-relevant network operation that transmits user-provided data. Although the script validates configuration and prints usage errors, it does not include any confirmation, notice, or explanatory comment near the submission to warn the user that their prompt is being sent to an external service.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This markdown file includes a minimal curl example that sends the API key in a request header to a remote endpoint, but it does not explicitly warn users that invoking the example transmits credentials and prompt content to an external service. The existing notes only say not to ship the key with the skill, which is secret-handling guidance rather than a user-facing disclosure about network transmission.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The file hard-codes the presentation language as 'zh-CN' in both `pptx.lang` and the theme language. This imposes a specific locale on all generated output without offering user opt-in or documenting why the constraint is required.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The script creates a local output directory and writes downloaded task artifacts to disk via path.write_bytes(data). While filenames are sanitized and the destination is constrained, there is no explicit disclosure in the file beyond implementation details that remote content will be persisted locally.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This code loads values from a local config file into environment variables and uses MANUS_API_KEY as an HTTP header for outbound requests. Although the behavior is functionally expected for API access, the file itself provides no docstring, comment, or user-facing message disclosing that credentials will be read and sent over the network.

Static analysis

No suspicious patterns detected.