Back to skill

Security audit

Asset Management

Security checks for vulnerabilities and agentic risk

Overview

This crypto portfolio skill is mostly purpose-aligned, but it handles sensitive financial data with unsafe local/cloud APIs, persistent browser-stored credentials, and under-disclosed third-party AI processing.

Review this skill carefully before installing. Use only data you are comfortable storing in local files, browser storage, and optionally Cloudflare KV; avoid configuring real AI keys or cloud sync until the local API is authenticated and loopback-only, XSS-safe rendering is fixed, Worker secrets replace hardcoded tokens, and the UI clearly warns before sending financial documents or portfolio context to third parties. Rotate any Cloudflare token or AI key already used with this version.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/serve.mjs:65
Finding
Unauthenticated Local Portfolio API Exposes Sensitive Financial Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/serve.mjs:65-91, 113` **Vulnerability Type**: Unauthenticated cross-origin API with non-loopback exposure **Risk Level**: High ### Complete Vulnerable Code ```javascript 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; } ``` ```javascript 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 HTTP API permits both reading and replacing the complete portfolio without authentication. It also returns `Access-Control-Allow-Origin: *`, authorizes cross-origin `GET`, `POST`, and `OPTIONS` requests, and accepts JSON writes after an unrestricted preflight request. Calling `server.listen(PORT)` without a hostname does not explicit ...[truncated 2127 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind the server explicitly to loopback: ```javascript server.listen(PORT, '127.0.0.1', () => { // ... }); ``` 2. Remove wildcard CORS. If cross-origin access is not necessary, do not return CORS headers. Otherwise, validate `Origin` against a strict allowlist. 3. Generate an unpredictable session token when the server starts and require it on every API request. 4. Validate `Host`, `Origin`, and `Sec-Fetch-Site` headers as defense in depth against DNS rebinding and cross-site requests. 5. Validate incoming data against a strict schema. Reject unknown properties, invalid identifiers, unsafe colors, unexpected types, and excessively long strings. 6. Enforce a conservative request-body size limit and terminate oversized requests before parsing. 7. Add safe-write behavior, including temporary-file replacement and backups, to reduce corruption risk. 8. Return appropriate security headers, including a restrictive Content Security Policy, `X-Content-Type-Options: nosniff`, and `Cache-Control: no-store` for API responses. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
index.html:574
Finding
Stored Cross-Site Scripting Through Unvalidated Portfolio Records<![CDATA[ ## Vulnerability Details **File Location**: `index.html:574-576, 598-603, 788-812, 2418-2440, 2464-2500, 3161-3165` **Vulnerability Type**: Stored cross-site scripting caused by unsafe HTML and inline-handler construction **Risk Level**: Critical ### Complete Vulnerable Code The escaping function does not encode quotation marks: ```javascript function esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); } ``` Generated content is assigned directly to `innerHTML`: ```javascript function renderTab(tab){ const m = document.getElementById('main'); if(tab==='overview') m.innerHTML = renderOverview(); if(tab==='positions') m.innerHTML = renderPositions(); if(tab==='trades') m.innerHTML = renderTrades(); if(tab==='finance') m.innerHTML = renderFinance(); if(tab==='transfers') m.innerHTML = renderTransfers(); if(tab==='accounts') m.innerHTML = renderAccounts(); } ``` Persisted IDs and colors are embedded directly into inline JavaScript and CSS contexts: ```javascript function renderAccounts(){ const cards = state.accounts.map(a=>{ const positions = state.positions.filter(p=>p.accountId===a.id); const trades = state.trades.filter(t=>t.accountId===a.id); const totalVal = positions.reduce((s,p)=>{ const v = p.currentValue!=null&&p.currentValue!==''?+p.currentValue:(+p.amount||0)*(+p.currentPrice||0); return s+v; },0); return `<div class="card acc-card" style="border-color:${a.color}30;cursor:pointer" onclick="viewAcc('${a.id}')"> <div style="display:flex;justify-content:space-between;margin-bottom:8px"> <div> <div style="font-size:15px;font-weight:700;font-family:var(--mono);color:var(--txt);margin-bottom:6px">${esc(a.name)}</div> ${tag(TYPE_LABEL[a.type],a.color)} </div> <div style="width:9px;height:9px;border-radius:50%;background:${a.color};box-shadow:0 0 7px ${a.color};margin-top:3px"></div> </div ...[truncated 4294 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stop constructing application interfaces with `innerHTML`. Build DOM elements with `document.createElement()` and assign untrusted data through `textContent`, `.value`, and validated properties. 2. Replace inline handlers such as `onclick="viewAcc('${a.id}')"` with `addEventListener()` and capture identifiers in closures. 3. Treat every field loaded from local files, cloud storage, AI output, and CSV imports as untrusted. 4. Apply strict schema validation before data enters application state: - IDs should match a narrow generated-ID format. - Colors should match a strict hexadecimal-color expression. - Enumerated fields should use explicit allowlists. - Numbers should be finite and within expected ranges. - Text fields should have conservative maximum lengths. 5. Regenerate imported or remotely supplied record IDs rather than preserving them. 6. If HTML templates remain, use context-specific encoding. HTML text, HTML attributes, URLs, CSS, and JavaScript strings require different handling. A single `esc()` function is not sufficient. 7. Add a Content Security Policy that disallows inline scripts and inline handlers, for example using external scripts with hashes or nonces. 8. Sanitize and validate existing persisted records before rendering them. Users should also rotate credentials potentially exposed by previous dashboard execution. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
index.html:387
Finding
Long-Lived Cloud and AI Credentials Stored in Script-Readable localStorage<![CDATA[ ## Vulnerability Details **File Location**: `index.html:387-390, 2855-2858, 2897-2911` **Vulnerability Type**: Plaintext persistent storage of sensitive authentication credentials **Risk Level**: High ### Complete Vulnerable Code Cloud credentials are persisted as plaintext JSON: ```javascript 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); } ``` AI provider credentials are stored in the same manner: ```javascript function getAIConfig(){ try{ return JSON.parse(localStorage.getItem('cryptofolio_ai')||'{}'); }catch(e){ return {}; } } function setAIConfig(cfg){ localStorage.setItem('cryptofolio_ai', JSON.stringify(cfg)); updateApiKeyBtn(); } ``` The saved key is read back and inserted into the page: ```javascript <div class="field"> <label id="key-label">API Key</label> <div style="position:relative"> <input id="f-apikey" type="password" value="${esc(cfg.key||'')}" placeholder="${esc(p.placeholder)}" style="font-family:var(--mono);font-size:12px;padding-right:56px"/> <button onclick="toggleApiKeyVis()" id="vis-btn" style="position:absolute;right:10px;top:50%;transform:translateY(-50%); background:none;border:none;color:var(--mid);cursor:pointer;font-size:11px;font-family:var(--mono)"> 显示 </button> </div> </div> ``` ### Technical Analysis `localStorage` is not a protected credential store. Every script executing under the same origin can read it, and values remain available across browser restarts until explicitly cleared. The use of `type="password"` only masks the visual presentation of a value; it does not encrypt the stored credential or prevent JavaScript access. The dashboard stores both: - The Cloudflare Worker URL and bearer token used to read and replace th ...[truncated 1547 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not expose long-lived service credentials to frontend JavaScript. 2. Store AI keys and cloud credentials in a loopback backend or operating-system credential manager. 3. Have the browser call narrow local backend operations rather than contacting AI providers with the raw API key. 4. If browser persistence is unavoidable, use short-lived, revocable, narrowly scoped tokens rather than account-level API keys. 5. Keep transient credentials in memory only and clear them when the page closes. 6. Do not automatically populate stored secrets into DOM elements. 7. Provide explicit credential-removal and rotation controls. 8. After fixing the stored-XSS vulnerability, advise existing users to rotate AI API keys and Cloudflare bearer tokens because prior script execution could have exposed them. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
cloudflare-worker/worker.js:4
Finding
Cloudflare Worker Uses a Hardcoded Shared Secret and Permissive Cross-Origin Policy<![CDATA[ ## Vulnerability Details **File Location**: `cloudflare-worker/worker.js:4-30` **Vulnerability Type**: Hardcoded authentication secret and overly permissive CORS configuration **Risk Level**: Medium ### Complete Vulnerable Code ```javascript // Cloudflare Worker for CryptoFolio // 部署到 Cloudflare Workers 作为数据存储后端 const TOKEN = 'your-secret-token'; // ⚠️ 修改为你的密码 export default { async fetch(request, env) { const url = new URL(request.url); const auth = request.headers.get('Authorization'); // CORS headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }; // CORS preflight if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } // Health check (无需认证) if (url.pathname === '/api/health') { return Response.json({ ok: true, timestamp: Date.now() }, { headers: corsHeaders }); } // 认证检查 if (auth !== `Bearer ${TOKEN}`) { return Response.json( { ok: false, error: 'Unauthorized' }, { status: 401, headers: corsHeaders } ); } ``` The deployment documentation also instructs users to modify source code to set the secret: ```javascript const TOKEN = 'your-secret-token'; // 改成你的密码 ``` ### Technical Analysis The Worker uses one long-lived bearer token embedded directly in source code. This creates several weaknesses: - The bundled placeholder is a predictable functional credential if deployed without modification. - A real token inserted into source can leak through version control, copied deployment code, backups, screenshots, or repository history. - There is no per-device or per-user credential separation. - Token rotation requires source modification and redeployment. - Wildcard CORS permits any website to send authenticated requests when the token is present in that browser. - ...[truncated 1605 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the token as a Cloudflare Worker Secret: ```bash wrangler secret put TOKEN ``` Then access it through `env.TOKEN` instead of declaring it in source: ```javascript const expected = `Bearer ${env.TOKEN}`; ``` 2. Refuse to start or authenticate if the secret is missing, empty, or equal to a known placeholder. 3. Update deployment documentation so users never paste production secrets into `worker.js`, `wrangler.toml`, repositories, or command examples retained in shell history. 4. Restrict `Access-Control-Allow-Origin` to explicitly trusted dashboard origins and return `Vary: Origin`. 5. Validate the request origin before returning CORS headers or processing authenticated data operations. 6. Introduce credential rotation, revocation, and preferably separate credentials per user or device. 7. Add rate limiting and logging for failed authentication attempts. 8. Enforce a maximum body size and validate all uploaded data against a strict schema before writing it to KV. 9. Consider encrypting especially sensitive portfolio data before KV persistence so compromise of Worker storage does not directly expose plaintext records. ]]>
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 (43)

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.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Uploaded screenshots/PDFs and account context are sent to external AI providers, potentially exposing balances, trades, account names, and embedded personal data. Because the feature handles financial documents, this transmission can leak highly sensitive information to multiple third parties and create compliance/privacy issues.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The UI claims API keys are kept only locally and never uploaded to any server, but the application necessarily transmits the key in Authorization/x-api-key headers to provider servers when making API calls. This is a materially false security statement that can cause users to make trust decisions based on incorrect information.

Missing User Warnings

High
Confidence
99% confidence
Finding
The upload-and-send flow transmits screenshots/PDFs to external AI services without a strong, immediate warning at the point of transmission. Users may unknowingly send full trading screenshots or documents containing balances, names, IDs, or other sensitive data to third-party processors.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

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
95% confidence
Finding
The README instructs users to hardcode a secret token in `worker.js` and store the same token in local configuration, which encourages unsafe secret handling and increases the chance of accidental disclosure through source control, screenshots, backups, or shared config files. In this skill context, the token protects access to portfolio and transaction data, so exposure could allow unauthorized reads or modification of sensitive financial records.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code performs a persistent write operation by storing request JSON into Cloudflare KV, but there is no confirmation prompt, user-facing log/print, or explanatory comment warning users that submitted data will be saved. Under the code-file criteria, file writes or other persistent data modifications should include some visible disclosure unless clearly documented as expected behaviour.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The document sets lang="zh" and the interface text throughout the file is Chinese-only, with no apparent language selector or opt-in. Under the stated policy, forcing a specific language without user choice is a natural-language locale policy violation unless the restriction is clearly justified.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The skill metadata presents the app as a local conversational asset recorder/exporter, but the code adds remote persistence via Cloudflare sync and localhost API storage. This materially changes the trust boundary for highly sensitive financial portfolio data and can mislead users into disclosing data they believed would remain local.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The app automatically pushes sensitive portfolio data to configured cloud endpoints during save operations without a strong user-facing warning at the moment of upload. Silent or background transmission of financial data increases the risk of accidental disclosure and undermines informed consent.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The application sends portfolio data to a localhost API endpoint without clearly informing the user in the visible interface. Even though localhost is local, it crosses the browser boundary into another service and may expose data to unexpected software listening on that port.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest describes portfolio recording/export, but the code also performs third-party AI processing and file analysis for screenshots/PDFs. This expands data collection and external transmission beyond the declared scope, increasing privacy and supply-chain risk for sensitive financial records.

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
96% confidence
Finding
This endpoint definition is part of the app's direct integration with Anthropic, enabling outbound transmission of user prompts, account context, and optionally uploaded files. In this skill's context, portfolio and document data are sensitive, so external transmission materially affects privacy and trust assumptions.

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
90% confidence
Finding
The OpenAI endpoint represents another third-party transmission target for sensitive user data. Even if browser CORS often blocks direct use, the code is designed to send account context and possibly uploaded content there when configured.

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
89% confidence
Finding
The MiniMax endpoint adds another external processor for sensitive financial text. Multiple provider options broaden the external attack and privacy surface, especially when users may not understand where their data is going.

External Transmission

Medium
Category
Data Exfiltration
Content
deepseek: {
    name: 'DeepSeek',
    placeholder: 'sk-...',
    url: 'https://api.deepseek.com/v1/chat/completions',
    docs: 'platform.deepseek.com',
    badge: '🔵',
    supportsImage: false,
Confidence
89% confidence
Finding
The DeepSeek endpoint is an additional third-party data recipient for parsed portfolio content. In a crypto portfolio skill, sending holdings and trade history to external services is security-relevant because it exposes valuable financial intelligence.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
API keys are stored in localStorage, which is persistent and accessible to any script running in the page origin. In a single-file app that also handles dynamic HTML and external integrations, this increases the blast radius of any future XSS or malicious script injection.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The stated purpose promises exporting data to CSV/Excel. In this file, the implemented data-transfer feature is a Notion CSV importer, and there is no corresponding CSV or Excel export logic present.

External Transmission

Medium
Category
Data Exfiltration
Content
if(cryptoAssets.length>0){
      const ids = [...new Set(cryptoAssets.map(a=>GECKO_IDS[a]))].join(',');
      try{
        const res = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`);
        if(res.ok){
          const data = await res.json();
          cryptoAssets.forEach(a=>{
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
for(const sym of stockAssets){
        try{
          const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(sym)}?interval=1d&range=1d`;
          const proxy = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`;
          const res = await fetch(proxy);
          if(res.ok){
            const wrapper = await res.json();
Confidence
86% confidence
Finding
Using allorigins as a proxy for Yahoo Finance exposes requested stock symbols to an unrelated third-party proxy and adds a supply-chain dependency. In aggregate, requested symbols can reveal parts of a user's holdings and investment behavior.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README promotes cloud synchronization of sensitive cryptocurrency portfolio data but does not clearly warn users that the data may reveal holdings, trading history, counterparties, and other financial information. In a crypto asset management context, this omission is security-relevant because users may enable remote storage without understanding the privacy and targeting risks if the endpoint, token, or hosting account is compromised.

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