Back to skill

Security audit

PLEX-CTL

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Plex control tool, but it stores a Plex access token insecurely and can contact Plex cloud services despite local-only privacy claims.

Review before installing. Use this only if you are comfortable giving the tool a Plex token that can access your Plex server and control clients. Protect ~/.plexctl/config.json, avoid committing or sharing it, consider tightening permissions manually, and expect some commands to contact Plex cloud services for client discovery.

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

T09 · Insecure Skill Coding Practices

Error
Location
plexctl.py:44
Finding

Plex Authentication Token Stored Without Restrictive File Permissions

Content
View full analysis

Vulnerability Details

File Location: plexctl.py:44-49
Vulnerability Type: Plaintext credential storage with insufficient access controls
Risk Level: High

Vulnerable Code

python
def save_config(cfg):
    """Save config to ~/.plexctl/config.json"""
    os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
    with open(CONFIG_PATH, "w") as f:
        json.dump(cfg, f, indent=2)
    print(f"✓ Config saved to {CONFIG_PATH}")

The configuration written by this function contains the Plex authentication token:

python
cfg = {
    "plex_url": url,
    "plex_token": token,
    "default_client": default_client
}
save_config(cfg)

Technical Analysis

The application stores a privileged Plex authentication token in plaintext at ~/.plexctl/config.json. Neither the containing directory nor the file is assigned an explicit restrictive permission mode.

When the file is created through open(CONFIG_PATH, "w"), its effective permissions depend on the process umask. A common umask can produce a file with mode 0644, allowing other local users to read it. If the file already exists with permissive permissions, opening it for writing does not repair those permissions.

The directory is similarly created without explicitly enforcing mode 0700. This violates least-privilege credential-storage practices and makes the security of the token dependent on external environment configuration.

Attack Path

  1. A user runs plexctl setup and supplies a valid Plex authentication token.
  2. save_config writes the token to ~/.plexctl/config.json.
  3. The file is created under a permissive umask or was previously created with overly broad permissions.
  4. Another local user or process reads the configuration file.
  5. The attacker extracts the Plex server URL and authentication token.
  6. The attacker submits authenticated Plex API requests using the stolen token.

Impact As

...[truncated 587 chars]

Remediation
View remediation

Remediation Suggestions

  • Create the configuration directory with mode 0700.
  • Create the configuration file with mode 0600, independently of the process umask.
  • Check existing permissions during loading and either reject or repair an overly permissive file.
  • Write configuration changes to a securely created temporary file in the same directory, set mode 0600, flush and synchronize it, and atomically replace the destination.
  • Avoid following attacker-controlled symbolic links when opening the credential file.
  • Prefer an operating-system credential store or keyring for the Plex token where supported.
  • Do not print, log, or include the token in exception messages.

Example hardening pattern:

python
def save_config(cfg):
    config_dir = os.path.dirname(CONFIG_PATH)
    os.makedirs(config_dir, mode=0o700, exist_ok=True)
    os.chmod(config_dir, 0o700)

    flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
    fd = os.open(CONFIG_PATH, flags, 0o600)
    try:
        os.fchmod(fd, 0o600)
        with os.fdopen(fd, "w") as f:
            json.dump(cfg, f, indent=2)
    except Exception:
        os.close(fd)
        raise

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Unbounded Third-Party Dependency Creates Supply-Chain Exposure

Content
View full analysis

Vulnerability Details

File Location: requirements.txt:1
Vulnerability Type: Unpinned dependency without integrity verification
Risk Level: Medium

Vulnerable Code

text
plexapi>=4.15.0

The installation documentation also instructs users to retrieve the latest accepted package directly:

bash
pip install plexapi

Technical Analysis

The dependency declaration specifies only a minimum version. Any future plexapi release satisfying the constraint can therefore be installed without review. The project provides no lockfile, upper bound, or package hashes to make installation reproducible or verify the expected distribution artifact.

Python packages can execute code during installation and later execute code when imported. This project imports plexapi.server while handling setup and server connections. Consequently, a compromised or unexpectedly unsafe future dependency release would execute with the privileges of the user running the CLI and could access the plaintext Plex configuration.

This finding does not establish that the current plexapi package is malicious. It identifies an avoidable supply-chain trust boundary caused by accepting uncontrolled future versions.

Attack Path

  1. A future plexapi release accepted by the >=4.15.0 constraint is compromised, malicious, or otherwise unsafe.
  2. A user follows the documented installation command or installs requirements.txt.
  3. pip resolves and downloads the uncontrolled release.
  4. Package installation code or imported package code executes with the installing user's privileges.
  5. Malicious code reads ~/.plexctl/config.json, accesses local files, or performs other actions available to that user.

