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

Content
View full analysis
{ 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
View remediation
{ // 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

Content
View full analysis
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
View remediation
{ 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

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cryptofolio.mjs:80
Finding

Cloud Synchronization Sends Credentials and Financial Data to Unvalidated Endpoints

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
index.html:386
Finding

Long-Lived Cloud and AI Credentials Stored in Browser Local Storage

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cryptofolio.mjs:568
Finding

Spreadsheet Formula Injection in Exported Portfolio Reports

Content
View full analysis
{ 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
View remediation
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
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · index.html (reported line 491)May include surrounding context.

html
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"/>

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · index.html (reported line 1705)May include surrounding context.

html
<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>

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · index.html (reported line 2286)May include surrounding context.

html
</div>
    </div>

    <!-- 出金 -->
    <div id="txn-withdraw" style="display:${d.type==='WITHDRAW'?'block':'none'}">
      <div class="grid2">
        <div class="field"><label>来源账户</label>

Intent-Code Divergence

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · README.md (reported line 76)May include surrounding context.

md
1. 点击左上角返回,或左侧菜单点 **Workers & Pages**
2. 左侧菜单点击 **KV**
3. 点击 **Create a namespace**
4. 名字输入 `cryptofolio-data`
5. 点击 **Add**

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · README.md (reported line 76)May include surrounding context.

md
1. 点击左上角返回,或左侧菜单点 **Workers & Pages**
2. 左侧菜单点击 **KV**
3. 点击 **Create a namespace**
4. 名字输入 `cryptofolio-data`
5. 点击 **Add**

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · cloudflare-worker/README.md (reported line 21)May include surrounding context.

bash
cd cloudflare-worker
wrangler kv:namespace create "KV"

记下输出的 id,例如:{ binding = "KV", id = "xxxx-xxxx-xxxx" }

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · cloudflare-worker/README.md (reported line 21)May include surrounding context.

bash
cd cloudflare-worker
wrangler kv:namespace create "KV"

记下输出的 id,例如:{ binding = "KV", id = "xxxx-xxxx-xxxx" }

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
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.

Content

Scanner excerpt · index.html (reported line 2820)May include surrounding context.

html
claude: {
    name: 'Claude (Anthropic)',
    placeholder: 'sk-ant-api03-...',
    url: 'https://api.anthropic.com/v1/messages',
    docs: 'console.anthropic.com',
    badge: '🤖',
    supportsImage: true,

External Transmission

Medium
Category
Data Exfiltration
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.

Content

Scanner excerpt · index.html (reported line 2829)May include surrounding context.

html
openai: {
    name: 'OpenAI / ChatGPT',
    placeholder: 'sk-...',
    url: 'https://api.openai.com/v1/chat/completions',
    docs: 'platform.openai.com/api-keys',
    badge: '🟢',
    supportsImage: true,

External Transmission

Medium
Category
Data Exfiltration
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.

Content

Scanner excerpt · index.html (reported line 2838)May include surrounding context.

html
minimax: {
    name: 'MiniMax',
    placeholder: 'sk-xxxxxxxxxxxxxxxx',
    url: 'https://api.minimax.io/v1/chat/completions',
    docs: 'platform.minimax.io',
    badge: '🇨🇳',
    supportsImage: false,