Back to skill

Security audit

cryptofolio

Security checks for vulnerabilities and agentic risk

Overview

CryptoFolio is a real crypto portfolio tool, but it handles sensitive financial data and credentials with under-scoped local server, cloud sync, and AI-provider data flows that users should review carefully.

Install only if you are comfortable storing detailed crypto portfolio records locally and, if enabled, in your own Cloudflare KV. Avoid starting the visualization server on untrusted networks, use a strong unique token, prefer Cloudflare secrets over hardcoded Worker tokens, verify any sync URL before use, keep backups, and do not enter paid AI API keys or upload financial documents unless you accept sending that content directly to the selected AI provider.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/serve.mjs:63
Finding
Unauthenticated Network-Accessible Portfolio API<![CDATA[ ## Vulnerability Details **File Location**: `scripts/serve.mjs:63-94, 115-121` **Vulnerability Type**: Missing authentication, unrestricted CORS, and unsafe network binding **Risk Level**: High ### Vulnerable Code ```js const server = createServer((req, res) => { const url = new URL(req.url, `http://localhost:${PORT}`); // CORS res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } // API: 获取数据 if (url.pathname === '/api/data' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, data: loadData() })); return; } // API: 保存数据 if (url.pathname === '/api/data' && req.method === 'POST') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { try { const data = JSON.parse(body); saveData(data); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true })); } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: e.message })); } }); return; } }); server.listen(PORT, () => { console.log(`\n🚀 CryptoFolio 可视化界面已启动`); console.log(`📊 打开浏览器访问: http://localhost:${PORT}`); console.log(`📁 数据文件: ${DATA_FILE}`); console.log(`\n按 Ctrl+C 停止服务器\n`); }); ``` ### Technical Analysis The local API provides unauthenticated read and write access to the user's complete portfolio. It also returns `Access-Control-Allow-Origin: *`, allowing scripts from arbitrary origins to read API responses and submit JSON requests if they can reach the service. The call to `server.listen(PORT)` does not explicitly bind the service to `127.0.0.1`. Depending on ...[truncated 1420 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind explicitly to the loopback interface: ```js server.listen(PORT, '127.0.0.1', () => { // Startup message }); ``` 2. Generate an unpredictable per-session authentication token and require it for every API request. 3. Restrict `Access-Control-Allow-Origin` to the exact trusted UI origin instead of using `*`. 4. Validate the `Origin` and `Host` headers and reject unexpected values. 5. Use `SameSite=Strict` cookies or an explicit authorization header together with CSRF protection. 6. Separate read and write permissions if the UI does not always require write access. 7. Warn and fail safely if the selected port is already exposed through a proxy or non-loopback interface. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/serve.mjs:82
Finding
Unbounded and Unvalidated Portfolio Write Requests<![CDATA[ ## Vulnerability Details **File Location**: `scripts/serve.mjs:82-94` **Vulnerability Type**: Unbounded request buffering and missing schema validation **Risk Level**: Medium ### Vulnerable Code ```js // API: 保存数据 if (url.pathname === '/api/data' && req.method === 'POST') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { try { const data = JSON.parse(body); saveData(data); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true })); } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: e.message })); } }); return; } ``` ### Technical Analysis The server appends every incoming chunk to an in-memory string without imposing a maximum body size. A sufficiently large request can cause excessive memory consumption. If parsing succeeds, the resulting data is written to disk without validating its structure, field types, collection sizes, or numeric bounds. Because the endpoint is also unauthenticated, a remote or browser-based attacker who can reach the service can directly exploit these weaknesses. Even when deployed as a loopback-only service, another local process could trigger the same behavior. ### Attack Path 1. The victim starts the local CryptoFolio server. 2. The attacker establishes a connection to `/api/data`. 3. The attacker sends a very large POST request, causing the server to continuously concatenate data in memory. 4. The Node.js process consumes excessive memory and may terminate or become unresponsive. 5. Alternatively, the attacker submits syntactically valid JSON with malformed portfolio structures or extremely large arrays. 6. The malformed state is persisted and may subsequently break the web interface or consume substantial disk space. ### Impact Assessment Successful exploitation can cause denial of service, memory exhaustion, disk consumpt ...[truncated 218 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a conservative request-body limit before concatenating chunks: ```js const MAX_BODY_SIZE = 1024 * 1024; let size = 0; let body = ''; req.on('data', chunk => { size += chunk.length; if (size > MAX_BODY_SIZE) { res.writeHead(413); res.end('Payload Too Large'); req.destroy(); return; } body += chunk; }); ``` 2. Validate the submitted object against a strict schema before saving it. 3. Limit array lengths and string sizes and require finite numeric values. 4. Reject unknown top-level properties where they are unnecessary. 5. Write data atomically through a temporary file followed by a rename. 6. Retain a recoverable backup before replacing the current portfolio. 7. Combine these controls with authentication and loopback-only binding. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cryptofolio.mjs:35
Finding
Cloud Bearer Token Stored and Distributed Through Plaintext Channels<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cryptofolio.mjs:35-40`; `cloudflare-worker/worker.js:4`; `README.md:126-129`; `cloudflare-worker/README.md:36-43` **Vulnerability Type**: Plaintext credential storage and hardcoded secret guidance **Risk Level**: Medium ### Vulnerable Code ```js function saveCloudConfig(config) { if (!existsSync(DATA_DIR)) { mkdirSync(DATA_DIR, { recursive: true }); } writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8'); } ``` ```js // Cloudflare Worker for CryptoFolio // 部署到 Cloudflare Workers 作为数据存储后端 const TOKEN = 'your-secret-token'; // ⚠️ 修改为你的密码 ``` ```bash node ~/.openclaw/workspace/skills/cryptofolio/scripts/cryptofolio.mjs setup \ --url "https://cryptofolio-api.xxx.workers.dev" \ --token "你的密码" ``` ### Technical Analysis The CLI saves the full cloud configuration, including the bearer token, to a normal JSON file without explicitly setting restrictive filesystem permissions. The deployment instructions also encourage users to place the secret directly in Worker source code and pass it as a command-line argument. These channels can expose the credential through: - Source repositories or copied Worker source. - Shell history. - Process argument listings while the setup command is running. - Backups and filesystem indexing. - Access by other local users if file permissions are permissive. - Accidental inclusion of the configuration file in support bundles. The token grants both read and write access to the complete cloud portfolio, so it should be managed as a high-value secret. ### Attack Path 1. The user follows the documentation and places the token in Worker source or runs setup with `--token`. 2. The token is retained in source history, shell history, process arguments, or `cryptofolio-config.json`. 3. An attacker with access to one of these plaintext channels recovers the token. 4. The attacker identifies the corresponding Worker URL from the same configuration o ...[truncated 456 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the Worker token as a Cloudflare secret: ```bash wrangler secret put CRYPTOFOLIO_TOKEN ``` 2. Read the secret from the Worker environment: ```js const expectedToken = env.CRYPTOFOLIO_TOKEN; ``` 3. Do not place real tokens in source code or committed configuration. 4. Avoid accepting secrets through command-line arguments. Use protected interactive input, standard input, or environment injection. 5. Create local credential files with mode `0600`: ```js writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf8', mode: 0o600 }); ``` 6. Prefer the operating system's credential store or keychain for persistent secrets. 7. Update the documentation and rotate tokens previously stored through insecure channels. 8. Implement token rotation and revocation procedures. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cryptofolio.mjs:80
Finding
Cloud Synchronization Sends Credentials and Financial Data to Unvalidated Endpoints<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cryptofolio.mjs:80-107, 658-663` **Vulnerability Type**: Missing URL scheme and destination validation **Risk Level**: Medium ### Vulnerable Code ```js // 从云端加载数据 async function loadFromCloud(config) { try { const res = await fetch(`${config.apiUrl}/api/data`, { headers: { 'Authorization': `Bearer ${config.token}` }, }); if (res.ok) { const d = await res.json(); if (d.ok && d.data) return d.data; } } catch (e) { log(`云端读取失败: ${e.message}`, 'yellow'); } return null; } // 保存到云端 async function saveToCloud(config, data) { try { const res = await fetch(`${config.apiUrl}/api/data`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.token}`, }, body: JSON.stringify(data), }); return res.ok; } catch (e) { log(`云端保存失败: ${e.message}`, 'yellow'); return false; } } ``` ```js if (opts.url && opts.token) { const config = { apiUrl: opts.url.replace(/\/$/, ''), token: opts.token, }; ``` ### Technical Analysis The configured API URL is accepted as an arbitrary string. The code does not require HTTPS, constrain the destination to an expected Worker domain, or warn before uploading the existing local portfolio. If an HTTP URL is used, the bearer token and portfolio data can be exposed to network interception. If a malicious or misleading HTTPS endpoint is configured, the endpoint legitimately receives the bearer token and full dataset. The unauthenticated health check is not proof that the endpoint is a trusted CryptoFolio Worker. Sending portfolio data is necessary for the optional synchronization feature, but accepting any destination and transport without confirmation exceeds a safe minimum-privilege design. ### Attack Path 1. An attacker supplies or recommends a malicious synchronization URL, or the user mistypes the intended URL. 2. ...[truncated 930 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the endpoint with `new URL()` and reject invalid URLs. 2. Require `https:` for all non-loopback endpoints. 3. Permit HTTP only for explicit development use on `127.0.0.1` or `localhost`. 4. Display the normalized hostname and require confirmation before the first portfolio upload. 5. Use an authenticated challenge or server identity check rather than relying only on the public health endpoint. 6. Consider an allowlist for expected Cloudflare Worker domains while still supporting documented custom domains. 7. Provide an explicit preview of the categories of data that will be uploaded. 8. Do not automatically replace local data with remote data until the user confirms the initial synchronization direction. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.html:386
Finding
Long-Lived Cloud and AI Credentials Stored in Browser Local Storage<![CDATA[ ## Vulnerability Details **File Location**: `index.html:386-390, 2855-2858` **Vulnerability Type**: Persistent browser storage of sensitive credentials **Risk Level**: Medium ### Vulnerable Code ```js function getCFConfig(){ try{ return JSON.parse(localStorage.getItem(CF_CONFIG_KEY)||'{}'); }catch(e){ return {}; } } function setCFConfig(cfg){ localStorage.setItem(CF_CONFIG_KEY, JSON.stringify(cfg)); } function hasCF(){ const c=getCFConfig(); return !!(c.apiUrl && c.token); } ``` ```js function getAIConfig(){ try{ return JSON.parse(localStorage.getItem('cryptofolio_ai')||'{}'); }catch(e){ return {}; } } function setAIConfig(cfg){ localStorage.setItem('cryptofolio_ai', JSON.stringify(cfg)); updateApiKeyBtn(); } ``` ### Technical Analysis The browser client stores the Cloudflare bearer token and third-party AI API key in `localStorage`. These values persist across browser sessions and are available to all JavaScript executing under the same origin. The README recommends using a GitHub Pages deployment. Under such a deployment, any cross-site scripting vulnerability, compromised same-origin asset, malicious future application change, or unauthorized browser extension with page access could read the credentials. The statement in the interface that the AI key is stored only in the local browser is technically true, but it does not communicate that the key remains accessible to same-origin scripts. ### Attack Path 1. The user enters a Cloudflare token or paid AI-provider key. 2. The application stores the credential in `localStorage`. 3. A malicious or compromised script executes under the application origin. 4. The script reads `cryptofolio_cf` and `cryptofolio_ai`. 5. The script transmits the extracted values to an attacker. 6. The attacker uses the Cloudflare token to access portfolio data or uses the AI key to make billable API requests. ### Impact Assessment Compromise of the Cloudflare token permits complete read and write access to clo ...[truncated 259 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Keep credentials in memory for the duration of the browser session rather than persisting them in `localStorage`. 2. If persistence is required, use a trusted backend that keeps provider credentials server-side. 3. Isolate CryptoFolio on a dedicated origin that does not host unrelated applications. 4. Apply a strict Content Security Policy that restricts scripts and network destinations. 5. Remove inline event handlers and inline scripts so that a nonce- or hash-based CSP can be enforced. 6. Clearly warn users about the risks of storing long-lived API keys in a browser. 7. Use narrowly scoped, revocable, low-quota AI credentials where providers support them. 8. Provide visible credential deletion and rotation controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cryptofolio.mjs:568
Finding
Spreadsheet Formula Injection in Exported Portfolio Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cryptofolio.mjs:568-619` **Vulnerability Type**: CSV formula injection and incomplete CSV escaping **Risk Level**: Medium ### Vulnerable Code ```js // 账户汇总 csv += '=== 账户汇总 ===\n'; csv += '账户名称,类型,持仓数量,总市值\n'; state.accounts.forEach((acc) => { const positions = state.positions.filter((p) => p.accountId === acc.id); const totalValue = positions.reduce((sum, p) => { const v = p.currentValue || (+p.amount || 0) * (+p.currentPrice || 0); return sum + v; }, 0); csv += `"${acc.name}","${TYPE_LABEL[acc.type] || acc.type}",${positions.length},${totalValue.toFixed(2)}\n`; }); csv += '\n=== 持仓明细 ===\n'; csv += '账户,资产,数量,均价,现价,市值,备注\n'; state.positions.forEach((p) => { const acc = state.accounts.find((a) => a.id === p.accountId); const value = p.currentValue || (+p.amount || 0) * (+p.currentPrice || 0); csv += `"${acc?.name || ''}","${p.asset}",${p.amount},${p.avgCost || 0},${p.currentPrice || 0},${value.toFixed(2)},"${p.note || ''}"\n`; }); csv += '\n=== 交易记录 ===\n'; csv += '日期,账户,资产,方向,数量,价格,手续费,盈亏,备注\n'; state.trades.forEach((t) => { const acc = state.accounts.find((a) => a.id === t.accountId); csv += `"${t.date}","${acc?.name || ''}","${t.asset}","${t.side}",${t.amount},${t.price},${t.fee || 0},${t.pnl || ''},"${t.note || ''}"\n`; }); const finalOutput = format === 'xlsx' ? output.replace('.xlsx', '.csv') : output; writeFileSync(finalOutput, '\ufeff' + csv, 'utf8'); success(`报告已导出到: ${finalOutput}`); ``` ### Technical Analysis User-controlled values are inserted into CSV cells without escaping embedded double quotes and without neutralizing spreadsheet formula prefixes. Spreadsheet applications may interpret a cell beginning with `=`, `+`, `-`, or `@` as a formula even when the value is quoted in CSV. Potentially attacker-controlled fields include account names, assets, dates, and notes. These values can originate from manual input, imported files, browser state, or cloud ...[truncated 1181 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement a single CSV cell encoder that: - Converts values to strings. - Escapes every double quote as `""`. - Wraps the resulting value in double quotes. - Neutralizes cells beginning with `=`, `+`, `-`, `@`, tab, carriage return, or line feed. ```js function safeCsvCell(value) { let text = String(value ?? ''); if (/^[=+\-@\t\r\n]/.test(text)) { text = `'${text}`; } return `"${text.replace(/"/g, '""')}"`; } ``` 2. Apply the encoder to every exported string field, including account names, assets, dates, types, and notes. 3. Validate numeric fields and export only finite numeric values. 4. Use a maintained XLSX generation library for actual XLSX output rather than writing CSV under an XLSX option. 5. Add regression tests for formula prefixes, embedded quotes, commas, and multiline values. 6. Warn users when exporting data that originated from untrusted cloud or imported sources. ]]>
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (45)

Credential Access

High
Category
Privilege Escalation
Content
placeholder="https://cryptofolio-api.你的用户名.workers.dev"
        style="font-family:var(--mono);font-size:12px"/>
    </div>
    <div class="field"><label>Access Token</label>
      <input id="cf-token" type="password" value="${esc(cfg.token||'')}"
        placeholder="你在 Cloudflare 设置的密码"
        style="font-family:var(--mono);font-size:12px"/>
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Hidden Instructions

High
Category
Prompt Injection
Content
<div style="display:flex;gap:6px;margin-bottom:16px">${catBtns}</div>
    <input type="hidden" id="f-fincat" value="${cat}"/>

    <!-- 币本位理财 -->
    <div id="fin-fields-COIN_STAKE" style="display:${cat==='COIN_STAKE'?'block':'none'}">
      <div class="grid2">
        <div class="field"><label>账户</label><select id="fcs-acc">${accOpts(f?.accountId||'')}</select></div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
    </div>

    <!-- 出金 -->
    <div id="txn-withdraw" style="display:${d.type==='WITHDRAW'?'block':'none'}">
      <div class="grid2">
        <div class="field"><label>来源账户</label>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The UI claims API keys 'will not be uploaded to any server,' but the code immediately uses those keys in Authorization headers to third-party model APIs. This is materially misleading and could cause users to expose provider credentials under false assumptions about how they are used.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README promotes cloud sync for sensitive financial portfolio data but does not clearly disclose that user data is transmitted to and stored on third-party Cloudflare infrastructure. For an asset-tracking skill, this omission can cause users to underestimate confidentiality, jurisdiction, retention, and provider-access risks before enabling sync.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The documented Worker accepts authenticated POST requests that overwrite the entire stored portfolio dataset, but the README does not prominently warn that compromise, reuse, or misconfiguration of the bearer token enables full remote modification or destruction of asset records. In a financial tracking context, this can lead to silent tampering, loss of integrity, and accidental data wipeouts.

Session Persistence

Medium
Category
Rogue Agent
Content
1. 点击左上角返回,或左侧菜单点 **Workers & Pages**
2. 左侧菜单点击 **KV**
3. 点击 **Create a namespace**
4. 名字输入 `cryptofolio-data`
5. 点击 **Add**
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.

Session Persistence

Medium
Category
Rogue Agent
Content
1. 点击左上角返回,或左侧菜单点 **Workers & Pages**
2. 左侧菜单点击 **KV**
3. 点击 **Create a namespace**
4. 名字输入 `cryptofolio-data`
5. 点击 **Add**
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 network access and likely environment/config handling, but the manifest does not declare any tool scope or allowed-tools restrictions. This increases the blast radius if the skill is executed in a permissive runtime, because commands for cloud sync and local serving could access networked resources without explicit user- or platform-visible scoping.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest/description presents the skill as a local asset-recording/export assistant but the documentation also supports cloud synchronization to a remote Cloudflare Worker. Omitting this material capability from the top-level description undermines informed consent, because users may not realize the skill can transmit portfolio records off-device.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill encourages cloud synchronization of sensitive crypto portfolio data without warning that holdings, transactions, and account information may be sent to a remote service. In this context, the data is highly sensitive financial metadata, so silent or poorly disclosed transmission could lead to privacy loss, profiling, or compromise if the endpoint or token is mishandled.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The skill instructs the agent to start a local HTTP server in the background and open a browser, which goes beyond simple portfolio recording/export and introduces active service exposure on the local machine. Even if bound to localhost, launching services and opening URLs expands the attack surface and may expose sensitive financial data to the browser or other local processes.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The markdown directs the skill to reply in concise Chinese, which imposes a specific language on all users. There is no opt-in, alternative language choice, or stated reason that the skill must be Chinese-only.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
cd cloudflare-worker
wrangler kv:namespace create "KV"
```

记下输出的 `id`,例如:`{ binding = "KV", id = "xxxx-xxxx-xxxx" }`
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.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
cd cloudflare-worker
wrangler kv:namespace create "KV"
```

记下输出的 `id`,例如:`{ binding = "KV", id = "xxxx-xxxx-xxxx" }`
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.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README instructs users to hardcode a secret token directly into source code (`worker.js`) and presents it as the normal setup path without any warning about secure secret handling. This increases the chance the credential will be committed to version control, leaked through screenshots or logs, or reused insecurely across environments.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation shows storing a bearer token in a local OpenClaw JSON config file without any guidance about file permissions, secret storage, or credential rotation. If that config is backed up, synced, shared, or committed accidentally, the API token could be exposed and used to access the user's portfolio data.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This worker exposes GET and POST HTTP endpoints that receive and return portfolio data over the network, including persistence to backend storage. The code does not include any user-facing notice that requests send data to a remote Cloudflare Worker backend or that returned data is fetched from remote storage.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The POST handler accepts arbitrary JSON from the request and stores it in Cloudflare KV, which is a persistent file/data write operation. While the code comments describe saving data, there is no user-facing warning, confirmation, or visible disclosure in the code about persisting user portfolio data.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill description implies local asset recording/export, but the code supports synchronization to arbitrary remote Cloudflare endpoints and also persists data through a localhost API. This expands the trust boundary significantly: sensitive portfolio, trade, and account data can be transmitted off-device without being clearly disclosed in the manifest.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Portfolio state is automatically posted to a configured cloud endpoint in saveToAPI/pushToCloud, but the transmission point itself does not force a clear warning or confirmation. Because the data includes accounts, positions, trades, finance records, and transfers, unintended synchronization can leak a highly sensitive financial profile.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The AI entry flow encourages users to upload screenshots, PDFs, and text describing trades, then transmits that content to external model providers. The UI mentions networking only vaguely and does not provide a clear, immediate warning that financial records and uploaded files leave the device.

External Transmission

Medium
Category
Data Exfiltration
Content
claude: {
    name: 'Claude (Anthropic)',
    placeholder: 'sk-ant-api03-...',
    url: 'https://api.anthropic.com/v1/messages',
    docs: 'console.anthropic.com',
    badge: '🤖',
    supportsImage: true,
Confidence
95% confidence
Finding
The code directly transmits user prompts and optional uploaded files to Anthropic's API from the browser. In this skill context, those prompts may contain private financial data, so the external transmission is security-relevant and should be treated as sensitive data egress.

External Transmission

Medium
Category
Data Exfiltration
Content
openai: {
    name: 'OpenAI / ChatGPT',
    placeholder: 'sk-...',
    url: 'https://api.openai.com/v1/chat/completions',
    docs: 'platform.openai.com/api-keys',
    badge: '🟢',
    supportsImage: true,
Confidence
94% confidence
Finding
The code is prepared to send user financial text and files to OpenAI endpoints. Even if browser CORS limits some deployments, the implemented behavior still constitutes an external transmission path for sensitive user data.

External Transmission

Medium
Category
Data Exfiltration
Content
minimax: {
    name: 'MiniMax',
    placeholder: 'sk-xxxxxxxxxxxxxxxx',
    url: 'https://api.minimax.io/v1/chat/completions',
    docs: 'platform.minimax.io',
    badge: '🇨🇳',
    supportsImage: false,
Confidence
94% confidence
Finding
The skill includes direct transmission capability to MiniMax for AI parsing. This exposes user-entered portfolio and transaction information to another third-party processor beyond the locally described functionality.

Static analysis

Detected: suspicious.env_credential_access, suspicious.secret_argv_exposure

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/cryptofolio.mjs:20

Instructions pass high-value credentials through process argv.

Critical
Code
suspicious.secret_argv_exposure
Location
SKILL.md:38