Back to skill

Security audit

IMAP SMTP Email

Security checks for vulnerabilities and agentic risk

Overview

The email skill is mostly transparent, but its optional watcher can expose raw mailbox credentials to a spawned OpenClaw process and has some local file-safety weaknesses.

Review before installing if this mailbox is sensitive. The basic IMAP/SMTP functions are purpose-aligned, but avoid enabling the optional watcher unless you trust the spawned OpenClaw environment with mailbox credentials. Use an app-specific password, restrict allowed read/write directories, avoid symlinks in those directories, and run setup only in a trusted local account.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/email-watch-lite.js:371
Finding
OpenClaw Agent Subprocess Inherits Raw Email Credentials## Vulnerability Details **File Location**: `scripts/email-watch-lite.js:371-376` **Related Credential Loading**: `scripts/email-watch-lite.js:6`, `scripts/imap.js:30-33` **Vulnerability Type**: Excessive subprocess privilege and secret exposure **Risk Level**: High ### Vulnerable Code ```js const { checkEmails } = require('./imap'); ``` Importing `scripts/imap.js` executes the following credential-loading code: ```js const EMAIL_ENV_DEFAULT = path.join(os.homedir(), '.openclaw', 'credentials', 'imap-smtp-mail.env'); const EMAIL_ENV_FILE = process.env.EMAIL_ENV_FILE || EMAIL_ENV_DEFAULT; require('dotenv').config({ path: EMAIL_ENV_FILE }); ``` The resulting environment is then passed without filtering to the AI-agent subprocess: ```js const agentOutput = execFileSync(openclawBin, agentArgs, { cwd: WORKSPACE_ROOT, encoding: 'utf8', stdio: 'pipe', env: process.env, timeout: CHILD_PROCESS_TIMEOUT_MS, }); ``` ### Technical Analysis Requiring `./imap` loads the configured dotenv file into the watcher's global `process.env`. This environment can contain `IMAP_PASS`, `SMTP_PASS`, mailbox usernames, server addresses, and unrelated secrets inherited from the parent OpenClaw process. The watcher subsequently starts `openclaw agent` with `env: process.env`, granting the entire subprocess direct access to every loaded credential. The agent needs to process pending email UIDs and can invoke the narrowly scoped mail scripts, but it does not need unrestricted possession of raw IMAP and SMTP passwords. Although `execFileSync` avoids shell command injection, it does not mitigate secret inheritance. Any compromised OpenClaw binary, loaded plugin, agent-accessible local tool, or other code executing in that subprocess can read the credentials directly from its environment. ### Attack Path 1. The watcher imports `scripts/imap.js`. 2. `scripts/imap.js` loads `~/.openclaw/credentials/imap-smtp-mail.e ...[truncated 1042 chars]
Remediation
## Remediation Suggestions - Replace `env: process.env` with an explicit allowlist containing only variables required by the OpenClaw CLI, such as `PATH`, `HOME`, locale settings, and narrowly selected OpenClaw configuration. - Explicitly remove `IMAP_PASS`, `SMTP_PASS`, and other secret-bearing variables before creating the subprocess. - Avoid loading the credential file into the watcher's global environment. Refactor IMAP configuration loading so credentials are held in a local configuration object. - Prefer a narrowly scoped local broker or capability interface that allows the agent to fetch designated UIDs without exposing mailbox credentials. - Run the optional watcher under a dedicated account with minimal filesystem and environment access. - Document the subprocess trust boundary and test that child processes cannot observe email passwords.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/imap.js:42
Finding
Attachment Write Allowlist Can Be Bypassed Through a Symlinked Ancestor## Vulnerability Details **File Location**: `scripts/imap.js:42-47`, `scripts/imap.js:487-496` **Vulnerability Type**: Symlink-based filesystem allowlist bypass **Risk Level**: Medium ### Vulnerable Code The path validator falls back to lexical resolution when the requested directory does not yet exist: ```js let resolved; try { resolved = fs.realpathSync(expanded); } catch { resolved = path.resolve(expanded); } ``` The validated path is subsequently created and used for attachment writes: ```js const resolvedDir = validateWritePath(outputDir); if (!fs.existsSync(resolvedDir)) { fs.mkdirSync(resolvedDir, { recursive: true }); } const downloaded = []; for (const attachment of parsed.attachments) { if (specificFilename && attachment.filename !== specificFilename) { continue; } if (attachment.content) { const filePath = path.join(resolvedDir, sanitizeFilename(attachment.filename)); fs.writeFileSync(filePath, attachment.content); ``` ### Technical Analysis `fs.realpathSync` correctly resolves symbolic links only when the complete target path exists. When the requested output directory is nonexistent, the function catches the error and uses `path.resolve`, which performs only lexical normalization and does not resolve symlink components. An output path can therefore appear to be under an allowed directory during validation while an existing ancestor is a symlink pointing outside that directory. `fs.mkdirSync` and `fs.writeFileSync` then follow the symlink during actual filesystem operations. Filename sanitization prevents direct `../` traversal in attachment names, but it does not protect the destination directory from symlink traversal. ### Attack Path 1. An attacker or another local process creates a symlink under an allowed directory, for example `workspace/tmp/link` pointing to an external writable directory. 2. The attacker requests an attachment do ...[truncated 1066 chars]
Remediation
## Remediation Suggestions - Resolve the nearest existing ancestor of a nonexistent destination and verify that its canonical path remains inside an allowed directory. - Reject destination paths containing symlink components, for example by checking each component with `lstatSync`. - Create the destination directory and then call `realpathSync` again before writing any attachment. - Repeat the allowlist check on the final canonical directory immediately before every write. - Open files using restrictive and exclusive flags where overwriting is not intended. - Consider using directory file descriptors and platform-supported no-follow semantics to reduce time-of-check/time-of-use races. - Add regression tests covering symlinks directly under allowed directories and symlinks in nested ancestor paths.

