Back to skill

Security audit

Smart Email

Security checks for vulnerabilities and agentic risk

Overview

This email assistant is purpose-aligned, but it handles mailbox credentials and private email contents with materially under-disclosed and unsafe security controls.

Review before installing. This skill can read private email, store reusable mailbox and OAuth credentials, run a web admin UI, and send email contents to an external AI provider. Use only in a trusted local environment, prefer OAuth over app passwords, avoid custom IMAP servers until TLS validation is fixed, and do not rely on the current encrypted/local-only privacy claims.

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
store.js:15
Finding
Mailbox credentials and access tokens are stored in plaintext despite an encryption claim<![CDATA[ ## Vulnerability Details **File Location**: `store.js:15-23`, `store.js:31-42`, `store.js:45-61`; related configuration storage at `config.js:38-43` **Vulnerability Type**: Plaintext storage of sensitive credentials **Risk Level**: High ### Vulnerable Code ```js db.exec(` CREATE TABLE IF NOT EXISTS accounts ( email TEXT PRIMARY KEY, password TEXT, email_type TEXT DEFAULT 'gmail', auth_type TEXT DEFAULT 'password', access_token TEXT, refresh_token TEXT, token_expires INTEGER DEFAULT 0, created_at INTEGER ); `); ``` ```js function addAccount(email, password, emailType) { db.prepare(` INSERT INTO accounts (email, password, email_type, auth_type, created_at) VALUES (?, ?, ?, 'password', ?) ON CONFLICT(email) DO UPDATE SET password = excluded.password, email_type = excluded.email_type, auth_type = 'password', access_token = NULL, refresh_token = NULL, token_expires = 0 `).run(email, password, emailType || 'gmail', Date.now()); } ``` ```js function addOAuthAccount(email, emailType, accessToken, refreshToken, tokenExpires) { db.prepare(` INSERT INTO accounts (email, password, email_type, auth_type, access_token, refresh_token, token_expires, created_at) VALUES (?, '', ?, 'oauth', ?, ?, ?, ?) ON CONFLICT(email) DO UPDATE SET email_type = excluded.email_type, auth_type = 'oauth', access_token = excluded.access_token, refresh_token = excluded.refresh_token, token_expires = excluded.token_expires `).run(email, emailType || 'outlook', accessToken, refreshToken, tokenExpires, Date.now()); } ``` ```js function set(key, value) { const file = loadFileConfig(); file[key] = value; const dir = path.dirname(CONFIG_FILE); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(CONFIG_FILE, JSON.stringify(file, null, 2)); _cache = file; } ``` ### Technical Analysis The SQLite schema stores mailbox ...[truncated 1560 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store passwords and refresh tokens in an operating-system credential manager or secret-management service. - If local database storage is unavoidable, use authenticated encryption such as AES-GCM with a key stored separately from the database. - Encrypt each sensitive value independently and include account identity as authenticated associated data. - Create secret files with owner-only permissions, such as mode `0600`, and verify directory permissions. - Securely migrate and remove existing plaintext records, including residual SQLite WAL and backup files. - Avoid retaining access tokens when they can be regenerated from a securely protected refresh token. - Correct the documentation so that it accurately describes storage protections. - Add automated tests that verify secrets do not appear as plaintext in database or configuration files. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
imap.js:61
Finding
TLS certificate validation is disabled for every custom IMAP server<![CDATA[ ## Vulnerability Details **File Location**: `imap.js:61-70` **Vulnerability Type**: Improper certificate validation **Risk Level**: High ### Vulnerable Code ```js const client = new ImapFlow({ host: server.host, port: server.port, secure: true, auth: authObj, logger: false, tls: { rejectUnauthorized: emailType === 'custom' ? false : true }, }); ``` ### Technical Analysis For any account represented as a custom IMAP server, `rejectUnauthorized` is set to `false`. Encryption without certificate authentication does not establish the identity of the remote server. Consequently, a self-signed, expired, hostname-mismatched, or attacker-generated certificate is accepted. The authentication object supplied to the connection contains either the mailbox password or an OAuth access token: ```js if (typeof authConfig === 'object' && authConfig.accessToken) { authObj = { user: email, accessToken: authConfig.accessToken }; } else { authObj = { user: email, pass: authConfig }; } ``` Custom IMAP support is legitimate for the declared functionality, but disabling verification is not required and exceeds acceptable security tradeoffs for a credential-bearing connection. ### Attack Path 1. A user configures a custom-domain mailbox. 2. An attacker gains a network interception position, controls DNS resolution, compromises a gateway, or redirects the configured hostname. 3. The attacker presents an arbitrary TLS certificate while impersonating the IMAP service. 4. The client accepts the certificate because verification is disabled. 5. The client authenticates to the impersonated service with the mailbox username and password or access token. 6. The attacker captures the credential and may return forged mailbox data to the Skill. ### Impact Assessment An attacker may obtain reusable mailbox credentials or bearer tokens and use them to read email directly from the legitimate provider. The attacker can also manipulate the email data supplied to the ...[truncated 88 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Always set `rejectUnauthorized: true`. - Rely on the platform trust store for publicly trusted IMAP servers. - For private certificate authorities, support an explicit CA certificate through the `ca` TLS option. - Optionally support certificate or public-key pinning for managed deployments. - Validate that the certificate hostname matches the configured IMAP hostname. - Do not silently downgrade validation. Return a clear error explaining how the administrator can install or configure the correct CA. - Add integration tests proving that self-signed and hostname-mismatched certificates are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
ai.js:8
Finding
Email content and API credentials can be transmitted to an unrestricted AI endpoint<![CDATA[ ## Vulnerability Details **File Location**: `ai.js:8-43`, `ai.js:51-89`; configuration endpoint at `server.js:96-102` **Vulnerability Type**: Unrestricted sensitive-data transmission and privacy misrepresentation **Risk Level**: High ### Vulnerable Code ```js function getApiConfig() { return { key: get('ai_api_key', ''), base: get('ai_api_base', 'https://api.deepseek.com'), model: get('ai_model', 'deepseek-chat'), }; } ``` ```js async function summarizeEmail(from, subject, body) { const prompt = `你是邮件助手。请用中文简洁解读以下邮件,包括: 1. 一句话概述 2. 是否需要用户处理/回复 3. 重要程度(🔴紧急/🟡一般/🟢无需关注) 发件人: ${from} 主题: ${subject} 正文: ${body || '(无法读取正文)'}`; const api = getApiConfig(); if (!api.key) return fallback(from, subject); try { const res = await fetch(`${api.base}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${api.key}`, }, body: JSON.stringify({ model: api.model, messages: [{ role: 'user', content: prompt }], max_tokens: 500, temperature: 0.3, }), }); ``` ```js const emailList = emails.map((e, i) => `[${i + 1}] 发件人: ${e.from || e.fromAddr || '未知'}\n主题: ${e.subject || '(无主题)'}\n正文摘要: ${(e.body || '').substring(0, 200)}` ).join('\n\n'); ``` ```js if (pathname === '/api/config' && req.method === 'POST') { const { key, value } = JSON.parse(body); if (!key) return json({ error: 'key required' }, 400); config.set(key, value); return json({ success: true }); } ``` ### Technical Analysis Individual summarization sends the sender, subject, and up to the fetched email body to an OpenAI-compatible endpoint. Batch summarization sends content excerpts from all fetched messages. The configured API key is attached as a bearer credential. The destination is entirely configuration-controlled. There is no HTTPS requirement, origin allowlist, confirmation of destination, content redaction, or warning whe ...[truncated 1325 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Clearly disclose that cloud summarization uploads email content and identify the receiving provider. - Require explicit, informed consent before the first transmission and whenever the destination changes. - Enforce HTTPS and reject URLs containing user information, unexpected ports, or unsupported schemes. - Allowlist supported provider origins by default. Put custom endpoints behind a clearly marked advanced option. - Display the exact destination host before each first use. - Minimize submitted data by redacting addresses, secrets, links, quoted history, signatures, and unnecessary body content. - Offer a local-model or no-upload mode. - Restrict `/api/config` to a defined schema rather than allowing arbitrary keys. - Separate endpoint configuration from secret management and test for redirects to untrusted origins. - Update the local-only documentation claim to accurately explain the AI data flow and retention implications. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
server.js:44
Finding
Web mailbox API can be exposed over plaintext HTTP using a reusable URL bearer token<![CDATA[ ## Vulnerability Details **File Location**: `server.js:44-52`, `server.js:68-77`, `server.js:303-307`; client token handling at `ui.html:233-280` **Vulnerability Type**: Insecure authentication token transport and network exposure **Risk Level**: High ### Vulnerable Code ```js function checkAuth(req) { const url = new URL(req.url, `http://localhost:${PORT}`); const cookieToken = (req.headers.cookie || '').split(';') .map(c => c.trim().split('=')) .find(([k]) => k === 'token')?.[1]; const queryToken = url.searchParams.get('token'); const expected = getOrCreateToken(); return cookieToken === expected || queryToken === expected; } ``` ```js if (pathname === '/api/login' && req.method === 'POST') { const { token } = JSON.parse(body); if (token === getOrCreateToken()) { res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': `token=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=31536000`, }); res.end(JSON.stringify({ success: true })); } else { json({ error: 'Invalid token' }, 401); } return; } ``` ```js server.listen(PORT, () => { const token = getOrCreateToken(); console.log(`\n📧 Email Skill Web UI`); console.log(` http://localhost:${PORT}?token=${token}\n`); console.log(` Share this URL to give access (token included).\n`); }); ``` ```js function getToken() { const params = new URLSearchParams(location.search); return localStorage.getItem('email_token') || params.get('token') || ''; } ``` ```js if (urlToken) { try { await api('/api/login', { token: urlToken }); localStorage.setItem('email_token', urlToken); history.replaceState(null, '', location.pathname); } catch {} } ``` ### Technical Analysis The HTTP server calls `listen(PORT)` without explicitly binding to a loopback address. Depending on the runtime and network configuration, this can listen on all available interfaces. The interface is served over plaintext HTTP. The long-lived b ...[truncated 1586 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Bind to `127.0.0.1` or `::1` by default, for example `server.listen(PORT, '127.0.0.1')`. - Require an explicit security warning and configuration switch before enabling remote access. - Do not put authentication tokens in URLs or advise users to share tokenized links. - Remove bearer-token storage from `localStorage`. - Use short-lived, randomly generated server-side sessions after login. - Set cookies with `HttpOnly`, `Secure`, and an appropriate `SameSite` policy when HTTPS is used. - Provide TLS directly or require deployment behind a correctly configured HTTPS reverse proxy for remote access. - Add rate limiting and temporary lockout to the login endpoint. - Rotate the Web token and invalidate existing sessions after suspected disclosure. - Apply CSRF protection to state-changing endpoints and validate request origins. - Restrict configuration endpoints to approved keys and values to reduce the consequences of session compromise. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
cli.js:245
Finding
Mailbox passwords and API keys are accepted as command-line arguments<![CDATA[ ## Vulnerability Details **File Location**: `cli.js:245-266`, `cli.js:367-380`; documented invocation at `SKILL.md:96-105`, `SKILL.md:133-141` **Vulnerability Type**: Sensitive information exposure through process arguments and command history **Risk Level**: Medium ### Vulnerable Code ```js const password = getFlag('password'); if (!password) { console.log(JSON.stringify({ error: 'Password required: --password <APP_PASSWORD>' })); return; } return await setupPassword(email, password, detected); ``` ```js const password = getFlag('password'); if (!password) { console.log(JSON.stringify({ action: 'setup_need_password', email, detected: detected.label, message: `Run again with: setup ${email} --password <APP_PASSWORD>`, })); return; } await setupPassword(email, password, detected); ``` ```js function cmdConfig() { const key = args[1]; const value = args[2]; if (!key) { // Show all config const all = config.getAll(); ``` The documented interface requires commands such as: ```bash node <SKILL_DIR>/cli.js setup <email> --password <APP_PASSWORD> node <SKILL_DIR>/cli.js config ai_api_key <KEY> ``` ### Technical Analysis Command-line arguments are not an appropriate transport for secrets. Depending on the operating system and execution environment, process arguments may be observable through process inspection tools while the command is running. They may also be retained in shell history, automation logs, chat transcripts, process accounting systems, telemetry, or Agent tool-call records. The Skill not only supports this pattern but instructs users and calling agents to use it for mailbox app passwords and AI API keys. ### Attack Path 1. A user or Agent runs the documented setup or configuration command with a secret in the argument list. 2. The complete command is retained in shell history, process telemetry, an orchestration log, or a chat/tool transcript; alternatively, another local user inspects the pro ...[truncated 529 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Read passwords and API keys from an interactive hidden prompt rather than `process.argv`. - Support secret input through a protected file descriptor or standard input without echo. - Prefer an operating-system credential store for both entry and persistence. - If environment variables are supported for automation, document their exposure limitations and avoid printing them. - Remove commands containing real secrets from examples generated for users or Agents. - Ensure errors and diagnostic logs never include secret values. - Advise existing users to clear affected shell history and rotate credentials previously supplied on command lines. ]]>
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (40)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The public description frames the skill as a chat-based email assistant, but the documentation also exposes a standalone web UI, HTTP administration surface, persistent credential storage, and token-based access URL. This mismatch is security-relevant because reviewers and users may authorize the skill without realizing it opens an additional remotely reachable management interface and stores sensitive tokens.

Missing User Warnings

High
Confidence
96% confidence
Finding
The skill does not clearly warn users that email contents may be sent to an external AI service for summarization. Because email often contains highly sensitive personal or business information, omission of this disclosure undermines informed consent and can lead to unintended third-party data sharing.

Ssd 3

High
Confidence
97% confidence
Finding
The setup flow explicitly instructs users to provide email passwords or app passwords through chat. Collecting mailbox credentials in a chat-mediated workflow is highly dangerous because chat systems, bot logs, intermediaries, or compromised clients can capture reusable secrets that grant direct access to the user's email account.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The documentation claims all data stays local and is not uploaded, yet AI summarization necessarily sends email content to an external AI API. This is a direct privacy and trust violation: users may expose confidential emails under the false belief that no third party receives them.

Missing User Warnings

High
Confidence
94% confidence
Finding
The function sends full email-derived content, including sender, subject, and body, to a third-party AI endpoint configured by `ai_api_base`. In an email-assistant context this can expose sensitive personal, business, financial, or authentication-related content to an external processor without any visible consent, minimization, or trust restriction in this file, creating a significant privacy and data-governance risk.

Missing User Warnings

High
Confidence
96% confidence
Finding
The batch summarization path transmits snippets from multiple emails in one request to an external LLM API, amplifying the amount and variety of sensitive data disclosed in a single call. Because this skill is specifically designed to process inbox contents across providers and chat platforms, a compromise, misconfiguration, or untrusted API endpoint could expose a broad set of private communications at once.

Missing User Warnings

High
Confidence
99% confidence
Finding
The read command always summarizes the full message body, meaning simply reading an email causes its contents to be transmitted to the AI service automatically. In an email assistant context this is especially dangerous because it turns a basic viewing action into undisclosed third-party exfiltration of potentially highly sensitive correspondence.

Missing User Warnings

High
Confidence
99% confidence
Finding
Disabling TLS verification for custom IMAP connections without any user-facing disclosure removes an important security guarantee while giving users no indication their credentials and mailbox contents may be exposed to interception. Because this skill processes highly sensitive email data, the lack of warning increases the likelihood of unsafe use and silent compromise.

Known Vulnerable Dependency: ip-address==10.1.0 — 2 advisory(ies): CVE-2026-69192 (ip-address: Address4 decodes leading-zero octets as decimal while resolvers deco); CVE-2026-42338 (ip-address has XSS in Address6 HTML-emitting methods)

High
Category
Supply Chain
Confidence
95% confidence
Finding
The lockfile pins ip-address 10.1.0, which the provided advisory data identifies as affected by parsing inconsistencies and an HTML-emitting XSS issue. In this email skill, the package is pulled in via socks and is likely not a primary UI surface, so exploitability depends on whether untrusted IP strings or rendered HTML output are exposed; however, keeping a known vulnerable transitive dependency is still a real supply-chain risk.

Known Vulnerable Dependency: nodemailer==8.0.2 — 10 advisory(ies): CVE-2026-82661 (Nodemailer: CRLF injection in Nodemailer List-* header comments allows arbitrary); GHSA-2x7j-588g-ccc2 (Nodemailer: Quadratic (O(n²)) time complexity in addressparser allows remote den); GHSA-8m3c-c648-2xjj (Nodemailer: resolveContent() on a MailMessage bypasses disableFileAccess/disable) +7 more

High
Category
Supply Chain
Confidence
98% confidence
Finding
The lockfile includes nodemailer 8.0.2, and the referenced advisories indicate multiple known issues including header injection, denial of service in address parsing, and content resolution bypasses. This is especially concerning in an email assistant skill that processes and may generate email-related content across chat platforms, because untrusted message metadata, addresses, or attachments could flow into vulnerable mail-handling paths and enable spoofing, abuse of outbound mail generation, or service disruption.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code stores plaintext account passwords directly in SQLite and returns full account rows through retrieval helpers, making credential disclosure trivial if the database file or process memory is accessed. Because this skill handles email accounts, exposed passwords may enable direct login to mailboxes and potentially other services where users reuse credentials.

Hidden Instructions

High
Category
Prompt Injection
Content
<body>

<div class="container" id="app">
  <!-- Login -->
  <div id="login-view" class="login-box" style="display:none">
    <h2>📧 Email Assistant</h2>
    <p style="color:var(--text2);margin-bottom:20px">输入访问令牌登录</p>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Credential Access

High
Category
Privilege Escalation
Content
<div id="login-view" class="login-box" style="display:none">
    <h2>📧 Email Assistant</h2>
    <p style="color:var(--text2);margin-bottom:20px">输入访问令牌登录</p>
    <input type="password" id="login-token" placeholder="Access Token" onkeydown="if(event.key==='Enter')doLogin()">
    <button class="btn btn-primary" style="width:100%" onclick="doLogin()">登录</button>
  </div>
Confidence
97% confidence
Finding
The application accepts an access token through both a password field and, more critically, a URL query parameter via getToken()/init(). Tokens in URLs are commonly exposed through browser history, logs, screenshots, copied links, and Referer headers, making credential leakage much more likely in a chat-integrated email assistant.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares no explicit tool scope or permission boundaries even though its documented behavior requires network access and likely reads configuration or environment-derived secrets. Missing scope declarations increase the chance that a host agent invokes the skill with broader privileges than users expect, weakening containment for a credential-handling email integration.

Natural-Language Policy Violations

Medium
Confidence
83% confidence
Finding
The skill instructions and reply-format guidance are written to operate in Chinese, including trigger phrases, setup flow, and output recommendations, but do not offer users a language choice. This can violate language/locale policy expectations when the skill is presented to multilingual users or integrated into non-Chinese chat environments.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger phrases are broad enough to overlap with ordinary conversation, which can cause unintended invocation of a skill that accesses sensitive email data. In a chat environment, accidental activation can reveal inbox contents or start setup flows without clear user intent.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The prompt explicitly instructs the model to summarize emails in Chinese, and the batch prompt also requires Chinese output. This is a natural-language locale policy issue because the file imposes a specific language without any opt-in, configuration, or documented regional justification.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The batch prompt ends with an instruction to output in Chinese, with no indication that the user can choose another language. This creates a locale-policy concern because the skill hardcodes a language preference rather than offering a configurable or opt-in choice.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
When --summarize is used, full email body content is sent to the summarization backend without any visible consent, warning, or data-classification check. Because emails routinely contain sensitive personal, financial, or corporate information, silent transfer to an external AI provider creates a real confidentiality and compliance risk.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Digest generation sends batches of email data to the AI summarizer without an obvious warning that message contents may leave the local environment. Batch processing can amplify exposure by transmitting many emails at once, increasing both privacy impact and regulatory risk.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The setup flow accepts direct email passwords/app passwords and persists them for later mailbox access. For a chat-integrated assistant, storing reusable mailbox credentials expands the blast radius of any compromise and is riskier than delegated OAuth, especially when users may provide secrets through chat or automation layers not designed for credential handling.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The `remove` command deletes a configured account immediately after receiving the email argument, with no confirmation or cautionary message. Removing stored account configuration can be irreversible from the user's perspective and should have some disclosure or confirmation.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The config command can return arbitrary stored configuration values by key, and the single-key read path does not mask sensitive entries. In a chat-facing email assistant, this creates a direct secret-disclosure primitive for items like API keys, OAuth client settings, or other stored credentials if an untrusted user can invoke the CLI through the agent.

Missing User Warnings

Medium
Confidence
78% confidence
Finding
The function accepts either an access token or password and uses it to connect to a mailbox, then fetches sender, recipient, subject, date, and body content from unread emails. This is sensitive account and message access, but this code includes no prompt, logging, or explanatory comment warning users that mailbox contents and credentials are being used.

Context-Inappropriate Capability

Medium
Confidence
99% confidence
Finding
For custom IMAP hosts, the code sets `tls.rejectUnauthorized` to `false`, which disables certificate validation and permits man-in-the-middle interception of email credentials and message contents. In an email assistant handling sensitive inbox data, this materially weakens transport security and expands trust to arbitrary, potentially spoofed servers.

Static analysis

No suspicious patterns detected.