Back to skill

Security audit

Project OS Organizer

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches its project-organizer purpose, but it needs Review because normal chat can trigger broad local scanning, local state changes, background dashboard processes, and unverified optional remote code execution.

Install only if you are comfortable with a shell-based local project indexer that can scan project folders, keep persistent organizer state under ~/.project_os, and run a localhost dashboard. Keep chat indexing, GitHub sync, home discovery, and remote install disabled unless you explicitly need them; prefer setting PROJECT_OS_ROOT to a reviewed local checkout, and avoid using dashboard restart/stop on ports used by other services.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/start_dashboard.sh:39
Finding

Unrelated Processes Can Be Terminated Based Only on Port Ownership

Content
View full analysis
/dev/null 2>&1; then existing_pid="$(lsof -ti "tcp:$PROJECT_OS_PORT" 2>/dev/null | head -n 1 || true)" fi if [[ -n "$existing_pid" && "$restart" -eq 0 ]]; then printf 'Dashboard already running at http://%s:%s (pid %s)\n' "$PROJECT_OS_HOST" "$PROJECT_OS_PORT" "$existing_pid" exit 0 fi if [[ -n "$existing_pid" && "$restart" -eq 1 ]]; then kill "$existing_pid" sleep 1 fi ``` The related easy-mode stop operation has the same issue: ```bash stop) pid="" if command -v lsof >/dev/null 2>&1; then pid="$(lsof -ti "tcp:$PROJECT_OS_PORT" 2>/dev/null | head -n 1 || true)" fi if [[ -n "$pid" ]]; then kill "$pid" printf 'Stopped dashboard PID %s\n' "$pid" else printf 'Dashboard is not running.\n' fi ;; ``` ### Technical Analysis The scripts treat any process listening on `PROJECT_OS_PORT` as the Project OS dashboard. They do not verify the process executable, command line, ownership, start time, or relationship to the dashboard PID file before issuing `kill`. This is especially relevant because the documented smoke-test workflow calls: ```bash start_dashboard.sh --detach --restart ``` Consequently, normal first-run behavior can terminate an unrelated application that happens to occupy the configured port. Although the signal is sent with the invoking user's privileges and does not itself escalate privileges, the logic violates process isolation and can disrupt other same-user services. There is also a time-of-check/time-of-use concern: the PID returned by `lsof` is not tied to a validated process identity before termination. ### Attack Path 1. An unrelated service is started under the victim user's account and listens on port `87 ...[truncated 1006 chars]
Remediation
View remediation
/cmdline` on Linux or use an equivalent platform API. - Verify that the process command matches the expected Project OS dashboard invocation. - Confirm that its configured database, host, and port match the current instance where practical. 3. If the configured port is occupied by an unrecognized process, fail safely with a message asking the user to choose another port. 4. Record process identity information alongside the PID, such as start time or a random instance identifier, to reduce PID-reuse risk. 5. Apply the same validation to both `start_dashboard.sh --restart` and `easy_mode.sh dashboard stop`. 6. Remove stale PID files only after confirming that the recorded process no longer exists or is not the expected dashboard. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/common.sh:6
Finding

Executable Shell Configuration Is Automatically Sourced by Every Wrapper

Content
View full analysis
Remediation
View remediation

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/common.sh:68
Finding

Mutable Remote Repository Head Is Retrieved and Executed Without Integrity Verification

Content
View full analysis
/dev/null 2>&1; then printf 'Could not locate project-os and git is unavailable for remote install.\n' >&2 return 1 fi if [[ "$PROJECT_OS_REPO_URL" != "$PROJECT_OS_TRUSTED_REPO_URL" ]]; then printf 'Remote install blocked: PROJECT_OS_REPO_URL must match PROJECT_OS_TRUSTED_REPO_URL.\n' >&2 return 1 fi mkdir -p "$(dirname "$PROJECT_OS_INSTALL_DIR")" if [[ -d "$PROJECT_OS_INSTALL_DIR/.git" ]]; then # Existing clone is reused as-is; no forced reset/update. : elif [[ -d "$PROJECT_OS_INSTALL_DIR" ]]; then printf 'Remote install blocked: %s exists but is not a git clone.\n' "$PROJECT_OS_INSTALL_DIR" >&2 return 1 else git clone --depth 1 "$PROJECT_OS_REPO_URL" "$PROJECT_OS_INSTALL_DIR" >/dev/null 2>&1 || return 1 fi if _is_project_os_root "$PROJECT_OS_INSTALL_DIR"; then printf '%s\n' "$PROJECT_OS_INSTALL_DIR" return 0 fi return 1 } ``` The downloaded module is later executed: ```bash project_os_cli() { local root local py root="$(project_os_root)" py="$(project_os_python "$root")" ( cd "$root" "$py" -m project_os.cli --db "$PROJECT_OS_DB" --config "$PROJECT_OS_CONFIG" "$@" ) } ``` ### Technical Analysis Remote installation is disabled by default and requires both `PROJECT_OS_AUTO_SETUP=1` and `PROJECT_OS_ALLOW_REMOTE_INSTALL=1`, which is a meaningful safeguard. The repository URL must also equal a configured trusted URL. However, the installation clones the current remote branch head with `--depth 1`. It does not pin a reviewed commit, verify a signed tag, val ...[truncated 1595 chars]
Remediation
View remediation
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description presents a project-management/chat workflow tool focused on tracking projects and resuming work across coding environments with privacy controls. The supplied code does not implement project tracking, chat interactions, update capture, local-folder/GitHub integration, or consent controls. Instead, it is a low-level process-management helper that launches arbitrary commands as background daemons, writes PID files, and redirects output to logs. That is a materially different primary purpose and introduces undeclared capabilities related to process spawning and daemon management.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description presents a user-facing project management skill centered on tracking projects and resuming work across tools with privacy/opt-in controls. This code chunk does not implement those behaviors. Instead, it is an infrastructure/diagnostic smoke test for bootstrapping and validating a local dashboard service. It starts services, reads PID files, probes an HTTP endpoint, checks port-listening state, and writes operational logs/results. Those are materially different primary behaviors from the declared project-management purpose, so this chunk is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The supplied code chunk does not implement or expose the declared project-manager behavior. Instead, it simply delegates to another shell script for a smoke-test style execution with an agent-safe flag. While this may be a supporting utility within a larger project, the chunk itself materially differs from the declared purpose and shows no evidence of the described chat-first project management, local/GitHub workflow handling, or privacy controls.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill instructs that the default action for every user message is to interpret it and run a shell router script with the raw message. This creates an over-broad activation surface where unrelated conversation can trigger command execution, filesystem scanning, and other side effects without a clear invocation boundary or user intent confirmation.

