T06 · System Persistence
- Location
SKILL.md:32- Finding
Persistent execution through a macOS LaunchAgent
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
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.
SKILL.md:32Persistent execution through a macOS LaunchAgent
scripts/server.js:420Unauthenticated LAN users can read and modify TODO data
scripts/server.js:101Stored cross-site scripting through unescaped TODO item text
${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 `${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 `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.
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.
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.
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.
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.
### 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)
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.
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`
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.
Then load it:
launchctl load ~/Library/LaunchAgents/com.todo.plist
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.
Then load it:
launchctl load ~/Library/LaunchAgents/com.todo.plist
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.
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.
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.
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.
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.
No suspicious patterns detected.