Back to skill

Security audit

Todo4 Onboard

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its Todo4 onboarding purpose, but it persists agent credentials and trusts server-provided MCP configuration too broadly.

Review before installing. This skill will send your email and OTP to Todo4, connect the agent to your account, and store an agent token plus MCP configuration under ~/.openclaw. Install only if you trust Todo4 and are comfortable with persistent agent access; stronger safeguards should validate the MCP snippet and lock down token file permissions.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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
Findings (3)

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/connect.sh:61
Finding
Unvalidated Server-Controlled Configuration Is Merged into the Trusted MCP Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/connect.sh:61-80` **Vulnerability Type**: Unvalidated remote MCP configuration injection **Risk Level**: High ### Vulnerable Code ```bash AGENT_TOKEN=$(echo "$BODY" | jq -r '.data.agentAccessToken') MCP_SNIPPET=$(echo "$BODY" | jq -c '.data.mcpConfigSnippet') if [ -z "$AGENT_TOKEN" ] || [ "$AGENT_TOKEN" = "null" ]; then echo '{"error":"parse_error","message":"Could not extract agentAccessToken from response"}' >&2 exit 1 fi if [ -z "$MCP_SNIPPET" ] || [ "$MCP_SNIPPET" = "null" ]; then echo '{"error":"parse_error","message":"Could not extract mcpConfigSnippet from response"}' >&2 exit 1 fi # ── Write/merge MCP config ─────────────────────────────────────────────────── MCP_CONFIG="${HOME}/.openclaw/mcp_config.json" if [ -f "$MCP_CONFIG" ]; then # Deep-merge: add/replace mcpServers.todo4, keep everything else TMP_CONFIG=$(mktemp) trap 'rm -f "$TMP_CONFIG"' EXIT jq --argjson snippet "$MCP_SNIPPET" '. * $snippet' "$MCP_CONFIG" > "$TMP_CONFIG" mv "$TMP_CONFIG" "$MCP_CONFIG" else mkdir -p "$(dirname "$MCP_CONFIG")" echo "$MCP_SNIPPET" | jq . > "$MCP_CONFIG" fi ``` ### Technical Analysis The script extracts `mcpConfigSnippet` from a remote API response and writes it into OpenClaw's trusted MCP configuration without validating its structure or contents. The merge expression `. * $snippet` does not enforce the comment's claim that only `mcpServers.todo4` will be added or replaced. It allows the remote response to introduce or replace arbitrary top-level configuration properties. The script also does not verify: - That the snippet contains only `mcpServers.todo4`. - That the MCP transport is an expected type. - That network destinations belong to Todo4. - That executable or stdio-based server definitions are absent. - That unrelated existing MCP server definitions remain unchanged. Although HTTPS protects the response in transit under ordinary conditions, the Todo4 API remains ...[truncated 1676 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not merge an arbitrary server-provided object into the complete MCP configuration. - Validate the response against a strict schema before writing it. - Require exactly one expected entry, such as `mcpServers.todo4`. - Construct the final Todo4 configuration locally from individually validated response fields. - Allowlist the expected transport and Todo4 HTTPS or WSS hostnames. - Reject stdio, executable command, shell argument, environment injection, and unexpected top-level fields. - Merge only the validated property: ```bash jq --argjson todo4 "$VALIDATED_TODO4_CONFIG" \ '.mcpServers.todo4 = $todo4' \ "$MCP_CONFIG" ``` - Preserve a backup and validate the complete resulting JSON before atomically replacing the existing configuration. - If practical, authenticate or sign configuration material independently so that a generic API compromise cannot silently redefine local tools. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/connect.sh:74
Finding
Persistent Agent Credentials May Be Written with Overly Broad File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/connect.sh:74-96` **Vulnerability Type**: Insecure permissions for persistent credential files **Risk Level**: Medium ### Vulnerable Code ```bash if [ -f "$MCP_CONFIG" ]; then # Deep-merge: add/replace mcpServers.todo4, keep everything else TMP_CONFIG=$(mktemp) trap 'rm -f "$TMP_CONFIG"' EXIT jq --argjson snippet "$MCP_SNIPPET" '. * $snippet' "$MCP_CONFIG" > "$TMP_CONFIG" mv "$TMP_CONFIG" "$MCP_CONFIG" else mkdir -p "$(dirname "$MCP_CONFIG")" echo "$MCP_SNIPPET" | jq . > "$MCP_CONFIG" fi # ── Store agent token in ~/.openclaw/.env ──────────────────────────────────── ENV_FILE="${HOME}/.openclaw/.env" mkdir -p "$(dirname "$ENV_FILE")" if [ -f "$ENV_FILE" ]; then # Remove existing TODO4_AGENT_TOKEN line (portable: no sed -i differences) TMP_ENV=$(mktemp) grep -v "^TODO4_AGENT_TOKEN=" "$ENV_FILE" > "$TMP_ENV" || true mv "$TMP_ENV" "$ENV_FILE" fi echo "TODO4_AGENT_TOKEN=${AGENT_TOKEN}" >> "$ENV_FILE" ``` ### Technical Analysis The agent bearer token is persisted in `~/.openclaw/.env`, and MCP connection information is written to `~/.openclaw/mcp_config.json`. The script does not set a restrictive `umask`, explicitly create the directory as mode `0700`, or enforce mode `0600` on either file. For newly created files, effective permissions therefore depend on the process's ambient `umask`. Under a common `022` umask, shell redirection can create files as mode `0644`, allowing other local users to read them. The containing directory may similarly be created as mode `0755`. `mktemp` generally creates restrictive temporary files, which helps when an existing file is replaced. However, that does not protect first-time creation, and the script does not verify or repair the ownership and permissions of pre-existing paths. ### Attack Path 1. The Skill runs in a multi-user environment with a permissive `umask`. 2. `~/.openclaw/.env` or `mcp_config.json` is created with group-readab ...[truncated 1086 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Set a restrictive process mask before creating any sensitive path: ```bash umask 077 ``` - Create and verify the configuration directory with owner-only permissions: ```bash install -d -m 700 "${HOME}/.openclaw" ``` - Write both files through restrictive temporary files in the destination directory, validate them, set mode `0600`, and atomically rename them. - Explicitly enforce permissions after updates: ```bash chmod 600 "$ENV_FILE" "$MCP_CONFIG" ``` - Verify that the destination files are regular files owned by the current user before modifying them. - Reject symbolic links and unexpected path types. - Prefer a platform credential store or operating-system keychain over a plaintext `.env` file where OpenClaw supports one. - Document token revocation and rotation procedures in case local disclosure occurs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/verify.sh:12
Finding
OTP and Bearer Tokens Are Exposed Through Process Arguments and Excessive Credential Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/verify.sh:12-13, 24-28, 58-63`; `scripts/connect.sh:9, 20, 25-29`; `SKILL.md:70, 96` **Vulnerability Type**: Sensitive credential exposure through process arguments and stdout **Risk Level**: Medium ### Vulnerable Code From `scripts/verify.sh`: ```bash if [ $# -lt 2 ]; then echo '{"error":"missing_argument","message":"Usage: verify.sh <email> <code>"}' >&2 exit 2 fi EMAIL="$1" CODE="$2" API_URL="https://todo4.io/api/v1" # Temporary cookie jar — cleaned up on exit (even on error) COOKIE_JAR=$(mktemp) trap 'rm -f "$COOKIE_JAR"' EXIT JSON_BODY=$(jq -n --arg email "$EMAIL" --arg code "$CODE" '{"email": $email, "code": $code}') RESPONSE=$(curl -s -w "\n%{http_code}" --fail-with-body \ -X POST "${API_URL}/auth/verify-otp" \ -H "Content-Type: application/json" \ -c "$COOKIE_JAR" \ -d "$JSON_BODY" 2>&1) || true ``` The verification script then emits both credentials: ```bash # Output combined credentials jq -n \ --arg at "$ACCESS_TOKEN" \ --arg rt "$REFRESH_TOKEN" \ '{"accessToken": $at, "refreshToken": $rt}' exit 0 ``` From `scripts/connect.sh`: ```bash if [ $# -lt 1 ]; then echo '{"error":"missing_argument","message":"Usage: connect.sh <access_token> [agent_name]"}' >&2 exit 2 fi ``` ```bash ACCESS_TOKEN="$1" AGENT_NAME="${2:-OpenClaw}" API_URL="https://todo4.io/api/v1" JSON_BODY=$(jq -n --arg name "$AGENT_NAME" '{"agentName": $name, "agentPlatform": "openclaw"}') RESPONSE=$(curl -s -w "\n%{http_code}" --fail-with-body \ -X POST "${API_URL}/auth/agent-connect" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -d "$JSON_BODY" 2>&1) || true ``` The Skill instructions explicitly invoke the scripts with secrets in argv: ```bash ACCESS_TOKEN=$(scripts/verify.sh <email> <code> | jq -r '.accessToken') ``` ```bash CONNECT_OUT=$(scripts/connect.sh "$ACCESS_TOKEN" <agent_name>) ``` ### Technical Analysis The OTP is supplied as a pos ...[truncated 2010 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not pass OTPs, access tokens, or refresh tokens as positional command-line arguments. - Read secrets from protected stdin or dedicated file descriptors. - For curl, use a restrictive temporary configuration or header file, pass the sensitive request material through stdin where supported, and delete it immediately after use. - Ensure temporary credential files are created with mode `0600` under `umask 077`. - Change `verify.sh` to return only the access token required by the next operation; do not emit the unused refresh token. - Prefer combining verification and connection inside one process so the access token never needs to cross a process boundary. - Disable shell tracing around secret-handling operations and ensure logs redact authorization headers, OTPs, cookies, and response credentials. - Clear shell variables containing secrets as soon as they are no longer needed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
While the mismatch language is overstated, the skill does handle and capture sensitive authentication material (`ACCESS_TOKEN`) and writes connection state via `connect.sh`, yet these side effects are not prominently disclosed in the metadata/summary. Hidden credential-handling behavior can mislead reviewers and users about the sensitivity of the workflow.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
While the mismatch language is overstated, the skill does handle and capture sensitive authentication material (`ACCESS_TOKEN`) and writes connection state via `connect.sh`, yet these side effects are not prominently disclosed in the metadata/summary. Hidden credential-handling behavior can mislead reviewers and users about the sensitivity of the workflow.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
While the mismatch language is overstated, the skill does handle and capture sensitive authentication material (`ACCESS_TOKEN`) and writes connection state via `connect.sh`, yet these side effects are not prominently disclosed in the metadata/summary. Hidden credential-handling behavior can mislead reviewers and users about the sensitivity of the workflow.

Credential Access

High
Category
Privilege Escalation
Content
## Security rules (apply to every step)

- NEVER echo the OTP verification code back to the user. If you must reference it, say "the code you entered."
- NEVER display the access token, refresh token, agent token, or MCP config contents.
- If a script produces unexpected output, summarize the problem in plain English — do not quote raw JSON that may contain secrets.
Confidence
88% confidence
Finding
The skill processes an OTP-derived access token and uses it to establish an agent connection, which is a credential-access pattern even though the text warns not to display the token. If the shell environment, scripts, logs, or subprocess outputs are not tightly controlled, the token could be exposed or reused to access the user's Todo4 account.

Credential Access

High
Category
Privilege Escalation
Content
set -euo pipefail

# Todo4 Onboarding — Step 3: Connect agent and write MCP config
# Calls POST /auth/agent-connect with the user's access token.
# Writes MCP config to ~/.openclaw/mcp_config.json and stores agent token in ~/.openclaw/.env.
# Exit codes: 0 = success, 1 = server/network error, 2 = validation/client error
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
set -euo pipefail

# Todo4 Onboarding — Step 3: Connect agent and write MCP config
# Calls POST /auth/agent-connect with the user's access token.
# Writes MCP config to ~/.openclaw/mcp_config.json and stores agent token in ~/.openclaw/.env.
# Exit codes: 0 = success, 1 = server/network error, 2 = validation/client error
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

MCP Config Access

High
Category
Agent Snooping
Content
# Todo4 Onboarding — Step 3: Connect agent and write MCP config
# Calls POST /auth/agent-connect with the user's access token.
# Writes MCP config to ~/.openclaw/mcp_config.json and stores agent token in ~/.openclaw/.env.
# Exit codes: 0 = success, 1 = server/network error, 2 = validation/client error

if [ $# -lt 1 ]; then
Confidence
92% confidence
Finding
The script writes an MCP configuration file and stores an agent token under the user's home directory, creating persistent local trust material for a new service connection. This is security-sensitive because compromise of those files or unsafe permissions could let another local process or user reuse the Todo4 agent session and access connected capabilities.

Credential Access

High
Category
Privilege Escalation
Content
echo "$MCP_SNIPPET" | jq . > "$MCP_CONFIG"
fi

# ── Store agent token in ~/.openclaw/.env ────────────────────────────────────

ENV_FILE="${HOME}/.openclaw/.env"
mkdir -p "$(dirname "$ENV_FILE")"
Confidence
95% confidence
Finding
The script persists the returned agent access token in plaintext in ~/.openclaw/.env. Plaintext token storage is dangerous because any local compromise, permissive file permissions, backups, or other tooling that reads dotfiles can disclose the token and allow unauthorized reuse of the Todo4 agent session.

Credential Access

High
Category
Privilege Escalation
Content
# ── Store agent token in ~/.openclaw/.env ────────────────────────────────────

ENV_FILE="${HOME}/.openclaw/.env"
mkdir -p "$(dirname "$ENV_FILE")"

if [ -f "$ENV_FILE" ]; then
Confidence
94% confidence
Finding
The code explicitly creates and reuses ~/.openclaw/.env as a credential container, but does not set restrictive permissions or verify that the file is not symlinked or otherwise unsafe. An attacker with local foothold could read the token or exploit unsafe file handling to redirect secret writes to another path.

Credential Access

High
Category
Privilege Escalation
Content
case "$HTTP_CODE" in
  200)
    # Extract access token from Netscape-format cookie jar
    # Format: domain  flag  path  secure  expiry  name  value
    ACCESS_TOKEN_MATCHES=$(grep -c "[[:space:]]access_token[[:space:]]" "$COOKIE_JAR" || true)
    if [ "$ACCESS_TOKEN_MATCHES" -ne 1 ]; then
Confidence
93% confidence
Finding
The script deliberately extracts the access token from the HTTP-only cookie jar into a shell variable, bypassing the protection boundary intended to keep it out of script-visible contexts. Once materialized in process memory and later emitted, the token becomes accessible to logs, subprocesses, crashes, and any downstream consumer, increasing the likelihood of unauthorized account access.

Missing User Warnings

High
Confidence
95% confidence
Finding
The script emits both the access token and refresh token to standard output as JSON, which can expose credentials to logs, calling processes, shell history capture, or downstream tools that are not intended to handle secrets. In an onboarding skill that automatically wires up an MCP connection, these tokens provide authenticated access and are highly sensitive, so printing them materially increases the chance of credential leakage.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger phrase "I want to use Todo4" is broad and mirrors ordinary user intent, which can cause the skill to auto-activate in situations where the user did not clearly consent to installing or onboarding a third-party integration. In an onboarding skill that creates accounts and connects an agent, ambiguous invocation boundaries increase the risk of unintended account actions and disclosure of sensitive setup data such as email and OTP flow details.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The phrase "Set me up with Todo4" is also ambiguous and can overlap with general product inquiries or exploratory requests rather than explicit authorization to begin onboarding. Because the skill performs external account registration and agent connection, weak activation boundaries make unintended execution more dangerous than in a read-only informational skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes shell commands (`scripts/register.sh`, `scripts/verify.sh`, `scripts/connect.sh`) but does not declare an explicit tool scope such as permissions or allowed-tools. That weakens containment and reviewability, making it easier for the skill to run code capabilities without clear policy boundaries.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger condition includes broad phrases and an open-ended 'similar request' clause, which increases the risk of accidental or inappropriate activation. In this skill, activation can initiate account registration, OTP delivery, token acquisition, and agent connection, so false positives have meaningful security and privacy consequences.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The run condition is ambiguous because it treats generic requests to 'install', 'connect', 'onboard', or 'start using' Todo4 as authorization to begin a sensitive authentication flow. In context, that can cause the agent to solicit an email and trigger OTP/account actions without sufficiently confirming the user's precise intent.

Session Persistence

Medium
Category
Rogue Agent
Content
> Open your tasks in the browser — you'll be signed in automatically (link is single-use, valid for 5 minutes):
     > <WEB_LOGIN_URL>
  3. SAY (verbatim):
     > Or just tell me to create your first task — e.g., "Create a task to review the Q2 report by Friday."

  If `$WEB_LOGIN_URL` is empty, SAY (verbatim):
  > Done — I'm connected to your Todo4 account and the MCP tools are ready. Try: "Create a task to review the Q2 report by Friday."
Confidence
86% confidence
Finding
The skill generates and shares a one-time auto-login URL that creates browser session access to the user's Todo4 account. Even though it is single-use and short-lived, exposing session-bearing links in chat increases the risk of interception, unintended forwarding, or misuse by anyone with access to the conversation.

Session Persistence

Medium
Category
Rogue Agent
Content
> Or just tell me to create your first task — e.g., "Create a task to review the Q2 report by Friday."

  If `$WEB_LOGIN_URL` is empty, SAY (verbatim):
  > Done — I'm connected to your Todo4 account and the MCP tools are ready. Try: "Create a task to review the Q2 report by Friday."

  Then WAIT for the user's first task request. When it arrives, use the Todo4 MCP tools (e.g., `create_task`) to fulfill it.
- `2` with HTTP 422 → SAY: "Your account has reached the maximum number of connected agents. You can manage them at todo4.io." STOP.
Confidence
80% 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
#!/usr/bin/env bash
set -euo pipefail

# Todo4 Onboarding — Step 3: Connect agent and write MCP config
# Calls POST /auth/agent-connect with the user's access token.
# Writes MCP config to ~/.openclaw/mcp_config.json and stores agent token in ~/.openclaw/.env.
# Exit codes: 0 = success, 1 = server/network error, 2 = validation/client error
Confidence
90% confidence
Finding
The script establishes persistent session state by writing MCP configuration and an agent token to disk so the agent remains connected after onboarding. In the context of an agent skill, persistence increases risk because a one-time user action results in durable delegated access that may outlive user awareness or consent.

External Transmission

Medium
Category
Data Exfiltration
Content
JSON_BODY=$(jq -n --arg name "$AGENT_NAME" '{"agentName": $name, "agentPlatform": "openclaw"}')

RESPONSE=$(curl -s -w "\n%{http_code}" --fail-with-body \
  -X POST "${API_URL}/auth/agent-connect" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
Confidence
70% 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
JSON_BODY=$(jq -n --arg email "$EMAIL" '{"email": $email}')

RESPONSE=$(curl -s -w "\n%{http_code}" --fail-with-body \
  -X POST "${API_URL}/auth/register-passwordless" \
  -H "Content-Type: application/json" \
  -d "$JSON_BODY" 2>&1) || true
Confidence
70% 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
JSON_BODY=$(jq -n --arg email "$EMAIL" --arg code "$CODE" '{"email": $email, "code": $code}')

RESPONSE=$(curl -s -w "\n%{http_code}" --fail-with-body \
  -X POST "${API_URL}/auth/verify-otp" \
  -H "Content-Type: application/json" \
  -c "$COOKIE_JAR" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.