Content

No source excerpt is available for this finding.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Confidence
85% confidence
Finding

os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Content

Scanner excerpt · scripts/daemonize_command.py (reported line 77)May include surrounding context.

python
writer.write(str(os.getpid()))
        writer.flush()

    os.execvp(args.cmd[0], args.cmd)


if __name__ == "__main__":

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill requests behavior that clearly requires shell, filesystem, environment, and potentially network access, but it does not declare any explicit tool scope or permission boundaries. That makes the trust surface opaque: an agent may grant broad capabilities by default, and users cannot easily tell that plain-language interaction may trigger command execution, local file writes, or optional remote/GitHub operations.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
92% confidence
Finding

The skill contemplates treating chat sessions as projects and creating chat-derived project records, which introduces session persistence and indexing of conversation artifacts. Even though chat transcript indexing is said to be disabled by default, the design still increases privacy risk because conversations may contain sensitive data, path hints, credentials, or personal context that can be collected, stored, and correlated across tools.

Content

Scanner excerpt · SKILL.md (reported line 70)May include surrounding context.

md
Treat an item as a project if it matches any of these:
1. Git repository folder.
2. Non-git code folder with project markers (`package.json`, `pyproject.toml`, `Cargo.toml`, etc).
3. Chat session (Claude/Codex) that does not match an existing project; create a chat-derived project.
4. Claude workspace chats should map to deeper subprojects when session folders indicate a nested workspace path.

Reject likely non-project folders in collection roots (docs-only, logs, backups, notes-only folders) unless there is strong code/marker evidence.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

Telling users to 'just type plain English in chat' without boundaries reinforces implicit activation and makes it easy to trigger the skill on ambiguous or unrelated requests. In a shell-capable skill, vague invocation semantics increase the chance of unintended execution and data access.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The quickstart encourages very broad natural-language triggers such as 'Show my projects', 'Focus list', and 'Start dashboard' that overlap with ordinary conversation. In a chat-first agent environment, this can cause accidental invocation or prompt-trigger collisions, leading the skill to act on ambient user text or unrelated discussion without sufficiently explicit intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The workflow explicitly instructs recursive scanning of local roots and syncing GitHub data, but it does not mention any runtime consent, scope limitation, or warning to the user before accessing filesystem contents and remote project metadata. In a privacy-focused project manager, silent or under-disclosed collection of local and remote data can expose sensitive source code, notes, tokens, or repository metadata beyond what the user expects.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script automatically sources a local .project_os_env file as shell code before using its variables. If that file is writable by another local user, supplied by an untrusted repository copy, or modified through another workflow, arbitrary commands in the file will execute with the privileges of whoever runs the script.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The function creates directories and may run git clone to download code into the user's home directory when PROJECT_OS_AUTO_SETUP=1 and PROJECT_OS_ALLOW_REMOTE_INSTALL=1. Although there are guard variables and error messages, there is no affirmative notice at execution time that network access and local installation will occur.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This script implements a general-purpose background process launcher using pipes, double-fork daemonization, and os.execvp to run arbitrary commands. For a skill described as a privacy-first, chat-first project manager that tracks projects and resumes work across tools, a reusable daemonizer is not an obvious or necessary capability from the stated purpose alone.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The dashboard stop path issues kill "$pid" as soon as it finds a process listening on the configured port. While it prints a message after termination, there is no prior warning or confirmation that this action will forcibly stop a running process, which is a safety-relevant operation affecting system state.

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 · scripts/setup_test_config.py (reported line 78)May include surrounding context.

python
return env_name

    try:
        proc = subprocess.run(
            ["gh", "api", "user", "--jq", ".login"],
            capture_output=True,
            text=True,

Tainted flow: 'CONFIG_PATH' from os.environ.get (line 11, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
89% confidence
Finding

CONFIG_PATH is derived from the PROJECT_OS_CONFIG environment variable and is written to without validation, allowing an attacker who can influence the environment to redirect the config write to an arbitrary filesystem location accessible to the user. In a setup script that auto-creates parent directories and writes structured content, this can overwrite user files or place files in sensitive locations, making the skill more dangerous because it scans user-home-adjacent paths and is intended to run locally with user privileges.

Content

Scanner excerpt · scripts/setup_test_config.py (reported line 142)May include surrounding context.

python
"servers": [],
    }

    CONFIG_PATH.write_text(json.dumps(config, indent=2), encoding="utf-8")

    print(f"Wrote config: {CONFIG_PATH}")
    print(f"DB path: {DB_PATH}")