Back to skill

Security audit

Bucket List

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local bucket-list skill, but its browser GUI has a real import-related script injection risk and under-disclosed local persistence of personal fields and a file handle.

Review before installing. The skill appears intended to run locally and does not show exfiltration or destructive behavior, but avoid importing bucket-list JSON from untrusted sources. Be aware that the GUI can retain a browser file handle and life-progress values locally across sessions.

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

T09 · Insecure Skill Coding Practices

Warning
Location
bucket-list.html:554
Finding

Stored DOM Cross-Site Scripting Through Unvalidated Wish IDs

Content
View full analysis
w && typeof w.content === 'string' && w.content.trim()) .map(w => ({ id: String(w.id || generateId()), content: String(w.content || '').trim(), category: String(w.category || '其他'), status: ['pending', 'completed', 'cancelled'].includes(w.status) ? w.status : 'pending', createdAt: String(w.createdAt || new Date().toISOString().split('T')[0]).slice(0, 10), endedAt: w.endedAt ? String(w.endedAt).slice(0, 10) : null, endedBy: w.endedBy || null, completionNote: String(w.completionNote || ''), cancelReason: String(w.cancelReason || ''), timeline: Array.isArray(w.timeline) ? w.timeline : [] })) ``` The identifier is subsequently embedded in a single-quoted inline event-handler attribute and assigned to `innerHTML`: ```javascript container.innerHTML = wishes.map(w => { const idArg = JSON.stringify(String(w.id)); const endedText = w.endedAt ? ` · ${w.status === 'completed' ? '完成于' : '取消于'} ${escapeHtml(w.endedAt)}` : ''; const note = w.completionNote || w.cancelReason || ''; return `
${escapeHtml(w.content)}
添加于 ${escapeHtml(w.createdAt || '未知')}${endedText}
${note ? `
${escapeHtml(note)}
` : ''}
Remediation
View remediation
completeWish(w.id)); ``` This keeps the identifier in a JavaScript closure rather than serializing it into HTML. 2. **Avoid rendering dynamic records with `innerHTML`.** Create elements with `document.createElement()` and assign untrusted values through `textContent`. 3. **Validate IDs on both client and server.** Reject identifiers outside a strict format and length limit, for example: ```javascript const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/; function validateId(id) { const value = String(id || ''); if (!SAFE_ID.test(value)) { throw new Error('Invalid wish identifier.'); } return value; } ``` Apply equivalent validation in `scripts/bucket-list-data.js` so imported and API-submitted data cannot retain dangerous identifiers. 4. **Regenerate invalid imported IDs.** For compatibility with existing data, replace malformed identifiers with values generated by `generateId()` rather than inserting them into the DOM. 5. **Deploy a restrictive Content Security Policy.** Move the inline script to a separate JavaScript file and configure a policy such as: ```http Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' ``` A policy without `unsafe-inline` prevents inline event handlers from executing and provides defense in depth. 6. **Add regression tests.** Test imported IDs containing apostrophes, quotation marks, angle brackets, and event-handler payloads, and verify that none create executable DOM nodes or attributes. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

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 · bucket-list.html (reported line 259)May include surrounding context.

html
</head>
<body>
    <div class="container">
        <!-- 人生进度(可选显示) -->
        <div class="life-progress-section">
            <label class="life-progress-toggle">
                <input type="checkbox" id="showLifeProgress" onchange="toggleLifeProgress()">

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 · bucket-list.html (reported line 259)May include surrounding context.

html
</head>
<body>
    <div class="container">
        <!-- 人生进度(可选显示) -->
        <div class="life-progress-section">
            <label class="life-progress-toggle">
                <input type="checkbox" id="showLifeProgress" onchange="toggleLifeProgress()">

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The UI adds a 'life progress' feature that collects birth year and life expectancy, which are personal attributes not required for core bucket-list management. Expanding scope to collect extra personal data increases privacy risk and data sensitivity, especially because the data is later persisted locally without a clear necessity or disclosure.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
77% confidence
Finding

The manifest mentions a localhost GUI sharing the same JSON data file, so local persistence is expected, but this code additionally exposes explicit file-linking, export/import controls and writes via POST to a data path. Those are user-facing file-management capabilities not described in the manifest's operation list, creating a description/behavior mismatch.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Wish entries are sent via synchronous HTTP POST to a server path, but the UI presents itself as 'local-first' and does not warn users that their data will be transmitted to a backend endpoint. This mismatch can expose highly personal bucket-list content to a server or intermediary unexpectedly.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Birth year and life expectancy are stored in localStorage, which persists across sessions and is accessible to any script running in the same origin. Persisting sensitive personal profile data in browser storage without strong justification or protection creates avoidable privacy exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code silently persists sensitive life-profile data locally without informing the user that the information will remain on disk/browser storage. Lack of notice and consent increases privacy harm because users may assume the optional calculator is ephemeral when it is not.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This code embeds Chinese defaults and user-facing text, starting with the timeline default event at L035 and continuing throughout the CLI responses and intent parsing. Because the file forces a specific language for commands, matching, and output with no opt-in or alternative locale handling, it constitutes a natural-language policy violation under the language/locale rule.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The HTML document declares lang="zh-CN" and all visible UI text is hard-coded in Simplified Chinese. There is no indication that the skill is region-specific or that users can opt into another language, which conflicts with the language-choice policy for natural-language behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.