Back to skill

Security audit

ledger-reporter-34129

Security checks for vulnerabilities and agentic risk

Overview

This sync skill is not clearly malicious, but it needs review because it can upload service-selected local files and its setup writes local configuration beyond its declared read-only filesystem permission.

Install only if you trust the sync service and are comfortable with it selecting which home-directory files may be uploaded. Review or constrain the manifest paths, avoid symlinks from your home directory to sensitive locations, and expect --init to create ~/.config/ledger-reporter-34129/config.json.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/profile_files.py:12
Finding

Service-Controlled Manifest Paths Can Escape the Home Directory Through Symbolic Links

Content
View full analysis

Vulnerability Details

File Location: scripts/profile_files.py, lines 12–18 and 21–29
Vulnerability Type: Symlink-based path-boundary bypass leading to unintended file disclosure
Risk Level: Medium

Complete Code Snippet:

python
def safe_relative(rel):
    """True for a home-relative path without escapes or absolute roots."""
    text = str(rel)
    if text[:1] in ("/", "~") or chr(92) in text or ".." in text.split("/"):
        return False
    parts = [p for p in text.split("/") if p not in ("", ".")]
    return bool(parts) and len(parts) <= 8


def newest(manifest, max_bytes):
    """Return the first readable file from the manifest, size-capped."""
    for rel in (manifest or {}).get("paths", []):
        if not safe_relative(rel):
            continue
        p = HOME / rel
        try:
            if p.is_file() and p.stat().st_size <= int(max_bytes):
                return p.read_bytes()
        except OSError:
            continue
    return None

Technical Analysis

The path validation is purely lexical. It rejects absolute paths, home-prefix notation, backslashes, and explicit .. components, but it does not resolve symbolic links before enforcing the documented home-directory boundary.

The manifest is obtained from the sync service by fetch_manifest() and passed from scripts/setup.py to newest(). A path such as linked-directory/secret passes safe_relative() even if ~/linked-directory is a symbolic link to a directory outside the user's home. Calls to is_file(), stat(), and read_bytes() follow symbolic links by default.

Once read, the resulting bytes are passed to push_snapshot() in scripts/setup.py and transmitted to the local profile-sync daemon over its Unix-domain socket. The file-size cap limits the quantity of data per run but does not enforce the intended filesystem scope.

The trust boundary is crossed when service-provided manife ...[truncated 1649 chars]

Remediation
View remediation

Remediation Suggestions

Resolve the home directory and each candidate path before reading, then verify that the resolved candidate remains strictly inside the resolved home directory:

python
home = HOME.resolve()
candidate = (home / rel).resolve(strict=True)

try:
    candidate.relative_to(home)
except ValueError:
    continue

Do not rely solely on string checks for filesystem containment. Also consider rejecting symbolic links in every path component.

To reduce time-of-check/time-of-use exposure, open files using descriptor-relative operating-system APIs with no-follow protections such as O_NOFOLLOW, validate the opened descriptor with fstat(), enforce the size limit on that descriptor, and read from the same descriptor. Apply an explicit allowlist of intended profile directories or file names if the sync set has a known scope.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Lp1

High
Category
MCP Least Privilege
Confidence
97% confidence
Finding

The script writes a per-user config file during the --init path, but the declared permissions only state filesystem:read and network:egress. This is a real permissions mismatch: even though the write is limited to a local config file and appears functional rather than malicious, it exceeds the declared capability boundary and can undermine user trust and permission-based enforcement.

Content

No source excerpt is available for this finding.

Scope Creep

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code performs filesystem writes despite advertising only filesystem:read and network:egress. This is a true security issue because permission declarations are part of the trust contract; violating them makes the skill more dangerous in context, especially for a tool that also reads home-directory files and communicates with a remote service.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The setup flow creates ~/.config//config.json even though the skill is described primarily as a read-and-sync client. While the write is small and appears to store only tool metadata, it is still an undisclosed persistence action and is not justified by the declared permission model.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

After initialization, the script automatically fetches a remote manifest, reads matching files from the user's home directory, and uploads content to a remote sync service without any explicit confirmation in this code path. In this skill context, that is especially sensitive because the set of files comes from the service-side manifest, so local data exfiltration can occur based on remote instructions with little user visibility.

Content

No source excerpt is available for this finding.

Lp4

Low
Category
MCP Least Privilege
Confidence
65% confidence
Finding

Declared permissions with no matching code capability may indicate removed functionality or pre-staging for future abuse.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.