Back to skill

Security audit

TaskOps

Security checks for vulnerabilities and agentic risk

Overview

TaskOps is mostly coherent, but it documents persistent agent execution and broad git/file mutation paths that need careful review before installation.

Install only if you intentionally want TaskOps to manage local work graphs and possibly run agents in the background. Avoid daemon install/start, openclaw-chat-inject, and git-sync until you have reviewed the exact work directory, service lifetime, session-key handling, git diff, and remote branch. Treat the bundled Python graph_task.py as legacy migration tooling and do not run export --force or import untrusted graph IDs without containment.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T06 · System Persistence

Error
Location
SKILL.md:84
Finding

Persistent Unattended Agent Execution Through a User-Level systemd Service

Content
View full analysis
[--runtime dry-run|openclaw-cli] [--runner-id ] [--ttl-seconds ] [--max-attempts ] [--timeout ] [--report-sink none|ledger|openclaw-chat-inject] [--master-session-key ] [--json] taskops runner watch [--runtime dry-run|openclaw-cli] [--runner-id ] [--ttl-seconds ] [--max-attempts ] [--timeout ] [--report-sink none|ledger|openclaw-chat-inject] [--master-session-key ] [--poll-interval-ms ] [--max-waves ] [--max-idle-cycles ] [--idle-exit-after-seconds ] [--until ] [--continue-on-failure] [--json] taskops daemon run [--name ] [--runtime dry-run|openclaw-cli] [--runner-id ] [--ttl-seconds ] [--max-attempts ] [--timeout ] [--report-sink none|ledger|openclaw-chat-inject] [--master-session-key ] [--poll-interval-ms ] [--daemon-poll-interval-ms ] [--failure-backoff-ms ] [--max-daemon-cycles ] [--continue-on-failure] [--json] taskops daemon unit [--name ] [--runtime dry-run|openclaw-cli] [--json] taskops daemon install [--name ] [--runtime dry-run|openclaw-cli] [--start] [--dry-run] [--json] taskops daemon start|stop|restart|status|logs|uninstall [--json] ``` ```text Important boundary: - SQLite does not call OpenClaw and does not execute triggers by itself. - The watch runner is the process that stays alive and invokes the runtime adapter. - `taskops daemon install --name --start` is the preferred unattended local mode. It writes a user-systemd service around `taskops daemon run`, not around `runner watch`, so normal `all_closed` watch exits do not become systemd restart loops. - `taskops daemon run` repeatedly starts watch cycles, preserve ...[truncated 3879 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/graph_task.py:652
Finding

Arbitrary Markdown File Write Through Path Traversal in Obsidian Export Identifiers

Content
View full analysis
str: return step["id"] def obsidian_phase_note_name(step: dict, phase: dict) -> str: return f"{step['id']}__{phase['id']}" def obsidian_node_note_name(step: dict, phase: dict, node: dict) -> str: return f"{step['id']}__{phase['id']}__{node['id']}" ``` ```python def write_obsidian_export(output_dir: Path, data: dict) -> None: proj = project(data) for folder in ["projects", "steps", "phases", "nodes"]: (output_dir / folder).mkdir(parents=True, exist_ok=True) (output_dir / "index.md").write_text(render_obsidian_index(data), encoding="utf-8") (output_dir / "projects" / f"{proj['id']}.md").write_text(render_obsidian_project(proj), encoding="utf-8") for step in proj.get("steps", []): (output_dir / "steps" / f"{obsidian_step_note_name(step)}.md").write_text( render_obsidian_step(proj, step), encoding="utf-8", ) for phase in step.get("phases", []): (output_dir / "phases" / f"{obsidian_phase_note_name(step, phase)}.md").write_text( render_obsidian_phase(proj, step, phase), encoding="utf-8", ) for node in phase.get("nodes", []): (output_dir / "nodes" / f"{obsidian_node_note_name(step, phase, node)}.md").write_text( render_obsidian_node(proj, step, phase, node), encoding="utf-8", ) ``` ```python add_step_parser = subparsers.add_parser("add-step", help="Add a step") add_step_parser.add_argument("path") add_step_parser.add_argument("--id", required=True) add_step_parser.add_argument("--step-type", required=True) add_step_parser.add_argument("--description", ...[truncated 3466 chars]
Remediation
View remediation
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

These functions stage all changes in the repository, commit them, and push them upstream, which gives the skill repository-wide write and publication capability unrelated to simple graph tracking. In an autonomous or semi-autonomous agent setting, that can exfiltrate work, publish accidental secrets, or alter unrelated files the user did not intend to include.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The git-sync command combines pull, optional commit-all, and push into one action, enabling broad repository mutation and remote publication with minimal friction. That is especially risky for agent tooling because a single invocation can ingest remote changes, include all local modifications, and propagate them upstream without granular review.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · tests/test_graph_task_cli.py (reported line 29)May include surrounding context.

python
)

    def run_git(self, *args: str, cwd: Path, check: bool = True) -> subprocess.CompletedProcess:
        env = {
            **os.environ,
            "GIT_AUTHOR_NAME": "Graph Task Tests",
            "GIT_AUTHOR_EMAIL": "graph-task-tests@example.com",

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill directs the agent to use shell, file read/write, and environment-capable operations, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity where a host may expose broader capabilities than the skill actually needs, increasing the chance of unsafe command execution, unintended filesystem mutation, or secret exposure through environment access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This section encourages unattended/background execution, daemon installation, watch loops, and remote progress delivery via chat injection, but it does not prominently warn about persistence, automatic execution, external data transmission, or local system impact. In a skill context, that can cause users or orchestrators to enable long-running services and outbound reporting without informed consent, leading to privacy leakage, unintended resource use, or persistent automation they did not expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The documentation states that git sync commands operate on the whole backing repo, but it does not give a prominent operational warning that push/sync may stage, commit, and publish unrelated local changes across the repository. In an agent-execution context, this broad write scope can lead to accidental data exposure, unintended commits, or propagation of sensitive or unrelated modifications when the operator assumes the action is limited to the project folder.

Content

No source excerpt is available for this finding.

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · references/run-readiness.md (reported line 117)May include surrounding context.

md
`taskops run` does not only consume `runnable` tasks. Each step picks the next non-`done`/`cancelled` task in active snapshot order and dispatches based on the classification:

| Classification        | Runner action                                                                                                                                            |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `runnable`            | Execute via the executor; mark task done; attach task + run EoW; write the `closes_with` edge.                                                           |
| `needs_decomposition` | Open a `type: decomposition` run node; expand the task graph (child task group + v1 version); set parent `childTaskGroupId`; close parent with EoW reason `decomposed_by_runner`. The runner also extends the active snapshot's `selectedVersions` with the new child task group/version so the new children become visible to later steps of the same runner invocation. |

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/graph_task.py (reported line 308)May include surrounding context.

python
def run_git(command: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess:
    return subprocess.run(command, cwd=cwd, text=True, capture_output=True, check=True)


def prepare_repo_backed_run_dir(base_path: str, repo_url: str, branch: str, project_id: str) -> tuple[Path, Path, dict]:

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill expands from local task-graph management into remote repository cloning and synchronization, letting an agent fetch and operate on external codebases. In an agent skill context, this broadens trust boundaries and creates a pathway for supply-chain exposure, unintended network access, and manipulation of repositories beyond the user's likely expectation for a task-graph tool.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

When --force is supplied, the code recursively deletes whatever output directory the caller provides if it is non-empty. In an agent context, a mistaken, malformed, or adversarially influenced path can cause destructive deletion of unrelated user data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The destructive delete behavior is exposed behind only a force flag and lacks strong user-facing safeguards against data loss. In a skill used by agents, that weak safety posture materially increases the chance of accidental deletion when arguments are inferred or passed through from untrusted context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The repo commit/push workflow can modify and publish repository state without strong safety disclosure, scoped review, or explicit confirmation of what will be sent upstream. In the context of an agent skill, this is dangerous because the agent may act on indirect instructions and publish unrelated changes or sensitive data.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_graph_task_cli.py (reported line 20)May include surrounding context.

python
maxDiff = None

    def run_cli(self, *args: str, check: bool = True) -> subprocess.CompletedProcess:
        return subprocess.run(
            [sys.executable, str(CLI), *args],
            cwd=REPO_ROOT,
            text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_graph_task_cli.py (reported line 36)May include surrounding context.

python
"GIT_COMMITTER_NAME": "Graph Task Tests",
            "GIT_COMMITTER_EMAIL": "graph-task-tests@example.com",
        }
        return subprocess.run(
            ["git", *args],
            cwd=cwd,
            text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_graph_task_cli.py (reported line 470)May include surrounding context.

python
ignore=shutil.ignore_patterns(".git", "__pycache__", ".dist", "*.pyc"),
            )
            output_dir = Path(tmp) / "dist"
            result = subprocess.run(
                [sys.executable, str(PACKAGER), str(skill_copy), str(output_dir)],
                text=True,
                capture_output=True,

Static analysis

No suspicious patterns detected.