Back to skill

Security audit

skillsync

Security checks for vulnerabilities and agentic risk

Overview

SkillSync is a coherent skill-versioning tool, but its installer and sync behavior create review-worthy supply-chain and accidental data-upload risks.

Review before installing. Prefer a pinned package or audited local checkout instead of the one-line remote installer, avoid granting sudo during install, use only a private remote, and inspect the entire Skills directory for tokens or unrelated files before snapshot or sync because non-Skill files under that directory can be committed and uploaded.

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
  • 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
Findings (3)

T03 · Remote Payload Retrieval and Execution

Error
Location
SKILL.md:32
Finding
Unverified Remote Installer Scripts Are Downloaded and Immediately Executed<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:32-35`; also documented in `README.md:165-180`, `README.zh-CN.md:162-177`, and `install.ps1:3-7` **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: Critical ### Vulnerable Code ```markdown ## Install ```bash curl -fsSL https://raw.githubusercontent.com/johnsonbuilds/skillsync/main/install.sh | sh ``` ``` The Windows documentation provides the equivalent PowerShell pattern: ```powershell irm https://raw.githubusercontent.com/johnsonbuilds/skillsync/main/install.ps1 | iex # Windows PowerShell 5.1: iwr <url> -UseBasicParsing | iex ``` ### Technical Analysis The recommended installation commands retrieve a script from the mutable `main` branch of a personal GitHub repository and pass the response directly to a command interpreter. No release version, immutable commit hash, checksum, or cryptographic signature is verified before execution. Consequently, the effective code executed by users can differ from the code audited in this project. HTTPS protects the connection in transit but does not protect against compromise of the repository owner, GitHub account, branch, or upstream source. The risk is amplified on Unix because the retrieved installer can invoke `sudo apt-get` when virtual-environment creation fails. Even though the currently reviewed script only attempts to install `python3-venv`, a future or compromised remotely delivered script would not be constrained to that behavior. ### Attack Path 1. An attacker compromises the GitHub account, repository, default branch, or another component able to modify the raw installer response. 2. The attacker replaces `install.sh` or `install.ps1` on `main` with malicious commands. 3. A user or AI Agent follows the documented one-line installation instruction. 4. `curl` or `Invoke-RestMethod` downloads the modified payload. 5. `sh` or `Invoke-Expression` executes it without verification or inspection. 6. The payload operat ...[truncated 709 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all `curl | sh`, `irm | iex`, and `iwr | iex` installation instructions. 2. Publish versioned release artifacts instead of executing files from `main`. 3. Pin downloads to an immutable release tag and, preferably, a full commit or content digest. 4. Publish SHA-256 checksums or cryptographic signatures through an independently protected release channel. 5. Require a download-and-verify workflow, for example: ```sh curl -fSLo install.sh https://example.invalid/releases/v0.3.1/install.sh echo "<expected-sha256> install.sh" | sha256sum -c - less install.sh sh install.sh ``` 6. Prefer installation from a trusted package index using an exact version: ```sh python3 -m pip install "skillsync==0.3.1" ``` 7. Do not automatically request elevation from a remotely delivered installer. Detect missing system packages and print explicit manual installation instructions instead. 8. Apply the corrected instructions consistently to `SKILL.md`, both README files, and comments in both installers. ]]>

T08 · Insecure Dependencies