T09 · Insecure Skill Coding Practices

Warning
Location
setup.sh:145
Finding
Credential Setup Uses Non-Atomic, Symlink-Following File Creation## Vulnerability Details **File Location**: `setup.sh:145-177` **Vulnerability Type**: Insecure secret-file creation **Risk Level**: Medium ### Vulnerable Code ```bash # Create credentials file CRED_DIR="${HOME}/.openclaw/credentials" CRED_FILE="${CRED_DIR}/imap-smtp-mail.env" mkdir -p "$CRED_DIR" cat > "$CRED_FILE" << EOF # IMAP Configuration IMAP_HOST=$IMAP_HOST IMAP_PORT=$IMAP_PORT IMAP_USER=$EMAIL IMAP_PASS=$PASSWORD IMAP_TLS=$IMAP_TLS IMAP_REJECT_UNAUTHORIZED=$REJECT_UNAUTHORIZED IMAP_MAILBOX=INBOX # SMTP Configuration SMTP_HOST=$SMTP_HOST SMTP_PORT=$SMTP_PORT SMTP_SECURE=$SMTP_SECURE SMTP_USER=$EMAIL SMTP_PASS=$PASSWORD SMTP_FROM=$EMAIL SMTP_FROM_NAME=$FROM_NAME SMTP_REPLY_TO=$EMAIL SMTP_REJECT_UNAUTHORIZED=$REJECT_UNAUTHORIZED IMAP_SAVE_SENT=true # File access whitelist (security) ALLOWED_READ_DIRS=${ALLOWED_READ_DIRS:-$HOME/.openclaw/workspace,$HOME/.openclaw/workspace/exports,$HOME/.openclaw/workspace/out,$HOME/.openclaw/workspace/tmp} ALLOWED_WRITE_DIRS=${ALLOWED_WRITE_DIRS:-$HOME/.openclaw/workspace/exports,$HOME/.openclaw/workspace/tmp} EOF echo "" echo "Created credentials at: $CRED_FILE" chmod 600 "$CRED_FILE" ``` ### Technical Analysis Shell redirection creates or truncates the credential file before `chmod 600` is executed. Its initial permissions are determined by the process umask, so under a permissive umask the plaintext mailbox password can temporarily be readable by other local users. The credentials directory is created without explicitly enforcing mode `700`. In addition, shell redirection follows an existing symbolic link at `imap-smtp-mail.env`. The script does not verify that the target is a regular file owned by the current user before overwriting it. The final `chmod 600` is useful but occurs too late to make initial creation secure and can itself operate on the target of a preexisting symlink. ### Attack Path Possible exploitation ...[truncated 987 chars]
Remediation
## Remediation Suggestions - Set `umask 077` before creating either the credential directory or file. - Create the credential directory with mode `700` and verify that it is owned by the current user. - Refuse to proceed if the credential path is a symbolic link or is not a regular user-owned file. - Write credentials to a securely created temporary file in the same directory, set mode `600`, and atomically rename it into place. - Use a safe exclusive-creation mechanism to prevent races with another local process. - Apply cleanup traps so temporary files containing secrets are removed on errors or interruption. - Avoid retaining the password in shell variables longer than necessary and unset it after successful setup.
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (57)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code clearly matches much of the read-oriented portion of the description: it connects to IMAP, checks unread/all mail, fetches full messages, searches by common criteria, lists mailboxes, parses attachments (including PDF/Excel/CSV extraction), and downloads attachments with write-path restrictions. However, the declared description says the skill can both read and send email via IMAP/SMTP and mentions an optional inbox watcher that can forward alerts via OpenClaw CLI. None of the provided code performs SMTP operations, composes/sends mail, watches for new mail events, or invokes OpenClaw. Additionally, the code includes mailbox-state mutation functions (mark read/unread), which are not mentioned in the declared description. Because important declared capabilities are absent and one undeclared capability is present, this is a description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description presents a broader IMAP/SMTP mail skill focused on reading inboxes and sending mail. This specific code chunk is mainly an SMTP send CLI plus helper features: draft approval preview, SMTP test, reply metadata via IMAP fetch, saving sent copies to IMAP, and local contact resolution. It also accesses local contacts and file contents, which are undeclared resource interactions beyond basic SMTP sending. While some IMAP-related support exists, the primary behavior here is not full inbox reading/search/downloading/watching as described. Therefore the supplied code chunk does not accurately match the declared description.

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/imap.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `node scripts/smtp.js ...`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/email-watch-lite.js:280

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/imap.js:32