Back to skill

Security audit

TODO Web App

Security checks for vulnerabilities and agentic risk

Overview

This skill is a useful TODO web app, but it exposes readable and writable task data on the LAN without authentication while also encouraging persistent startup.

Install only if you trust every device and browser context on the LAN. Prefer running it manually on localhost, add authentication before LAN use, avoid persistent launchd startup until the plist is reviewable, and fix HTML escaping before using it with TODO content that could come from other tools or people.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (3)

T06 · System Persistence

Error
Location
SKILL.md:32
Finding

Persistent execution through a macOS LaunchAgent

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/server.js:420
Finding

Unauthenticated LAN users can read and modify TODO data

Content
View full analysis
{ if (req.method === 'GET' && req.url === '/') { const md = fs.readFileSync(TODO_PATH, 'utf8'); const sections = parseTodo(md); const html = renderHTML(sections); res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); res.end(html); } else if (req.method === 'GET' && req.url === '/sections') { const md = fs.readFileSync(TODO_PATH, 'utf8'); const sections = parseTodo(md); res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); res.end(renderSections(sections)); } else if (req.method === 'GET' && req.url === '/events') { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*' }); res.write(': connected\n\n'); sseClients.add(res); req.on('close', () => sseClients.delete(res)); } else if (req.method === 'POST' && req.url === '/toggle') { let body = ''; req.on('data', c => body += c); req.on('end', () => { try { const { lineIndex, checked } = JSON.parse(body); toggleLine(lineIndex, checked); notifyClients(); 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 })); } }); } else if (req.method === 'POST' && req.url === '/archive') { try { const count = archiveDone(); notifyClients(); res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({ ok: true, count })); } ...[truncated 2950 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/server.js:101
Finding

Stored cross-site scripting through unescaped TODO item text

Content
View full analysis
{ if (item.type === 'subheading') { return `

${escHtml(item.text)}

`; } if (item.type === 'note') { return `

${escHtml(item.text)}

`; } const cls = item.checked ? 'todo-item checked' : 'todo-item'; const emoji = item.text.match(/^(🔥|⚡|✅)/)?.[0] || ''; const textNoEmoji = emoji ? item.text.slice(emoji.length).trim() : item.text; const formatted = textNoEmoji.replace(/\*\*(.*?)\*\*/g, '$1'); return `
${item.checked ? '✅' : '☐'} ${emoji ? `${emoji}` : ''}${formatted}
`; }).join(''); ``` The same vulnerable rendering pattern is repeated in the partial-section renderer: ```javascript const itemsHTML = sec.items.map(item => { if (item.type === 'subheading') return `

${escHtml(item.text)}

`; if (item.type === 'note') return `

${escHtml(item.text)}

`; const cls = item.checked ? 'todo-item checked' : 'todo-item'; const emoji = item.text.match(/^(🔥|⚡|✅)/)?.[0] || ''; const textNoEmoji = emoji ? item.text.slice(emoji.length).trim() : item.text; const formatted = textNoEmoji.replace(/\*\*(.*?)\*\*/g, '$1'); return `
${item.checked ? '✅' : '☐'} ${emoji ? `${emoji}` : ''}${formatted}
Remediation
View remediation
$1'); ``` - Prefer constructing DOM nodes and assigning user-controlled content through `textContent` instead of producing HTML strings. - If richer Markdown is required, use a reviewed parser and sanitizer configured with a minimal element and attribute allowlist. - Never allow event-handler attributes, scripts, SVG scripting, dangerous URLs, or arbitrary style elements. - Apply the same safe rendering implementation to both `renderHTML()` and `renderSections()` to prevent inconsistent protection. - Add regression tests for payloads involving: - `
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

Most of the declared functionality is accurately represented by the code: it serves a LAN-accessible local web UI, reads and writes TODO.md, supports SSE auto-refresh, click-to-toggle checkboxes, and an Archive Done action that appends completed items to TODO-done.md. However, the description explicitly says the skill will deploy the app and auto-start it on boot via launchd. This code chunk only implements the runtime server behavior; it does not create launchd plist files, register a launch agent/daemon, or otherwise configure startup on boot. It also does not itself perform deployment/setup steps beyond being a server script. Because these are explicit declared capabilities absent from the supplied code, this is a description/behavior mismatch.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill describes deploying a LAN-accessible web application and therefore introduces network exposure, but it does not declare any explicit tool scope or permissions boundary. This makes the network capability implicit rather than transparent, increasing the risk that the agent or user enables broader exposure than intended without clear review.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The statement 'no HTTPS needed' downplays the security implications of sending content and actions unencrypted over the local network. On untrusted or shared LANs, traffic may be intercepted or modified, exposing task contents and allowing tampering with checkbox toggles or archive actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The statement 'no HTTPS needed' downplays the security implications of sending content and actions unencrypted over the local network. On untrusted or shared LANs, traffic may be intercepted or modified, exposing task contents and allowing tampering with checkbox toggles or archive actions.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The skill instructs users to install a launchd agent under ~/Library/LaunchAgents, creating persistent execution on login and automatic restart behavior. Persistence is security-relevant because a compromised or misconfigured web service would continue exposing the TODO interface and file-writing capability across sessions.

Content

Scanner excerpt · SKILL.md (reported line 38)May include surrounding context.

md
### 3. Install the launchd agent

Copy `assets/com.todo.plist.template` to `~/Library/LaunchAgents/com.todo.plist`.

Edit the plist and update these two values:
- The path to `node` (run `which node` to find it)

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

Directing the user to edit the plist with paths to node and server.js is part of establishing a persistent user-level background service. Even if intended for convenience, this increases attack surface by ensuring the web app runs repeatedly without manual invocation, which can prolong unauthorized exposure or abuse.

Content

Scanner excerpt · SKILL.md (reported line 40)May include surrounding context.

md
Copy `assets/com.todo.plist.template` to `~/Library/LaunchAgents/com.todo.plist`.

Edit the plist and update these two values:
- The path to `node` (run `which node` to find it)
- The path to `server.js`

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

The same instruction set at this location references the LaunchAgent plist and therefore confirms persistence behavior. Persistent background services are not inherently malicious, but they are security-sensitive and should be surfaced clearly because they can maintain network listeners and file access without ongoing user awareness.

Content

Scanner excerpt · SKILL.md (reported line 47)May include surrounding context.

Then load it:

bash
launchctl load ~/Library/LaunchAgents/com.todo.plist

4. Open in browser

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

The same instruction set at this location references the LaunchAgent plist and therefore confirms persistence behavior. Persistent background services are not inherently malicious, but they are security-sensitive and should be surfaced clearly because they can maintain network listeners and file access without ongoing user awareness.

Content

Scanner excerpt · SKILL.md (reported line 47)May include surrounding context.

Then load it:

bash
launchctl load ~/Library/LaunchAgents/com.todo.plist

4. Open in browser

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code directly rewrites TODO.md to change task state, but there is no confirmation prompt, visible server-side disclosure, or comment/docstring warning that a click causes an on-disk file write. For a code file, file modifications are safety-relevant operations and should have some explicit user disclosure unless clearly documented elsewhere in the skill description, which is not present in this file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The archive function removes completed items from TODO.md and appends them to TODO-done.md, which changes user data in a semi-irreversible way. Although the button label says 'Archive Done', the code lacks any confirmation prompt or explicit warning that this will rewrite one file and append to another.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The server binds to 0.0.0.0 and exposes POST /toggle and POST /archive with no authentication, authorization, or CSRF-style origin checks, so any host on the LAN can modify TODO.md and archive items. The skill context makes this more dangerous because it is explicitly intended to be reachable from any device on the LAN, broadening the attacker set from local-only to anyone with network access; the permissive Access-Control-Allow-Origin on /events further enables cross-origin observation of activity.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The archive timestamp is explicitly formatted using the 'en-US' locale and 'America/Chicago' timezone, which imposes a specific language/locale convention without user opt-in. This matches the natural-language locale policy violation category because the user is not given a choice and no justification is documented in the file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The rendered interface forces English text such as 'Live' without any visible user opt-in or language selection mechanism. The policy requires flagging language or locale constraints when a specific language is imposed without user choice or documented justification.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.