T09 · Insecure Skill Coding Practices
- Location
scripts/apple-notes-export.mjs:193- Finding
AppleScript Injection Through Unvalidated Apple Notes Configuration
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
TaskFlow fits its task-management purpose, but it needs review because the optional Apple Notes and background sync features use weakly validated local configuration and can create recurring user-level execution.
Use this only with a trusted local workspace. Do not run taskflow notes, install-daemon, or --yes setup against shared or downloaded project folders until the AppleScript/config handling and daemon template escaping are fixed. Review any exported dashboard JSON or Apple Notes output for sensitive project details before syncing or sharing it.
scripts/apple-notes-export.mjs:193AppleScript Injection Through Unvalidated Apple Notes Configuration
scripts/apple-notes-export.mjs:185Predictable Shared Temporary File Enables Symlink Clobbering and Data Disclosure
scripts/taskflow-cli.mjs:244Daemon Installer Interpolates Environment-Derived Paths Into Shell Commands and XML
scripts/taskflow-cli.mjs:256Default-Accepted LaunchAgent Creates Persistent Execution of Mutable Workspace Code
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const safeWorkspace = path.resolve(workspace)
3. **Do not construct paths from untrusted input.** Even with a valid `OPENCLAW_WORKSPACE`, never concatenate unvalidated user input onto it (e.g. `path.join(workspace, userSlug, '../../../etc/passwd')`). Use `path.resolve()` and check that the resolved path starts with the workspace root:
```js
function safeJoin(base, ...parts) {
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
Uninstall:
systemctl --user disable --now taskflow-sync.timer
rm ~/.config/systemd/user/taskflow-sync.{service,timer}
systemctl --user daemon-reload
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
UNINSTALL:
launchctl unload ~/Library/LaunchAgents/com.taskflow.sync.plist
rm ~/Library/LaunchAgents/com.taskflow.sync.plist
-->
<plist version="1.0">
<dict>
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
UNINSTALL:
launchctl unload ~/Library/LaunchAgents/com.taskflow.sync.plist
rm ~/Library/LaunchAgents/com.taskflow.sync.plist
-->
<plist version="1.0">
<dict>
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
# {{Project Name}} — Plan
<!-- ============================================================
PLAN FILE
============================================================
File name: plans/<slug>-plan.md
The README promotes automatic 60-second background sync and workspace setup that modifies local markdown and SQLite state, but it does not clearly warn users that local files will be created, regenerated, and continuously updated. In an agent-oriented tool, this omission can cause unintentional data overwrites or silent propagation of sensitive task content, especially when users assume the tool is passive documentation rather than an active sync system.
The Apple Notes feature is described as pushing a live project status summary to Apple Notes, but the README does not clearly disclose the privacy implications of exporting project metadata into an external application and potentially across synced Apple devices. For a task-management skill used by agents, project names, statuses, and summaries may contain sensitive operational or business information, so silent external publishing increases data exposure risk.
The skill uses a high-trust environment variable (OPENCLAW_WORKSPACE) extensively and includes operational guidance that can trigger filesystem and daemon-management actions, but it does not declare an explicit tool scope such as permissions or allowed-tools. In an agent setting, that omission weakens policy enforcement and increases the chance an agent will use environment access and shell actions more broadly than intended.
The skill recommends installing a cron job for recurring execution of apple-notes-export.mjs, which establishes persistence on the host. While this is a legitimate product feature, persistence mechanisms are sensitive in agent-executed environments because they can outlive the session and continue accessing workspace data without fresh user approval.
For hourly auto-refresh, add a cron entry:
# Run: crontab -e
0 * * * * OPENCLAW_WORKSPACE=/path/to/workspace /path/to/node /path/to/taskflow/scripts/apple-notes-export.mjs
The taskflow install-daemon flow installs and enables a LaunchAgent or systemd user timer, creating background persistence. In a normal local app this may be expected, but in an agent skill it is risky because it extends execution beyond the interactive task and may be abused if invoked without fully informed consent.
taskflow install-daemon
This detects your platform and installs the appropriate unit. On macOS it installs and loads the LaunchAgent; on Linux it writes systemd user units and enables the timer.
### macOS — LaunchAgent (manual steps)
Referencing the LaunchAgent plist template in the manual setup section is part of a workflow that installs persistent background execution on macOS. In skill form, that is security-relevant because it can lead agents or users toward host-level persistence changes.
### macOS — LaunchAgent (manual steps)
Templates: `taskflow/system/com.taskflow.sync.plist.xml`
1. Copy `taskflow/system/com.taskflow.sync.plist.xml` → `~/Library/LaunchAgents/com.taskflow.sync.plist`
2. Replace `{{workspace}}` with the absolute path to your workspace (no trailing slash)
The step instructing users to copy a plist into ~/Library/LaunchAgents is a concrete persistence action on macOS. LaunchAgents start automatically and therefore create enduring background execution beyond the current session.
Templates: `taskflow/system/com.taskflow.sync.plist.xml`
1. Copy `taskflow/system/com.taskflow.sync.plist.xml` → `~/Library/LaunchAgents/com.taskflow.sync.plist`
2. Replace `{{workspace}}` with the absolute path to your workspace (no trailing slash)
3. Replace `{{node}}` with the path to your `node` binary (`which node`)
4. Load: `launchctl load ~/Library/LaunchAgents/com.taskflow.sync.plist`
The same location also matches plist, but the actual risk is not the file extension itself; it is the activation of the LaunchAgent from that plist. In context, this contributes to a true persistence concern because it is part of the install-and-load sequence.
1. Copy `taskflow/system/com.taskflow.sync.plist.xml` → `~/Library/LaunchAgents/com.taskflow.sync.plist`
2. Replace `{{workspace}}` with the absolute path to your workspace (no trailing slash)
3. Replace `{{node}}` with the path to your `node` binary (`which node`)
4. Load: `launchctl load ~/Library/LaunchAgents/com.taskflow.sync.plist`
5. Verify: `launchctl list | grep taskflow`
Uninstall:
The same location also matches plist, but the actual risk is not the file extension itself; it is the activation of the LaunchAgent from that plist. In context, this contributes to a true persistence concern because it is part of the install-and-load sequence.
1. Copy `taskflow/system/com.taskflow.sync.plist.xml` → `~/Library/LaunchAgents/com.taskflow.sync.plist`
2. Replace `{{workspace}}` with the absolute path to your workspace (no trailing slash)
3. Replace `{{node}}` with the path to your `node` binary (`which node`)
4. Load: `launchctl load ~/Library/LaunchAgents/com.taskflow.sync.plist`
5. Verify: `launchctl list | grep taskflow`
Uninstall:
The instructions to create ~/.config/systemd/user and place unit files there are part of setting up user-level persistent services. Creating service definitions in autoloaded user-unit directories is a recognized persistence mechanism and is sensitive in automated agent contexts.
Templates: taskflow/system/taskflow-sync.service and taskflow/system/taskflow-sync.timer
# Create the user unit directory
mkdir -p ~/.config/systemd/user
# Copy templates, replacing placeholders
The documented systemctl --user enable --now taskflow-sync.timer command enables a persistent user timer immediately. This is a real persistence mechanism and should be treated as sensitive because it creates ongoing background execution tied to the user's account.
systemctl --user daemon-reload systemctl --user enable --now taskflow-sync.timer
Verify:
The guidance to use loginctl enable-linger $USER increases persistence further by allowing user services to run without an interactive login session. That materially expands the lifetime and autonomy of the background process and raises the risk profile in shared or server environments.
systemctl --user daemon-reload
> **Note:** systemd user units require a login session. To run them without an interactive session (e.g. on a server), enable lingering: `loginctl enable-linger $USER`
---
This line reiterates support for installing a periodic sync daemon. It is not independently exploitative, but it contributes to a pattern of encouraging persistent background execution from within the skill documentation.
- Notes are one-way (markdown → DB). Removing a note in markdown does not clear it in DB.
- `db-to-files` rewrites all project task files, even unchanged ones.
- One task file per project (1:1 mapping). Multiple files per project is post-MVP.
- Periodic sync daemon: macOS (LaunchAgent) and Linux (systemd user timer) are supported. Run `taskflow install-daemon` to install.
- Node.js 22.5+ required (`node:sqlite`). No Python fallback in v1.
---
The guide recommends exporting TaskFlow data into a public app directory and even serving it from an S3/CDN path, but it does not warn that project names, descriptions, task states, and activity history may contain sensitive internal information. In this skill context, the documentation could lead operators to unintentionally publish private operational metadata to broader audiences than intended.
The same line was also flagged for plist-related persistence, but the real issue is unsafe shell construction around a path influenced by environment variables. In agent contexts where environment can be attacker-controlled, this can be more dangerous than ordinary CLI use because setup/install steps may run unattended.
// Load the agent
const { execSync } = await import('node:child_process')
try {
execSync(`launchctl load "${plistDest}"`, { stdio: 'pipe' })
console.log(` ${c.green}loaded${c.reset} com.taskflow.sync LaunchAgent (sync every 60s)`)
} catch {
console.log(` ${c.yellow}!${c.reset} Could not load LaunchAgent automatically. Run manually:`)
The same line was also flagged for plist-related persistence, but the real issue is unsafe shell construction around a path influenced by environment variables. In agent contexts where environment can be attacker-controlled, this can be more dangerous than ordinary CLI use because setup/install steps may run unattended.
// Load the agent
const { execSync } = await import('node:child_process')
try {
execSync(`launchctl load "${plistDest}"`, { stdio: 'pipe' })
console.log(` ${c.green}loaded${c.reset} com.taskflow.sync LaunchAgent (sync every 60s)`)
} catch {
console.log(` ${c.yellow}!${c.reset} Could not load LaunchAgent automatically. Run manually:`)
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.
console.log(` ${c.green}loaded${c.reset} com.taskflow.sync LaunchAgent (sync every 60s)`)
} catch {
console.log(` ${c.yellow}!${c.reset} Could not load LaunchAgent automatically. Run manually:`)
console.log(` launchctl load "${plistDest}"`)
}
} catch (e) {
console.log(` ${c.yellow}!${c.reset} LaunchAgent install failed: ${e.message}`)
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.
console.log(` ${c.green}loaded${c.reset} com.taskflow.sync LaunchAgent (sync every 60s)`)
} catch {
console.log(` ${c.yellow}!${c.reset} Could not load LaunchAgent automatically. Run manually:`)
console.log(` launchctl load "${plistDest}"`)
}
} catch (e) {
console.log(` ${c.yellow}!${c.reset} LaunchAgent install failed: ${e.message}`)
Although flagged as persistence, the material risk here is command injection through shell-based process creation. In an agent skill, environment manipulation may be easier than in normal local CLI use, increasing the practical danger if this installer is invoked automatically or by higher-level tooling.
console.log(` ${c.green}created${c.reset} ${plistDest}`)
try {
execSync(`launchctl load "${plistDest}"`, { stdio: 'pipe' })
console.log(` ${c.green}loaded${c.reset} com.taskflow.sync (syncs every 60s)`)
} catch (e) {
console.log(` ${c.yellow}!${c.reset} Could not load automatically. Run:`)
Although flagged as persistence, the material risk here is command injection through shell-based process creation. In an agent skill, environment manipulation may be easier than in normal local CLI use, increasing the practical danger if this installer is invoked automatically or by higher-level tooling.
console.log(` ${c.green}created${c.reset} ${plistDest}`)
try {
execSync(`launchctl load "${plistDest}"`, { stdio: 'pipe' })
console.log(` ${c.green}loaded${c.reset} com.taskflow.sync (syncs every 60s)`)
} catch (e) {
console.log(` ${c.yellow}!${c.reset} Could not load automatically. Run:`)