Error
Location
install.sh:20
Finding
Installer Builds and Installs an Unpinned Mutable Git Repository<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:20-20,84-89`; equivalent behavior in `install.ps1:58-77` **Vulnerability Type**: Unsafe and unpinned supply-chain source **Risk Level**: High ### Vulnerable Code ```sh REPO_URL="${SKILLSYNC_REPO:-https://github.com/johnsonbuilds/skillsync.git}" ``` ```sh # 3. Install skillsync into it # REPO_URL may be a git URL or a local checkout path. case "$REPO_URL" in *.git | http://* | https://* | git+*) SRC="git+$REPO_URL" ;; *) SRC="$REPO_URL" ;; esac "$INSTALL_DIR/venv/bin/pip" install --upgrade "$SRC" ``` The Windows installer performs the same operation: ```powershell $RepoUrl = if ($env:SKILLSYNC_REPO) { $env:SKILLSYNC_REPO } else { 'https://github.com/johnsonbuilds/skillsync.git' } $needsGit = ($RepoUrl -match '\.git$') -or ($RepoUrl -like 'http*') -or ($RepoUrl -like 'git+*') $src = if ($needsGit) { "git+$RepoUrl" } else { $RepoUrl } $pip = Join-Path $VenvDir 'Scripts\pip.exe' & $pip install --upgrade $src ``` ### Technical Analysis The installers pass the default GitHub repository to pip as a VCS dependency without a tag or full commit identifier. Pip therefore resolves the repository's current default branch at installation time. Python package installation may execute build-backend code and installs runtime code that will later be invoked as `skillsync`. As a result, compromise or unexpected modification of the mutable branch can produce arbitrary local code execution even if the initially downloaded installer remains unchanged. The `--upgrade` option reinforces the mutable behavior by replacing an existing installation with whatever revision the repository resolves to at that time. The `SKILLSYNC_REPO` override is useful for development, but it does not provide integrity verification and should not be treated as a secure production source selector. ### Attack Path 1. An attacker gains the ability to modify the repository's default branch or ...[truncated 1014 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Install an exact published package version rather than the repository's current default branch: ```sh "$INSTALL_DIR/venv/bin/pip" install "skillsync==0.3.1" ``` 2. If VCS installation is unavoidable, pin a full immutable commit: ```sh pip install \ "skillsync @ git+https://github.com/johnsonbuilds/skillsync.git@<full-commit-sha>" ``` 3. Verify release artifacts with hashes or signatures before installation. 4. Lock build-system and runtime dependencies to reviewed versions and use hash checking where feasible. 5. Remove unrestricted `--upgrade` behavior from the installer. Provide a separate, explicit update command that names the target version. 6. Clearly label `SKILLSYNC_REPO` as a development-only override. For production, either reject unpinned network URLs or require an accompanying trusted digest. 7. Build releases in a controlled CI environment and publish provenance or signed attestations. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/skillsync/git.py:150
Finding
Snapshots and Remote Synchronization Include Files Outside Discovered Skill Boundaries<![CDATA[ ## Vulnerability Details **File Location**: `src/skillsync/git.py:150-151,379-393`; invoked by `src/skillsync/cli.py:391-403,450-466,750-757,774-820` **Vulnerability Type**: Excessive collection and upload scope **Risk Level**: High ### Vulnerable Code The staging function includes every changed file under the repository: ```python def add_all(repo: Path) -> None: git("add", "-A", cwd=repo) ``` The snapshot command calls it without Skill path restrictions: ```python entries = git.status_porcelain(skills_dir) if not entries: typer.echo("No changes to snapshot.") raise typer.Exit() changes, _ = _skill_changes(skills_dir) if changes: count = len(changes) typer.echo(f"{count} skill{'s' if count != 1 else ''} changed.\n") if message is None: message = f"SkillSync snapshot: {_dt.datetime.now():%Y-%m-%d %H:%M}" git.add_all(skills_dir) created = git.commit(skills_dir, message) ``` Synchronization also automatically snapshots all files before pushing: ```python if git.status_porcelain(skills_dir): message = f"Pre-sync snapshot: {_dt.datetime.now():%Y-%m-%d %H:%M}" git.add_all(skills_dir) created = git.commit(skills_dir, message) if created: typer.echo(f"Pre-sync snapshot created: {created}") ``` Committed content is then sent to the configured remote: ```python def push(repo: Path, branch: str) -> tuple[bool, str]: """Push ``branch`` to origin and set upstream. Returns (True, "") on success. Never force-pushes; a rejected non-fast-forward returns (False, stderr) so the caller can explain it. """ try: _net( ["push", "-u", REMOTE_NAME, branch], cwd=repo, timeout=PUSH_TIMEOUT, ) except GitError as exc: return False, str(exc) return True, "" ``` ### Technical Analysis Skill discovery treats a Skill as a directory containing `SKILL.md`, and status reporting explicitly distinguishes files outside every Skill root. Howev ...[truncated 2352 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace repository-wide staging with explicit pathspecs derived from validated Skill roots: ```python def add_skills(repo: Path, skill_roots: list[str]) -> None: if skill_roots: git("add", "-A", "--", *skill_roots, cwd=repo) ``` 2. Do not commit files outside discovered Skills by default. Require an explicit opt-in option for unmanaged files. 3. Before committing or pushing, display the exact file list and identify files outside Skill roots. 4. Add a confirmation boundary before the first upload and whenever unmanaged files would be included. 5. Run secret scanning before commits and pushes. Detect common API tokens, private keys, credential files, and high-entropy secrets. 6. Create or recommend a conservative `.gitignore` covering environment files, credentials, caches, editor files, and private keys. 7. Abort synchronization when likely credentials are detected, unless the user provides a deliberate override after reviewing the affected paths. 8. Document that deleting a secret in a later commit does not remove it from Git history, and provide incident-response instructions for credential rotation and history cleanup. ]]>
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 (16)

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill advertises and documents shell, file, environment, and network-capable operations but does not declare corresponding permissions. This creates a transparency and policy-enforcement gap: an agent or reviewer may underestimate the skill's ability to read/write skills, invoke Git or installer commands, or contact remote repositories, increasing the chance of unsafe execution.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The installer can automatically invoke apt-get, including via sudo or as root, to install python3-venv. That is broader system-modifying behavior than a user-scoped skill installer strictly needs, and when combined with a curl-piped installer model it increases the blast radius of compromise or mistakes by crossing from local setup into privileged package management.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The README recommends `curl ... | sh`, which executes code fetched over the network immediately without giving the user a chance to inspect it. In an agent-skills context, this is especially risky because users may copy these commands verbatim or let agents propose them, so a compromised upstream script, DNS/TLS interception edge case, or repository takeover could lead to arbitrary code execution.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The PowerShell instruction `irm ... | iex` has the same core issue as pipe-to-shell on Unix: it downloads and immediately executes remote code in the current user context. Because this project is designed for agent-operated workflows, presenting this as normal installation guidance increases the chance of unsafe automation and arbitrary code execution if the remote script is ever modified maliciously or unexpectedly.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The installer unconditionally deletes the existing virtual environment directory with `Remove-Item -Recurse -Force $VenvDir` before recreating it. While the path is fixed under the user's profile and not attacker-controlled in this script, this can still destroy an existing installation or locally modified environment without warning, creating avoidable data loss and reducing recoverability if the user stored custom packages or changes there.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script modifies the user's PATH persistently by appending `~\.local\bin` without an explicit consent step at install time. This is not arbitrary code execution by itself, but it changes the user's command-resolution behavior across future sessions and can create persistence, confusion, or command-shadowing risks if that directory later contains unexpected executables.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The installer unconditionally deletes the existing virtual environment directory with rm -rf before recreating it. While scoped to the expected install path, this can destroy an existing installation or locally modified environment without warning, creating avoidable data loss and making recovery harder.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script performs automatic apt-get installation without explicit user confirmation, and may do so through sudo. That behavior can surprise users, alter the host system outside the tool's main purpose, and becomes more dangerous because the installer is intended to be executed directly from a network fetch.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
`restore_skill` performs destructive repository actions (`git clean -fdq`, `git rm -r -f`, and checkout-based replacement) that can permanently discard untracked and uncommitted changes under the targeted skill path. In an agent context, wrapping these operations without any immediate safety interlock increases the chance of silent data loss if a higher-level component invokes it on the wrong path or target revision.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
`reset_hard` exposes a direct wrapper around `git reset --hard`, which irreversibly discards working-tree and index changes. In an autonomous skill, providing a no-friction primitive for destructive rollback creates a real risk of accidental or unauthorized loss of user work, especially if upstream intent validation is weak.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
try_apt_install() {
    if [ "$(id -u)" -eq 0 ]; then
        APT="env DEBIAN_FRONTEND=noninteractive apt-get"
    elif command -v sudo >/dev/null 2>&1; then
        APT="sudo env DEBIAN_FRONTEND=noninteractive apt-get"
    else
        echo "Error: python3-venv is missing and no sudo is available to install it." >&2
Confidence
92% confidence
Finding
This code path is part of logic that may run apt-get as root when the script is executed by UID 0. Privileged execution magnifies the impact of any installer bug, repository compromise, or unintended command behavior, especially in a bootstrap script fetched remotely.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
if [ "$(id -u)" -eq 0 ]; then
        APT="env DEBIAN_FRONTEND=noninteractive apt-get"
    elif command -v sudo >/dev/null 2>&1; then
        APT="sudo env DEBIAN_FRONTEND=noninteractive apt-get"
    else
        echo "Error: python3-venv is missing and no sudo is available to install it." >&2
        return 1
Confidence
92% confidence
Finding
This branch constructs a privileged apt-get command path and is part of the script's ability to make system-wide changes. Even if intended for convenience, embedding root-capable operations in a remotely fetched installer increases trust and compromise risk beyond the skill's stated Git/version-control functionality.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
elif command -v sudo >/dev/null 2>&1; then
        APT="sudo env DEBIAN_FRONTEND=noninteractive apt-get"
    else
        echo "Error: python3-venv is missing and no sudo is available to install it." >&2
        return 1
    fi
    echo "Attempting: $APT install $*" >&2
Confidence
93% confidence
Finding
The use of sudo allows the installer to transition from a user-level setup into privileged package installation. In context, that is a meaningful security concern because the script is designed to bootstrap software from remote sources, so privilege amplification increases potential damage if the script or its supply chain is compromised.

External Script Fetching

Low
Category
Supply Chain
Content
# SkillSync installer
#
# Usage:
#   curl -fsSL https://raw.githubusercontent.com/johnsonbuilds/skillsync/main/install.sh | sh
#
# What it does:
#   1. checks Python >= 3.12
Confidence
98% confidence
Finding
The documented installation method encourages piping a remotely fetched script directly into sh, which removes the user's opportunity to inspect the script before execution. This is particularly risky because the script can modify the filesystem, create environments, install packages from GitHub, and potentially invoke privileged package installation.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
any conflicted files outside any Skill;
* the two available choices:
  * adopt the remote version: `sync --use-remote`
    (aborts the rebase, `git reset --hard origin/main`; the abandoned local
    snapshots remain recoverable and their starting hash is printed);
  * keep the local version: manual `git push --force-with-lease`
    (SkillSync refuses to do this itself).
Confidence
84% confidence
Finding
The proposed 'sync --use-remote' flow performs 'git reset --hard origin/main', which is a destructive operation that discards local working tree and index state. Although the document says abandoned local snapshots remain recoverable, any uncommitted local changes or files outside committed history could still be lost if this path is triggered incorrectly or without strong safeguards.

Chaining Abuse

High
Category
Tool Misuse
Content
# SkillSync installer
#
# Usage:
#   curl -fsSL https://raw.githubusercontent.com/johnsonbuilds/skillsync/main/install.sh | sh
#
# What it does:
#   1. checks Python >= 3.12
Confidence
97% confidence
Finding
The advertised curl ... | sh pattern chains network retrieval directly into shell execution, creating a classic bootstrap trust problem. If the source, transport endpoint, account, or repository is compromised, arbitrary shell commands can execute immediately on the user's machine, and this script additionally contains logic for system-level package installation.

Static analysis

No suspicious patterns detected.