Back to skill

Security audit

Projectpilot

Security checks for vulnerabilities and agentic risk

Overview

ProjectPilot is a local project/task tracker whose file writes and Bash use match its stated purpose, with some quality and hardening issues users should understand.

Before installing, be aware that this skill can run Bash to execute its bundled Python tracker and will create or modify local project JSON files. Prefer using a private, user-owned PROJECTPILOT_DATA directory, avoid importing untrusted project JSON, and consider narrowing triggers if unrelated conversations often mention generic project-management terms.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/project_tracker.py:42
Finding

Path Containment Bypass Through Unsafe String-Prefix Validation

Content
View full analysis

Vulnerability Details

File Location: scripts/project_tracker.py, lines 42–54
Vulnerability Type: Improper path containment validation and symlink handling
Risk Level: Medium

Vulnerable Code

python
def sanitize_name(name):
    """Sanitize project name to prevent path traversal. Only allow safe characters."""
    name = name.strip().strip("/\\")
    sanitized = re.sub(r'[^a-zA-Z0-9._-]', '-', name)
    sanitized = re.sub(r'[-.]{2,}', '-', sanitized)
    if not sanitized or sanitized.startswith('.'):
        print("Error: Invalid project name. Use only letters, numbers, hyphens, and underscores.")
        sys.exit(1)
    return sanitized

def project_path(name):
    """Return safe path for project data. Validates name stays within DATA_DIR."""
    name = sanitize_name(name)
    p = (DATA_DIR / f"{name}.json").resolve()
    if not str(p).startswith(str(DATA_DIR.resolve())):
        print("Error: Invalid project name.")
        sys.exit(1)
    return p

Technical Analysis

The code attempts to ensure that the resolved project path remains beneath DATA_DIR by comparing path strings with startswith. A string prefix is not equivalent to filesystem ancestry. For example, /tmp/projects-backup/target begins with the string /tmp/projects, although it is not inside /tmp/projects.

Because Path.resolve() follows an existing symbolic link, a project file symlink can resolve into a prefix-sharing sibling directory and still pass this test. The sanitized project name prevents direct ../ traversal, but it does not prevent this symlink-based containment bypass.

Both reads and writes are affected because load_project() and save_project() rely on project_path(). Exploitation requires local ability to create or replace an entry in the configured project data directory.

Attack Path

  1. Determine or influence PROJECTPILOT_DATA, such as setting it to /tmp/projects.
  2. Create a sibling directory whose path shares that textu ...[truncated 1114 chars]
Remediation
View remediation

Remediation Suggestions

Use filesystem-aware ancestry validation rather than string comparison:

python
def project_path(name):
    name = sanitize_name(name)
    root = DATA_DIR.resolve()
    candidate = root / f"{name}.json"

    resolved = candidate.resolve()
    if not resolved.is_relative_to(root):
        raise ValueError("Project path escapes the data directory")

    return candidate

For compatibility with Python versions lacking Path.is_relative_to(), use os.path.commonpath():

python
if os.path.commonpath([str(root), str(resolved)]) != str(root):
    raise ValueError("Project path escapes the data directory")

Additional hardening should include:

  1. Reject project-file symbolic links explicitly with candidate.is_symlink().
  2. Ensure the data directory is owned by the expected user and is not writable by untrusted users.
  3. Open files with no-follow semantics where the operating system supports them.
  4. Use atomic writes through a securely created temporary file in the same verified directory, followed by os.replace().
  5. Revalidate immediately before file access to reduce time-of-check-to-time-of-use exposure.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/project_tracker.py:100
Finding

Terminal Escape-Sequence Injection Through Stored Task Data

Content
View full analysis

Vulnerability Details

File Location: scripts/project_tracker.py, lines 100–106
Vulnerability Type: Improper neutralization of terminal control sequences
Risk Level: Low

Vulnerable Code

python
status_icon = {"todo": "⬜", "doing": "🔄", "done": "✅"}
print(f"\n📋 {name} — {len(tasks)} task(s)\n")
for t in tasks:
    icon = status_icon.get(t["status"], "❓")
    due_str = f" | due: {t['due']}" if t.get("due") else ""
    assignee = f" | @{t['assignee']}" if t.get("assignee") else ""
    print(f"  {icon} [{t['id']}] [{t['priority']}] {t['task']}{due_str}{assignee}")

The same unsafe output pattern is also present in cmd_overdue() at lines 188–190:

python
print(f"\n⚠️  Overdue tasks in {name}:\n")
for t in overdue:
    print(f"  🔴 [{t['id']}] [{t['priority']}] {t['task']} (due: {t['due']})")

Technical Analysis

Task text, assignee names, due values, and fields loaded from project JSON are emitted directly to an interactive terminal. These values are not validated to reject or encode control characters.

An attacker-controlled value can contain ANSI or other terminal escape sequences. When a user runs list or overdue, the terminal may interpret those sequences rather than display them literally. Depending on terminal capabilities and configuration, this can rewrite visible output, conceal text, forge status messages, alter colors or window titles, or place deceptive content in the terminal display.

The CLI accepts task and assignee values directly from command-line arguments. Project JSON files may also be shared, imported, version-controlled, or modified outside the application, creating an additional untrusted-data path.

Attack Path

  1. Create a task whose text or assignee contains terminal control characters, or provide a crafted project JSON file containing such values.
  2. Store the malicious value by running add, or place the crafted JSON file in the project data directory.
  3. Persuade a user or automated o ...[truncated 831 chars]
Remediation
View remediation

Remediation Suggestions

Neutralize terminal control characters before displaying any value originating from CLI arguments or project files. A defensive output helper can render control characters visibly:

python
def safe_terminal_text(value):
    text = str(value)
    return "".join(
        ch if ch in "\n\t" or ord(ch) >= 32 and ord(ch) != 127
        else f"\\x{ord(ch):02x}"
        for ch in text
    )

For single-line fields, newline and tab characters should also be escaped. Apply the helper consistently to task text, assignee, due date, identifiers, project names, and values loaded from JSON.

Further hardening should include:

  1. Reject C0, DEL, C1, and escape characters when accepting task and assignee input.
  2. Validate all data again on output because project files can be modified externally.
  3. Treat imported or shared project JSON as untrusted input.
  4. Consider a dedicated terminal-sanitization library if support for complex Unicode and terminal behavior is required.
  5. Add tests containing escape, carriage-return, backspace, newline, and bidirectional-control characters.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (1)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The trigger list includes broad, common terms like "project," "task," "deadline," "priority," and "status report," which are likely to appear in many unrelated conversations. This can cause the skill to activate unintentionally, increasing the chance an agent invokes Bash-capable functionality in contexts where the user did not explicitly request project tracking behavior.

Content

No source excerpt is available for this finding.