Impact Assessment

Successful exploitation through a malicious dependency can provide arbitrary code execution under the account installing or running the package. This may expose the Plex token, Plex library ...[truncated 294 chars]

Remediation
View remediation

Remediation Suggestions

  • Pin plexapi to a specifically audited version rather than using a lower-bound-only constraint.
  • Generate a lockfile containing exact transitive dependency versions.
  • Record and enforce cryptographic hashes for every downloaded distribution.
  • Install dependencies in an isolated virtual environment under an unprivileged account.
  • Use a controlled package index or approved artifact mirror where appropriate.
  • Add automated dependency vulnerability and provenance monitoring.
  • Review and deliberately test dependency upgrades before updating the lockfile.
  • Replace the documented unrestricted command with a hash-verified installation process, such as:
bash
python3 -m pip install --require-hashes -r requirements.txt

other

Note
Location
plexctl.py:92
Finding

Automatic Plex Cloud Discovery Conflicts With Local-Only Privacy Claims

Content
View full analysis

Vulnerability Details

File Location: plexctl.py:92-99
Additional Location: plexctl.py:323-334
Vulnerability Type: Undisclosed or misleading external service communication
Risk Level: Low

Vulnerable Code

The client-resolution path automatically falls back to MyPlex:

python
# Fallback: cloud discovery via MyPlex account
try:
    account = plex.myPlexAccount()
    for res in account.resources():
        if "player" in res.provides and res.name == client_name:
            return res.connect()
except Exception:
    pass

The clients command also performs cloud account enumeration:

python
# Also check cloud
try:
    account = plex.myPlexAccount()
    cloud_players = [r for r in account.resources() if "player" in r.provides]
    if cloud_players:
        print("\nCloud (MyPlex):")
        for r in cloud_players:
            print(f"  • {r.name}")
            print(f"    {r.product}")
            print()
except Exception:
    pass

The README makes broader claims that do not accurately describe this automatic behavior:

markdown
- **Local only**: All communication is with your local Plex server
- **No cloud APIs**: Direct network connection (cloud discovery is optional fallback)
- **No external services**: No data sent to third parties

Technical Analysis

When local client resolution fails, get_client automatically invokes plex.myPlexAccount() and enumerates account resources. The clients command also invokes cloud discovery after listing local clients. There is no command-line option or configuration setting that allows users to disable these requests.

This behavior conflicts with claims that all communication remains local, that cloud discovery is optional, and that no data is sent to external services. The fallback may generate authenticated traffic to Plex infrastructure and retrieve account resource information without ...[truncated 1326 chars]

Remediation
View remediation

Remediation Suggestions

  • Default to local discovery and require explicit consent before contacting MyPlex.
  • Add a flag such as --allow-cloud-discovery and a persistent configuration option that defaults to disabled.
  • Clearly document which commands can contact Plex cloud infrastructure.
  • Identify the destination, authentication mechanism, and categories of metadata involved.
  • Revise “local only,” “no cloud APIs,” and “no external services” claims so they accurately reflect actual behavior.
  • Display a clear notice when cloud fallback is attempted.
  • Avoid silently suppressing all cloud-discovery exceptions; report a concise diagnostic without exposing credentials.
  • Consider separating local and cloud client listings into distinct commands so the user can make an informed choice.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (4)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill explicitly instructs users how to obtain and store a Plex access token in a local plaintext config file, but does not warn that the token is a sensitive bearer credential that should be protected, minimally scoped where possible, and rotated if exposed. If this documentation is followed carelessly, the token could be leaked through shell history, screenshots, shared configs, backups, logs, or source control, allowing unauthorized access to the user's Plex server and playback controls.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The setup flow stores the Plex authentication token directly in ~/.plexctl/config.json as plaintext, which exposes a long-lived credential to any local user, malware, backup system, or process that can read the file. In this skill's context, the token grants authenticated access to the user's Plex account/server and may enable library access and remote control of connected clients, so compromising the file can lead to unauthorized media access and device control.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README instructs users to store a Plex authentication token in a local JSON config file but does not clearly label the token as a sensitive secret or advise users to protect the file with appropriate permissions. If the config file is exposed through backups, shared home directories, screenshots, or source control, an attacker could reuse the token to access or control the user's Plex environment.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified with a lower bound only (plexapi>=4.15.0), which allows future major or minor releases to be installed without review. That can introduce breaking changes or a compromised/upstream-vulnerable version into the environment, reducing build reproducibility and weakening supply-chain control.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
plexapi>=4.15.0

Static analysis

No suspicious patterns detected.