Back to skill

Security audit

OKKI Email Sync

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent CRM-sync purpose, but it automatically writes sensitive email and quote data into OKKI and local temp files with weak customer-matching safeguards that can misfile data.

Install only in an environment where OKKI credentials are least-privileged, executable paths are controlled, and operators explicitly accept automatic CRM logging of email excerpts and quotation details. Require stronger customer-match validation or human review before enabling automatic writes, and move deduplication and unmatched logs out of shared /tmp storage.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/okki-sync.js:397
Finding
Outgoing email synchronization can associate sensitive content with the wrong CRM customer<![CDATA[ ## Vulnerability Details **File Location**: `scripts/okki-sync.js`, lines 397-401 **Vulnerability Type**: Incorrect customer identity selection **Risk Level**: High ### Vulnerable Code ```javascript const customer = await matchCustomer( emailData.from || emailData.to, emailData.subject, emailData.body ); ``` The selected customer is subsequently used to create the CRM trail: ```javascript const trailResult = await createEmailTrail(customer.company_id, { ...emailData, uid: emailData.uid }); ``` The trail includes message metadata and content: ```javascript const content = `${directionLabel}\n` + `主题:${emailData.subject}\n` + `时间:${emailData.date}\n` + `发件人:${emailData.from}\n` + `收件人:${emailData.to}\n` + `摘要:${emailData.body ? emailData.body.substring(0, 200) : '(无内容)'}${attachmentList}`; ``` ### Technical Analysis `syncEmailToOkki` always prefers `emailData.from` whenever it is present. It does not consider `emailData.direction`. For an inbound email, the sender may correctly identify the customer. For an outbound email, however, `from` normally contains the local employee or organization address, while the external customer is identified by `to`. The documented integration supplies both fields and sets `direction: 'out'`, but the implementation does not use that direction when selecting the address for customer matching. Once the wrong address is selected, its domain or vector-search result determines the CRM company. The module then creates a trail under that company containing: - The email subject - Sender and recipient addresses - The first 200 characters of the message body - Attachment filenames - The message date and direction This is an authorization-boundary failure at the data-association layer: data intended for one customer can be written into another customer's CRM record. ### Attack Path 1. An outbound message is sent with the local organization's address in `from` and the intended customer in `to`. 2. Th ...[truncated 1237 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Select the external party according to the message direction: ```javascript const customerAddress = emailData.direction === 'out' ? emailData.to : emailData.from; if (!customerAddress) { return { success: false, reason: 'missing_customer_address', message: 'Unable to determine the external customer address' }; } const customer = await matchCustomer( customerAddress, emailData.subject, emailData.body ); ``` Additional hardening should include: 1. Parse and normalize mailbox structures instead of assuming `from` and `to` are single plain strings. 2. For outbound messages with multiple recipients, require an explicit policy rather than assigning all content to the first inferred customer. 3. Exclude known internal domains from customer matching. 4. Refuse synchronization when both endpoints are internal or when the external party is ambiguous. 5. Verify that the selected CRM company corresponds to the intended external address before creating a trail. 6. Add tests for inbound, outbound, internal-only, multiple-recipient, and malformed-address cases. 7. Minimize trail content where possible, particularly body excerpts and attachment names. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/okki-sync.js:207
Finding
Unverified partial-domain results can route email and quotation data to an unrelated CRM company<![CDATA[ ## Vulnerability Details **File Location**: `scripts/okki-sync.js`, lines 207-214 **Vulnerability Type**: Insecure customer matching and missing result validation **Risk Level**: High ### Vulnerable Code ```javascript // 部分匹配 if (result.data.length > 0) { return { company_id: result.data[0].company_id || result.data[0].id, name: result.data[0].name, match_type: 'domain_partial', confidence: 0.7 }; } ``` The surrounding search initially attempts an exact match: ```javascript const exactMatch = result.data.find(company => { const companyDomain = extractDomain(company.website || company.email || ''); return companyDomain === domain; }); if (exactMatch) { return { company_id: exactMatch.company_id || exactMatch.id, name: exactMatch.name, match_type: 'domain_exact', confidence: 0.95 }; } ``` If no exact match exists, the implementation accepts the first keyword-search result without any additional verification. ### Technical Analysis `searchByDomain` calls the OKKI CLI with the email domain as a general keyword. When no returned company has a website or email domain exactly equal to the requested domain, the function nevertheless selects `result.data[0]`. There is no validation that the first result: - Owns the requested domain - Has a verified email address on that domain - Is uniquely associated with the keyword - Meets a minimum trustworthy confidence threshold - Is the intended recipient of the synchronized communication The hardcoded confidence value of `0.7` does not represent a validated similarity score and is not used to prevent automatic writes. Consequently, any nonempty inexact keyword result is treated as sufficient authorization to write email or quotation details into that company's CRM record. ### Attack Path 1. An email address uses a non-public domain that does not exactly match any returned CRM company's website or email. 2. The domain is submitted to `company list -k` as a keyword. ...[truncated 1528 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not automatically create CRM records from an unverified partial-domain match. The safest behavior is to return `null` unless a normalized, verified domain matches: ```javascript if (!exactMatch) { return null; } ``` A more comprehensive hardening plan should include: 1. Normalize domains using a robust mailbox and URL parser. 2. Compare registrable domains carefully and account for subdomains without accepting arbitrary substring matches. 3. Require a verified customer email or website domain before automatic trail creation. 4. Treat multiple matching companies as ambiguous and require explicit resolution. 5. Do not convert result ordering into an authorization decision. 6. Apply a documented minimum confidence threshold to vector results and require corroborating identifiers. 7. Route low-confidence matches to a review queue instead of automatically writing sensitive data. 8. Log only non-sensitive identifiers for failed or ambiguous matches. 9. Add regression tests covering substring collisions, similar company names, stale CRM data, subdomains, and multiple search results. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/okki-sync.js:35
Finding
Predictable shared temporary files permit state tampering, information disclosure, and symlink-based file writes<![CDATA[ ## Vulnerability Details **File Location**: `scripts/okki-sync.js`, lines 35-39 **Vulnerability Type**: Unsafe temporary-file handling **Risk Level**: Medium ### Vulnerable Code The module uses fixed filenames in the system temporary directory: ```javascript // 去重记录文件(可通过环境变量覆盖) processedFile: process.env.OKKI_SYNC_RECORD_FILE || path.join(os.tmpdir(), 'okki-sync-processed.json'), // 未匹配日志文件 unmatchedLog: path.join(os.tmpdir(), 'okki-unmatched-emails.log'), ``` The deduplication file is read and overwritten directly: ```javascript function loadProcessedRecords() { try { if (fs.existsSync(CONFIG.processedFile)) { const data = fs.readFileSync(CONFIG.processedFile, 'utf8'); return JSON.parse(data); } } catch (e) { console.error('加载已处理记录失败:', e.message); } return {}; } function saveProcessedRecord(uid, metadata = {}) { try { const records = loadProcessedRecords(); records[uid] = { processed_at: new Date().toISOString(), ...metadata }; fs.writeFileSync(CONFIG.processedFile, JSON.stringify(records, null, 2)); return true; } catch (e) { console.error('保存已处理记录失败:', e.message); return false; } } ``` The unmatched-email log is also appended to without secure creation or symlink validation: ```javascript function logUnmatchedEmail(email, reason) { try { const timestamp = new Date().toISOString(); const logLine = `${timestamp} | ${email} | ${reason}\n`; fs.appendFileSync(CONFIG.unmatchedLog, logLine); } catch (e) { console.error('写入未匹配日志失败:', e.message); } } ``` ### Technical Analysis The paths are predictable and located in a shared temporary directory. The implementation does not: - Create a private per-user or per-application directory - Set restrictive file permissions explicitly - Use exclusive file creation - Verify that the target is a regular file rather than a symbolic link - Use atomic replacement for the JSON state - Lock the file during read- ...[truncated 2923 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Store runtime state in a private application state directory rather than a shared temporary directory: 1. Create a per-user or per-service directory with mode `0700`. 2. Create state and log files with mode `0600`. 3. Reject symbolic links by checking with `lstat` and, where supported, opening files with no-follow semantics. 4. Use exclusive creation when initially creating files. 5. Write JSON to a new file in the same private directory, flush it, and atomically rename it over the previous state. 6. Use an inter-process lock or transactional datastore for read-modify-write operations. 7. Validate that loaded JSON is a plain object with expected field types and bounded size. 8. Avoid recording email addresses and other personal data unless operationally necessary. 9. Rotate and securely delete logs according to a defined retention policy. 10. If `OKKI_SYNC_RECORD_FILE` remains configurable, validate that the configured path is inside an approved directory. For example, initialize a private directory before use: ```javascript const stateDir = path.join(os.homedir(), '.local', 'state', 'okki-email-sync'); fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 }); const processedFile = path.join(stateDir, 'processed.json'); const unmatchedLog = path.join(stateDir, 'unmatched.log'); ``` File creation and updates should additionally specify restrictive modes and use locking and atomic replacement; changing the directory alone does not address concurrent-write races. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (14)

Ae1

High
Category
analysis-evasion
Content
**主文件:** `scripts/okki-sync.js`(同步镜像至 `$WORKSPACE/skills/imap-smtp-email/okki-sync.js`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**主文件:** `scripts/okki-sync.js`(同步镜像至 `$WORKSPACE/skills/imap-smtp-email/okki-sync.js`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**主文件:** `scripts/okki-sync.js`(同步镜像至 `$WORKSPACE/skills/imap-smtp-email/okki-sync.js`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**主文件:** `scripts/okki-sync.js`(同步镜像至 `$WORKSPACE/skills/imap-smtp-email/okki-sync.js`)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README describes automatic synchronization of emails and quotation events into OKKI and mentions local files used for deduplication and unmatched logs, but it does not present this as an explicit user-facing warning about data handling and persistence. In a skill that processes potentially sensitive business communications, this omission can lead to unintentional disclosure, retention, or compliance issues because operators may enable it without realizing customer email content and metadata are being written to CRM records and local filesystem logs.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill describes environment-dependent behavior and integration with external scripts and services, but it does not declare any explicit tool scope, permissions, or allowed-tools boundaries. In an agent ecosystem, that can cause overbroad execution assumptions and unintended access to environment-backed resources, especially because the module references workspace paths, CLI execution, temp files, and CRM synchronization.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill automatically writes email content and quotation events into an external CRM and also logs unmatched cases locally, but the user-facing description does not clearly warn about this data disclosure and retention. Because the synced fields include message bodies, recipients, attachments metadata, and quotation details, users may unknowingly expose sensitive customer, commercial, or personal data to third-party systems and local logs.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The module executes external Python programs whose paths are controllable via environment variables (OKKI_CLI_PATH, VECTOR_SEARCH_PATH, PYTHON_VENV_PATH). In an agent or automation environment, this allows path substitution to attacker-controlled code, turning a CRM sync skill into a generic code-execution primitive with access to process environment, filesystem, and CRM credentials.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The vector-search fallback transmits email address, subject, and a body excerpt to an external search script without transparency or trust guarantees. In context, that script path is also configurable, so sensitive message content may be exposed to untrusted code or secondary storage outside the intended CRM workflow.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill sends email metadata and body excerpts into OKKI trail records without any consent, minimization, or policy check. Because email bodies may contain sensitive business, personal, or regulated data, automatic forwarding to CRM can create unauthorized data propagation and retention risks.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill writes quotation details, including product and pricing information, directly into CRM records without an approval or notice mechanism. Quotations often contain commercially sensitive data, so silent synchronization can leak confidential pricing and deal terms into broader CRM visibility scopes.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The skill contains core operational description and instructions in Chinese, while not stating that the skill is intended only for Chinese-speaking users or offering an alternative language. This can amount to a language/locale policy issue because the documentation effectively forces one language without opt-in or justification.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The file’s natural-language documentation, console output, and CLI usage messages are presented in Chinese only, with no indication that users may choose another language. This can violate language/locale policy when a skill imposes a specific language without opt-in or documented justification.

Context-Inappropriate Capability

Low
Confidence
77% confidence
Finding
The stated purpose is operational synchronization into OKKI CRM. The exported and CLI-accessible runTest routine performs environment/connectivity checks against OKKI and the vector-search backend, which is auxiliary diagnostics rather than core sync behavior. While not inherently dangerous, it is outside the manifest's stated user-facing purpose.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/okki-sync.js:63