Back to skill

Security audit

Linear Todos

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do what it says for Linear todos, but its install guidance and credential/profile-file handling create review-worthy risk.

Review before installing. Prefer an already installed uv or a package-manager install instead of curl | sh, use a dedicated minimal Linear API key, prefer LINEAR_API_KEY over saved plaintext config, set LINEAR_TIMEZONE explicitly if you do not want USER.md read, and add cron entries only if you intentionally want scheduled runs.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T03 · Remote Payload Retrieval and Execution

Error
Location
README.md:25
Finding
Remote Installer Is Downloaded and Executed Without Verification<![CDATA[ ## Vulnerability Details **File Location**: `README.md:25-32` **Vulnerability Type**: Remote payload retrieval and immediate shell execution **Risk Level**: High ### Vulnerable Code ```bash You need [uv](https://docs.astral.sh/uv/) installed: ```bash # macOS/Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Or with Homebrew brew install uv ``` ``` ### Technical Analysis The installation documentation pipes the response from a mutable external URL directly into a shell. The downloaded script is neither pinned to a reviewed version nor authenticated through a checksum or signature before execution. Although `astral.sh` is presented as the official uv distribution source, the effective code executed by this command can change after the Skill has been reviewed. Compromise of the remote server, publishing infrastructure, DNS resolution, or another part of the delivery chain could therefore turn this installation step into arbitrary code execution. This behavior is not required for the Skill's core Linear todo functionality. The documented Homebrew installation method or a verified, separately downloaded installer would avoid immediate execution of unaudited remote content. ### Attack Path 1. An attacker compromises the remote installer, its hosting infrastructure, or another trusted part of the delivery chain. 2. The attacker modifies the content returned by `https://astral.sh/uv/install.sh`. 3. A user follows the README and executes the `curl ... | sh` command. 4. The user's shell immediately executes the attacker-controlled response. 5. The payload can act with all privileges available to that user, including accessing credentials, changing shell configuration, modifying source files, or establishing persistence. ### Impact Assessment Successful exploitation provides arbitrary command execution under the account that runs the installation command. This may expose the Linear API key, local project data, OpenClaw workspace files, SSH cre ...[truncated 147 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the direct pipe-to-shell installation instruction. 2. Prefer installation through a trusted package manager, such as the already documented Homebrew command. 3. If a standalone installer is necessary: - Pin it to a specific reviewed release. - Download it to a local file without executing it. - Verify a publisher-provided cryptographic signature or SHA-256 checksum. - Allow the user to inspect the script. - Execute it only after successful verification. 4. Document the exact expected checksum and update it through a reviewed release process. 5. Avoid recommending elevated privileges for installation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/linear_todos/config.py:169
Finding
Linear API Key Is Persisted in Plaintext Using a Non-Atomic Permission Sequence<![CDATA[ ## Vulnerability Details **File Location**: `src/linear_todos/config.py:169-183` **Vulnerability Type**: Insecure sensitive credential storage **Risk Level**: Medium ### Vulnerable Code ```python config = { "apiKey": api_key, "teamId": team_id, "stateId": state_id, } if done_state_id: config["doneStateId"] = done_state_id # Write with restrictive permissions (user read/write only) import stat with open(self.CONFIG_FILE, "w") as f: json.dump(config, f, indent=2) self.CONFIG_FILE.chmod(stat.S_IRUSR | stat.S_IWUSR) self._config = config ``` ### Technical Analysis The setup wizard stores the Linear bearer token directly in plaintext JSON. While the code eventually changes the file mode to `0o600`, it applies that restriction only after opening and writing the file. The initial mode is therefore determined by the process umask. Under a permissive or misconfigured umask, another local account or process may be able to read the credential during the interval before `chmod` completes. The use of `open(..., "w")` also follows an existing symbolic link. If a local attacker can prepare the expected configuration path, the write and subsequent permission change may target an unintended file. The containing directory is created without an explicit `0o700` mode, leaving its effective permissions dependent on the umask as well. The project clearly discloses that file storage is optional and recommends environment variables, which reduces surprise but does not eliminate the implementation weakness. ### Attack Path 1. A local attacker with access to the user's filesystem namespace predicts the path `~/.config/linear-todos/config.json`. 2. The attacker monitors the path under a permissive umask or prepares a symbolic link before setup runs. 3. The user runs the interactive setup wizard and enters a valid Linear API key. 4. The wizard w ...[truncated 925 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an operating-system credential manager instead of plaintext JSON. 2. Keep environment-variable configuration available for users who do not want persistent credentials. 3. If file storage remains supported: - Explicitly create the configuration directory with mode `0o700`. - Reject symbolic links and non-regular files. - Create a temporary file atomically with mode `0o600` using `os.open` and appropriate flags such as `O_CREAT`, `O_EXCL`, and, where available, `O_NOFOLLOW`. - Write and flush the configuration through the returned file descriptor. - Atomically replace the destination only after validation. - Verify ownership and final permissions. 4. Separate the API key from non-sensitive team configuration so the credential can be stored in a more secure backend. 5. Continue recommending a dedicated, minimally scoped, revocable Linear token. ]]>

T05 · Unauthorized Access and Privilege Escalation

Note
Location
src/linear_todos/config.py:11
Finding
Skill Automatically Searches for and Reads an OpenClaw USER.md File<![CDATA[ ## Vulnerability Details **File Location**: `src/linear_todos/config.py:11-65` **Vulnerability Type**: Unnecessary access to Agent workspace data **Risk Level**: Low ### Vulnerable Code ```python def _find_openclaw_user_timezone() -> Optional[str]: """Try to extract timezone from OpenClaw's USER.md if present. Searches up the directory tree from the skill location for a workspace containing USER.md, then parses the timezone field. """ # Start from the skill directory and search upward for workspace skill_dir = Path(__file__).resolve().parent current = skill_dir # Search up a few levels for workspace root for _ in range(5): user_md = current / "USER.md" if user_md.exists(): try: content = user_md.read_text() # Look for timezone: America/New_York or similar match = re.search(r'(?:timezone|time.?zone)\s*[:=]\s*["\']?([^\n"\']+)["\']?', content, re.IGNORECASE) if match: tz = match.group(1).strip() # Clean up common markdown/formatting artifacts tz = tz.split('(')[0].strip() # Remove " (EST/EDT)" suffix tz = tz.replace('*', '').strip() # Remove markdown asterisks # Validate it looks like a timezone (has a slash for region/city) if '/' in tz and not tz.startswith('http'): return tz except (IOError, OSError): pass break # Also check if we're in skills/ subdirectory of workspace if current.name == "skills": user_md = current.parent / "USER.md" if user_md.exists(): try: content = user_md.read_text() match = re.search(r'(?:timezone|time.?zone)\s*[:=]\s*["\']?([^\n"\']+)["\']?', content, re.IGNORECASE) if match: ...[truncated 2676 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic parent-directory scanning for `USER.md`. 2. Require the timezone to be supplied explicitly through `LINEAR_TIMEZONE` or the Skill's own configuration. 3. If OpenClaw integration is retained: - Make it explicitly opt-in. - Read a dedicated, narrowly scoped settings file or platform configuration API. - Avoid reading the complete user-profile or memory document. - Resolve and validate the approved workspace root rather than scanning parent directories. 4. Document exactly which file is accessed and when. 5. Preserve UTC as a safe default when no timezone has been configured. ]]>

T08 · Insecure Dependencies

Note
Location
pyproject.toml:20
Finding
Dependency Installation Is Not Reproducibly Pinned<![CDATA[ ## Vulnerability Details **File Location**: `pyproject.toml:20-24` **Vulnerability Type**: Unpinned third-party dependency supply chain **Risk Level**: Low ### Vulnerable Code ```toml dependencies = [ "click>=8.3.1", "dateparser>=1.3.0", "requests>=2.32.5", ] ``` The installation documentation at `README.md:34-38` resolves these dependencies: ```bash ### 2. Install Dependencies ```bash uv sync ``` ``` ### Technical Analysis The repository does not include a reviewed lockfile, and each runtime dependency uses an open-ended lower-bound constraint. As a result, separate executions of `uv sync` may resolve different package versions, including releases published after this audit. No currently declared dependency was shown to be malicious. The vulnerability is the lack of reproducibility and integrity pinning: if a dependency publisher account, package release, or package index is compromised, the resolver may install attacker-controlled code that was not part of the reviewed artifact. Python packages and their build backends may execute code during installation as well as at runtime. ### Attack Path 1. An attacker compromises a declared dependency's publishing account or distribution channel. 2. The attacker publishes a malicious version that satisfies the open-ended `>=` constraint. 3. A user runs the documented `uv sync` command without a reviewed lockfile. 4. The resolver selects and installs the malicious compatible release. 5. Package build or runtime code executes with the privileges of the user running the Skill. 6. The malicious dependency may access the Linear API key, configuration files, workspace data, and other resources available to that account. ### Impact Assessment Successful exploitation can lead to arbitrary code execution under the installing or invoking user's account. This could disclose the Linear API key and local files, alter Skill behavior, tamper with todo data, or establish persistence within the user's ...[truncated 171 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate and commit a reviewed `uv.lock` file. 2. Use locked or frozen installation modes so dependency resolution cannot silently change. 3. Pin exact dependency and build-backend versions through the lockfile. 4. Where supported, verify package hashes and trusted index configuration. 5. Review automated dependency updates before merging or releasing them. 6. Run vulnerability and provenance checks on direct and transitive dependencies. 7. Keep build-system dependencies locked as well as runtime dependencies. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (26)

Chaining Abuse

High
Category
Tool Misuse
Content
```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or with Homebrew
brew install uv
Confidence
98% confidence
Finding
The explicit shell pipe chaining pattern (curl ... | sh) is dangerous because it removes the opportunity for users to inspect what will execute and turns a network response directly into code execution. In the context of a source-execution skill, normalizing this pattern increases supply-chain and workstation compromise risk for users following setup instructions.

Session Persistence

Medium
Category
Rogue Agent
Content
### 3. Get a Linear API Key

1. Go to [linear.app/settings/api](https://linear.app/settings/api)
2. Create a new API key (name it "Linear Todos" or whatever you prefer)
3. Copy the key — you'll need it for the next step

### 4. Run Setup Wizard
Confidence
60% confidence
Finding
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.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill is explicitly source-executing and documents capabilities to read environment variables, access files, write configuration, make network requests, and invoke shell commands, but it does not declare any explicit tool scope restrictions such as permissions or allowed-tools. That creates an avoidable trust gap: consumers must rely on documentation rather than enforceable platform constraints, so future code changes could expand behavior without corresponding policy controls.

Session Persistence

Medium
Category
Rogue Agent
Content
### Recommended Security Practices

1. **Use a dedicated API key:** Create a separate Linear API token with minimal scope for this skill. Revoke it if you uninstall or stop using the skill.
2. **Prefer environment variables:** Set `LINEAR_API_KEY` in your shell instead of running `setup` — no plaintext file is created.
3. **Audit the code:** Review `src/linear_todos/api.py` to verify HTTP destinations before first use.
4. **Run initial setup in isolation:** If unsure, run the skill in a container/VM for the first setup to inspect behavior.
Confidence
60% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
**Natural Date Examples:**

```bash
uv run python main.py create "Task" --date "tomorrow"
uv run python main.py create "Task" --date "Friday"
uv run python main.py create "Task" --date "next Monday"
uv run python main.py create "Task" --date "in 3 days"
Confidence
60% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
"sensitive": true,
    "autoWrite": false,
    "permissions": "0o600",
    "note": "Config file is written ONLY during manual 'setup' command with 0o600 permissions (user read/write only). Environment variables can be used instead to avoid persisted state."
  },
  "network": {
    "destinations": ["https://api.linear.app"],
Confidence
86% confidence
Finding
The metadata explicitly states that the config file may store the Linear API key in plaintext JSON on disk, even though protected with 0o600 permissions and only written during manual setup. This is a real credential persistence risk: any local compromise of the user account, backups, accidental file exposure, or unsafe file handling could disclose the API key and enable unauthorized access to the user's Linear workspace.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
# Linear Todos Cron Jobs - EXAMPLE ONLY
# ======================================
# ⚠️  WARNING: This file contains EXAMPLE cron entries. It does NOT
#    automatically modify your crontab. Adding these is a manual
#    user action. Review before using.
#
# To add these jobs, run: crontab -e
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Session Persistence

Medium
Category
Rogue Agent
Content
#    automatically modify your crontab. Adding these is a manual
#    user action. Review before using.
#
# To add these jobs, run: crontab -e
# Then paste the entries you want (modify paths first!)

SHELL=/bin/bash
Confidence
85% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
#    automatically modify your crontab. Adding these is a manual
#    user action. Review before using.
#
# To add these jobs, run: crontab -e
# Then paste the entries you want (modify paths first!)

SHELL=/bin/bash
Confidence
85% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
#    automatically modify your crontab. Adding these is a manual
#    user action. Review before using.
#
# To add these jobs, run: crontab -e
# Then paste the entries you want (modify paths first!)

SHELL=/bin/bash
Confidence
85% confidence
Finding
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.

External Transmission

Medium
Category
Data Exfiltration
Content
class LinearAPI:
    """Client for the Linear GraphQL API."""
    
    API_URL = "https://api.linear.app/graphql"
    
    def __init__(self, api_key: Optional[str] = None, config=None):
        """Initialize the Linear API client.
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
class LinearAPI:
    """Client for the Linear GraphQL API."""
    
    API_URL = "https://api.linear.app/graphql"
    
    def __init__(self, api_key: Optional[str] = None, config=None):
        """Initialize the Linear API client.
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
class LinearAPI:
    """Client for the Linear GraphQL API."""
    
    API_URL = "https://api.linear.app/graphql"
    
    def __init__(self, api_key: Optional[str] = None, config=None):
        """Initialize the Linear API client.
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if variables:
            payload["variables"] = variables
        
        response = requests.post(
            self.API_URL,
            headers=headers,
            json=payload,
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Script Fetching

Low
Category
Supply Chain
Content
```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or with Homebrew
brew install uv
Confidence
96% confidence
Finding
The README recommends piping a remote script directly into a shell, which executes unreviewed code fetched over the network. If the remote host, CDN path, or transport is compromised, users could run attacker-controlled code on their machine during installation.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This markdown file documents a setup flow that saves settings to `~/.config/linear-todos/config.json`, immediately after collecting a Linear API key. The README does not warn users that credentials or other sensitive configuration may be written to disk, which is a privacy and system-safety relevant behavior for markdown under the missing user warnings rule.

Excessive Permissions

Low
Category
Privilege Escalation
Content
}
```

**Permissions:** Created with user-only read/write (0o600).

### Setup Behavior
During interactive setup, the wizard temporarily sets `LINEAR_API_KEY` in the process environment to validate the API key before saving. This only occurs during the setup session and is not persisted beyond the setup process.
Confidence
80% confidence
Finding
Skill requests more permissions than appear necessary for its stated functionality. Review if elevated access is justified.

Tool Parameter Abuse

Low
Category
Tool Misuse
Content
3. **Run in isolation first** — If unsure, test in a container:
   ```bash
   docker run -it --rm -v $(pwd):/app python:3.12 bash
   cd /app && pip install -e . && python main.py setup
   ```
Confidence
15% confidence
Finding
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).

Vague Triggers

Low
Confidence
84% confidence
Finding
This TOML manifest file is in scope for vague-trigger review. The description says the skill is "a powerful todo management system" with "smart date parsing," but it does not define any explicit invocation phrases, activation boundaries, or exclusions, making the skill scope broad and underspecified from a trigger/activation perspective.

Unverifiable Dependency: click has 1 known advisory(ies) (CVE-2026-7246 (Pallets Click, versions 8.3.2 and below, contain a command injection vulnerabili)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Intent-Code Divergence

Low
Confidence
92% confidence
Finding
The inline note says `secrets.choice used below for security scan compliance`, but the function immediately below also imports `random` and never uses it. This is a documentation/code contradiction about implementation intent, even though the security impact is low because greeting selection does in fact use `secrets.choice` later.

Context-Inappropriate Capability

Low
Confidence
88% confidence
Finding
The skill searches upward from its own directory and reads workspace-level USER.md files to infer a timezone, which expands its data access beyond what is necessary for todo management. Even though it only extracts a timezone-like value, this is still an undocumented cross-boundary read of user/workspace content and can violate least-privilege expectations in an agent skill.

Static analysis

No suspicious patterns detected.