Back to skill

Security audit

Save To Spotify

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly aligned with creating private Spotify audio episodes, but its install, authentication, token, and connected-service data flows are too broad to treat as routine.

Install only if you are comfortable with an agent installing a Spotify CLI, linking your Spotify account, and creating private episodes. Prefer a reviewed, pinned, user-local install over the one-line remote installer; choose exact data sources before using daily-briefing style recipes; avoid scripts containing secrets, third-party PII, or confidential business data; and prefer local TTS for sensitive content.

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

T03 · Remote Payload Retrieval and Execution

Error
Location
SKILL.md:32
Finding
Mutable Remote Installer Is Downloaded and Executed Directly<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:32-40`; repeated in `references/onboarding.md:33-43` and `references/cli-usage.md:7-28` **Vulnerability Type**: Remote payload retrieval and immediate shell execution **Risk Level**: Critical ### Vulnerable Code `SKILL.md:32-40`: ```markdown ## Install If `save-to-spotify` is not available on `PATH`, ask the user to confirm CLI installation first, then install it: ```shell curl -fsSL https://saveto.spotify.com/install.sh | bash ``` On Windows, run this in **Git Bash** (ships with Git for Windows) — it installs `save-to-spotify.exe` to `~/.local/bin`. ``` `references/cli-usage.md:17-28` also provides parameterized versions: ```shell # Specific version curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --version 0.2.0 # Custom directory curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --dir ~/.local/bin # Via environment variables SAVE_TO_SPOTIFY_VERSION=0.2.0 SAVE_TO_SPOTIFY_INSTALL_DIR=~/.local/bin \ curl -fsSL https://saveto.spotify.com/install.sh | bash ``` ### Technical Analysis Piping `curl` directly into Bash executes the current response from a mutable remote endpoint without first saving, inspecting, or independently authenticating it. HTTPS protects transport to the endpoint but does not make the endpoint's future contents immutable. The documentation states that the installer downloads a binary and verifies its SHA-256 checksum. This does not address the primary trust problem because the installer performing that verification is itself the unverified remote payload. Neither the installer nor a pinned digest or trusted signing key is included in the audited project. The version argument only tells the remote script which release to install; it does not authenticate the script being executed. The installation can also target `/usr/local/bin`, and manual installation examples use `sudo`, potentially increasing the impact. ### Attack Path 1. The Agent l ...[truncated 1196 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove every `curl | bash` installation instruction. 2. Resolve and display an exact release version before downloading it. 3. Download the installer or binary to a local file without executing it: ```shell curl --proto '=https' --tlsv1.2 -fL \ -o save-to-spotify \ https://github.com/spotify/save-to-spotify/releases/download/v0.2.0/save-to-spotify-linux-amd64 ``` 4. Verify the artifact against a SHA-256 digest pinned in the reviewed Skill, not a digest downloaded from the same mutable source. 5. Prefer signed releases and verify the signature using a pinned publisher key. 6. Install into a user-owned directory such as `~/.local/bin`; do not default to `sudo` or `/usr/local/bin`. 7. Show the user the version, source URL, digest, destination, and requested privilege level before installation. 8. Include the installer source in the Skill package if its behavior is required for operation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/cli-usage.md:138
Finding
Spotify Bearer Token Is Exposed Through Agent-Visible Standard Output<![CDATA[ ## Vulnerability Details **File Location**: `references/cli-usage.md:138-164`; `references/spotify-api.md:25-87` **Vulnerability Type**: Sensitive bearer-token exposure **Risk Level**: Medium ### Vulnerable Code `references/cli-usage.md:138-164`: ```shell export SAVE_TO_SPOTIFY_AUTH_TOKEN="BQD..." ``` ```shell save-to-spotify token ``` The documented behavior is: ```text Prints the current access token to stdout -- directly usable as a Spotify Web API bearer for requests against `api.spotify.com`. ``` `references/spotify-api.md:29-43`: ```shell if ! TOKEN=$(save-to-spotify token); then echo "not authenticated; run: save-to-spotify auth login" >&2 exit 1 fi curl -sfG "https://api.spotify.com/v1/search" \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "q=artist:The Beatles" \ --data-urlencode "type=artist" \ --data-urlencode "limit=1" >/dev/null ``` `references/spotify-api.md:70-88`: ```python import json import subprocess from urllib.parse import urlencode from urllib.request import Request, urlopen API = "https://api.spotify.com/v1" def spotify_token(): return subprocess.run( ["save-to-spotify", "token"], capture_output=True, text=True, check=True, ).stdout.strip() def spotify_get(path, params): url = f"{API}{path}?{urlencode(params)}" req = Request(url, headers={"Authorization": f"Bearer {spotify_token()}"}) with urlopen(req, timeout=20) as resp: return json.load(resp) ``` ### Technical Analysis Catalog lookup is a legitimate part of the Skill, and the supplied examples send the token only to Spotify's official `api.spotify.com` endpoint. No intentional transmission to an unrelated recipient was identified. The insecure aspect is that the full bearer token is exported to Agent-visible stdout and copied into shell or Python variables. This expands the credential's exposure surface to command capture, debug traces, Agent context, subprocess instrumentation, ac ...[truncated 1518 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `save-to-spotify token` with a narrow command such as: ```shell save-to-spotify --json catalog search --query "The Beatles" --type artist ``` The CLI should perform authentication internally without returning the token. 2. Never print access or refresh tokens to Agent-visible stdout. 3. If token export must remain available for advanced users, require an explicit high-risk confirmation and keep it out of normal Skill workflows. 4. Request a separate least-privilege token restricted to the catalog operations required by the Skill. 5. Redact `Authorization` headers and token values from logs, traces, errors, and telemetry. 6. Ensure the persistent token file is created with restrictive user-only permissions. 7. Avoid storing tokens in shell variables or long-lived Python strings. 8. Revoke and rotate tokens if exposure through Agent traces is suspected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/episode-description.md:20
Finding
Untrusted Titles and URLs Are Interpolated into HTML Without Escaping<![CDATA[ ## Vulnerability Details **File Location**: `references/episode-description.md:20-35` **Vulnerability Type**: HTML and attribute injection **Risk Level**: Medium ### Vulnerable Code ```python import json timeline = json.load(open('timeline.json')) chapters = [item['chapter'] for item in timeline['items'] if 'chapter' in item] # source_links maps chapter title -> original article URL (from sourcing phase) source_links = {"Segment title": "https://source.url/article"} parts = ['<p>Summary of episode themes.</p>'] for ch in chapters: ms = ch['start_time_ms'] ts = f"({ms // 60000}:{(ms % 60000) // 1000:02d})" title = ch['title'] url = source_links.get(title) if url: parts.append(f"<p>{ts} - {title} - <a href='{url}'>source</a></p>") else: parts.append(f"<p>{ts} - {title}</p>") description = ''.join(parts) ``` ### Technical Analysis The chapter title and source URL can be derived from user-provided documents or externally sourced pages. Both are inserted into HTML without escaping. A title containing HTML markup can inject additional elements into the description. A URL containing a single quote can terminate the `href` attribute and inject additional attributes or markup. The instruction to use single quotes to simplify shell escaping does not make external URL data safe for HTML interpolation. The project does not demonstrate that all downstream Spotify clients apply sufficient sanitization. Even if scripts are removed by the backend, malformed or deceptive links and altered presentation remain possible. ### Attack Path 1. The Agent processes an attacker-controlled source or document. 2. The source supplies a malicious title or URL, for example: ```text Title: </p><p>Attacker-controlled message URL: https://example.test/' title='trusted' data-value='injected ``` 3. The value is copied into `timeline.json` or `source_links`. 4. The builder inserts it directly into the HTML description. 5. The m ...[truncated 576 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every externally influenced text value: ```python from html import escape safe_title = escape(str(ch["title"]), quote=True) ``` 2. Validate URLs with `urllib.parse.urlsplit`. 3. Allow only `https` and, where strictly required, `http` schemes. 4. Reject credentials in URLs, control characters, quotes, embedded whitespace, and unsupported schemes such as `javascript`, `data`, and `file`. 5. Escape the URL for attribute context: ```python safe_url = escape(validated_url, quote=True) ``` 6. Prefer a trusted HTML builder or a restricted metadata structure over string concatenation. 7. Add tests covering closing tags, quotes, ampersands, Unicode controls, and unsupported URL schemes. 8. Apply server-side sanitization as defense in depth before storing or rendering the description. ]]>

T08 · Insecure Dependencies

Warning
Location
references/audio-providers.md:109
Finding
Unpinned Dependencies, Models, and Mutable Remote Fonts Create Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `references/audio-providers.md:109-124`; `references/timeline.md:116-123`; `references/cover-image.md:70-119` **Vulnerability Type**: Unauthenticated and unpinned third-party dependencies **Risk Level**: Medium ### Vulnerable Code `references/audio-providers.md:109-124`: ```shell python3 -c "import openai" # OpenAI production snippet python3 -c "import elevenlabs" # ElevenLabs production snippet python3 -c "import kokoro_onnx" # Kokoro (use the venv python — see the Kokoro section) ffmpeg -version && ffprobe -version # Required for assembly (portable check) ``` ```text Install what's missing with pip before synthesis. ``` `references/timeline.md:116-123`: ```shell python3 -c " from diffusers import StableDiffusionPipeline pipe = StableDiffusionPipeline.from_pretrained('stabilityai/stable-diffusion-xl-base-1.0') image = pipe('A clean illustration of neural networks, minimal, dark background').images[0] image.save('img.png') " ``` `references/cover-image.md:95-119`: ```python FONT_CACHE = os.path.join(os.path.expanduser("~"), ".cache", "save-to-spotify", "fonts") FONTS = { "latin": ("Montserrat-Bold.ttf", "https://raw.githubusercontent.com/JulietaUla/Montserrat/master/fonts/ttf/Montserrat-Bold.ttf"), "arabic": ("Tajawal-Bold.ttf", "https://raw.githubusercontent.com/google/fonts/main/ofl/tajawal/Tajawal-Bold.ttf"), "hebrew": ("NotoSansHebrew-Bold.ttf", "https://raw.githubusercontent.com/google/fonts/main/ofl/notosanshebrew/NotoSansHebrew-Bold.ttf"), } def load_font(size, title=""): os.makedirs(FONT_CACHE, exist_ok=True) fname, url = FONTS[detect_script(title)] path = os.path.join(FONT_CACHE, fname) if not os.path.exists(path): urllib.request.urlretrieve(url, path) return ImageFont.truetype(path, size) ``` ### Technical Analysis The Skill tells the Agent to install missing Python packages through pip without specifying versions or hash ...[truncated 1781 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain a version-controlled lock file with exact package versions and hashes. 2. Install dependencies into a dedicated virtual environment rather than the user's global Python environment. 3. Use pip hash enforcement: ```shell python3 -m pip install --require-hashes -r requirements.lock ``` 4. Pin model repositories to immutable commit revisions. 5. Prefer safe tensor formats and disable unsafe remote model code. 6. Vendor small font assets inside the reviewed package, preserving their license files. 7. If fonts remain remote, use immutable release URLs and verify hard-coded SHA-256 digests before parsing. 8. Do not use mutable `master` or `main` branch URLs for runtime assets. 9. Require explicit user approval before installing packages or downloading large model components. 10. Periodically review and update pinned dependencies through a controlled security process. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
references/recipes.md:5
Finding
Default Recipes Perform Broad Reconnaissance Across Connected User Services<![CDATA[ ## Vulnerability Details **File Location**: `references/recipes.md:5-16` and `references/recipes.md:30-37`; reinforced by `references/onboarding.md:92-134` **Vulnerability Type**: Access beyond the minimum data scope required for audio creation **Risk Level**: Medium ### Vulnerable Code `references/recipes.md:5-16`: ```markdown When the user picks a recipe, **do not ask the input question**. Use the default input instead. The user can always refine after hearing the result. The goal is zero typing to first episode. ## Daily briefing **Description:** Your calendar, tasks, and repo activity as a short morning brief. **Default input:** Pull from whatever is available — Google Calendar, GitHub, Linear. Auto-detect connected services. **Input question (only if user asks to customize):** "What accounts or services should I pull from?" ``` `references/recipes.md:30-37`: ```markdown ## Deep dive **Description:** Turn a topic into a personalized audio explainer. **Default input:** Cannot be fully defaulted — the topic is the user's intent; a deep dive on a topic they didn't choose is homework, not a gift. Suggest, don't pick. **Input question:** "What should we dive into?" — present **3 personalized topic suggestions as options** (inferred from the user's repos, role, and recent activity — the same signals you'd have used to pick silently) plus the free-text field for their own topic. ``` `references/onboarding.md:96-105`: ```markdown **When the user picks a recipe, start generating immediately.** Use the recipe's default input — do not ask follow-up questions. Most things are defaulted: - Content: recipe's default input - Language: user's system locale - Length: recipe default - Voice: determined in Step 5 - Show: auto-created from recipe name ``` ### Technical Analysis The Daily Briefing recipe directs the Agent to auto-detect and read all available Google Calendar, GitHub, and Linear integrations rather than asking the user to select specific ...[truncated 2060 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Default to no connected data source. 2. Require the user to choose each service before access. 3. For each service, request selection of the exact account, calendar, repository, organization, project, and date range. 4. Show a concise access plan before reading data, for example: ```text Read today's events from Work Calendar, open PRs from org/repository, and assigned Linear tasks from Project A. ``` 5. Separate authorization for data collection, cloud TTS transmission, and Spotify upload. 6. Add mandatory secret, PII, and confidential-data detection before content leaves the local machine. 7. Exclude event descriptions, attendee details, private repository names, and task bodies unless specifically requested. 8. Provide a local-only TTS option before transmitting any sensitive script to a cloud provider. 9. Present the exact source list and sensitive categories in the preview approval step. 10. Record and enforce the user's source selections for the current episode only unless they explicitly opt into persistent preferences. ]]>
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 (54)

External Script Fetching

High
Category
Supply Chain
Content
If `save-to-spotify` is not available on `PATH`, ask the user to confirm CLI installation first, then install it:

```shell
curl -fsSL https://saveto.spotify.com/install.sh | bash
```

On Windows, run this in **Git Bash** (ships with Git for Windows) — it installs `save-to-spotify.exe` to `~/.local/bin`. The unsigned .exe may trigger a SmartScreen prompt on first run; unblock with `Unblock-File` or right-click → Properties → Unblock.
Confidence
98% confidence
Finding
Piping a remotely fetched script directly into bash executes network-supplied code without integrity verification, review, or pinning. If the server, DNS, TLS termination, or distribution path is compromised, users could execute arbitrary commands on their machine under their own privileges.

Chaining Abuse

High
Category
Tool Misuse
Content
If `save-to-spotify` is not available on `PATH`, ask the user to confirm CLI installation first, then install it:

```shell
curl -fsSL https://saveto.spotify.com/install.sh | bash
```

On Windows, run this in **Git Bash** (ships with Git for Windows) — it installs `save-to-spotify.exe` to `~/.local/bin`. The unsigned .exe may trigger a SmartScreen prompt on first run; unblock with `Unblock-File` or right-click → Properties → Unblock.
Confidence
98% confidence
Finding
The shell pipeline from curl to bash is a classic chaining-abuse pattern because it combines retrieval and execution into a single step, removing inspection opportunities and making malicious or tampered content immediately runnable. In an agent skill, this is especially risky because a loosely triggered workflow could cause the agent to recommend or attempt this dangerous pattern during onboarding or setup.

External Script Fetching

High
Category
Supply Chain
Content
### One-line install (recommended)

```shell
curl -fsSL https://saveto.spotify.com/install.sh | bash
```

Detects OS and architecture, downloads the binary from GitHub Releases, verifies the SHA256 checksum, and installs to `/usr/local/bin` (or `~/.local/bin` if not writable).
Confidence
99% confidence
Finding
`curl | bash` executes remote script content immediately without a prior local review step, creating a classic supply-chain and remote-code-execution risk. If the distribution host, DNS, TLS endpoint, or served script is compromised, users may run attacker-controlled code instantly.

Chaining Abuse

High
Category
Tool Misuse
Content
### One-line install (recommended)

```shell
curl -fsSL https://saveto.spotify.com/install.sh | bash
```

Detects OS and architecture, downloads the binary from GitHub Releases, verifies the SHA256 checksum, and installs to `/usr/local/bin` (or `~/.local/bin` if not writable).
Confidence
98% confidence
Finding
The shell pipeline chains network retrieval directly into code execution, which is a dangerous command-composition pattern. It removes inspection boundaries and makes compromise of the upstream content immediately exploitable as arbitrary shell execution.

External Script Fetching

High
Category
Supply Chain
Content
```shell
# Specific version
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --version 0.2.0

# Custom directory
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --dir ~/.local/bin
Confidence
99% confidence
Finding
This version-pinned install example still uses direct execution of a remotely fetched script. Pinning a version does not mitigate the core risk that the fetched installer itself may be malicious or tampered with at retrieval time.

External Script Fetching

High
Category
Supply Chain
Content
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --version 0.2.0

# Custom directory
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --dir ~/.local/bin

# Via environment variables
SAVE_TO_SPOTIFY_VERSION=0.2.0 SAVE_TO_SPOTIFY_INSTALL_DIR=~/.local/bin \
Confidence
99% confidence
Finding
This custom-directory example continues the same remote-script execution pattern. Because the install target may be agent-controlled or user-writable, it can also facilitate persistence if an attacker can influence the script or command context.

External Script Fetching

High
Category
Supply Chain
Content
# Via environment variables
SAVE_TO_SPOTIFY_VERSION=0.2.0 SAVE_TO_SPOTIFY_INSTALL_DIR=~/.local/bin \
  curl -fsSL https://saveto.spotify.com/install.sh | bash
```

### Download a binary manually
Confidence
99% confidence
Finding
Using environment variables with the same `curl | bash` pattern does not reduce the risk; it still executes untrusted remote script content immediately. In automated environments, this is especially dangerous because it may run with elevated CI secrets and noninteractive trust.

Chaining Abuse

High
Category
Tool Misuse
Content
# Via environment variables
SAVE_TO_SPOTIFY_VERSION=0.2.0 SAVE_TO_SPOTIFY_INSTALL_DIR=~/.local/bin \
  curl -fsSL https://saveto.spotify.com/install.sh | bash
```

### Download a binary manually
Confidence
98% confidence
Finding
This is the same chaining-abuse pattern in an environment-variable variant. In CI/agent contexts, such command chains are particularly risky because they often execute unattended with privileged filesystem or secret access.

Credential Access

High
Category
Privilege Escalation
Content
### Environment token (skip OAuth entirely)

If the user already has a Spotify access token (e.g. from another tool or CI secret):

```shell
export SAVE_TO_SPOTIFY_AUTH_TOKEN="BQD..."
Confidence
91% confidence
Finding
The docs instruct users to place a live Spotify access token in an environment variable, including CI contexts. Environment variables are frequently exposed to child processes, debug output, crash reports, and CI logs, making them a common secret-leak vector.

Credential Access

High
Category
Privilege Escalation
Content
Token refresh is automatic -- if the saved token is expired, the CLI refreshes it silently on the next command. No action needed unless the refresh token itself is revoked, in which case the user must `auth login` again.

### Print the access token

```shell
save-to-spotify token
Confidence
97% confidence
Finding
A dedicated command to print the current access token exposes a reusable credential on stdout. In agent or automated settings, stdout is often harvested by orchestrators and logs, so this creates a straightforward credential-exfiltration path.

Credential Access

High
Category
Privilege Escalation
Content
save-to-spotify token
```

Prints the current access token to stdout -- directly usable as a **Spotify Web API bearer** for requests against `api.spotify.com`. Useful for catalog lookups (searching album/track URIs, fetching release metadata) from inside recipes. Exits non-zero and prints a diagnostic to stderr when the stored token cannot be refreshed. Always check the exit code before piping into an `Authorization` header -- otherwise an empty token produces a misleading HTTP 400 from Spotify rather than a clean auth error.

See [spotify-api.md](spotify-api.md) for the official `developer.spotify.com` references, OpenAPI spec URL, endpoint patterns, and URI-resolution helpers.
Confidence
97% confidence
Finding
The documentation explicitly says the printed token is directly usable as a Spotify Web API bearer for external requests. This broadens the credential's attack surface and encourages reuse in ad hoc scripts where secrecy controls are weak.

External Script Fetching

High
Category
Supply Chain
Content
**Goal:** CLI installed and on PATH.

```
curl -fsSL https://saveto.spotify.com/install.sh | bash
```

On Windows, run it in Git Bash. The installer downloads the binary, verifies it, and prints:
Confidence
99% confidence
Finding
`curl ... | bash` executes a remotely fetched script immediately, giving the remote endpoint code-execution capability on the user's machine. Even if the text claims verification occurs inside the installer, the user must already trust and execute the script before that verification logic can run, making this a classic high-risk supply-chain pattern.

Chaining Abuse

High
Category
Tool Misuse
Content
**Goal:** CLI installed and on PATH.

```
curl -fsSL https://saveto.spotify.com/install.sh | bash
```

On Windows, run it in Git Bash. The installer downloads the binary, verifies it, and prints:
Confidence
98% confidence
Finding
The shell pipe to `bash` enables command chaining abuse by turning downloaded network content into immediate executable input. In an onboarding flow this is especially dangerous because it normalizes blind execution as the first step, increasing the chance of full host compromise if the server, transport, or release process is subverted.

Credential Access

High
Category
Privilege Escalation
Content
## Getting a bearer token

`save-to-spotify token` prints the current access token to stdout. That token has been verified to work as a Spotify Web API `Bearer` token for requests against `api.spotify.com`, so agents do not need a separate app registration for the catalog lookups below.

Guard the call because it exits non-zero when the stored token cannot be refreshed:
Confidence
94% confidence
Finding
The guidance explicitly states that `save-to-spotify token` prints the current access token to stdout, which is a high-risk credential exposure pattern in agent and automation environments. Stdout is commonly captured in logs, tool traces, shell history wrappers, CI artifacts, or model-visible output, so disclosure of the bearer token could let an attacker issue authenticated Spotify API requests as the user until expiration or revocation.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The section header and text say the skill is 'Read-only. Always.' and to 'never interact with source platforms beyond reading,' but the skill later instructs the agent to create shows, upload episodes, set timelines, and run setup flows that modify Spotify-side state. This is an active contradiction in the documentation about what the skill does, even if the 'read-only' language is intended only for source platforms.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The instruction to persist collected data after each sourcing step creates local storage of potentially sensitive source material, metadata, or user content without requiring disclosure or consent. If the workspace is shared, synced, or later exfiltrated, users may unknowingly leave behind private artifacts and browsing-derived data.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The onboarding trigger includes very broad phrases such as 'get started', 'help me start', and 'set up', which can cause the skill to activate in unrelated contexts. Over-broad routing increases the chance the agent enters an installation/authentication workflow unexpectedly, potentially leading to unintended tool execution or account-affecting actions.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Every episode — regardless of content type — must complete these steps.

0. **Preflight: install, auth, and voice engine** — Run `save-to-spotify --json doctor` before any sourcing. This checks the binary, auth, TTS engines, and ffmpeg in one call. If the binary is missing, ask the user to confirm installation, install it with the command in the Install section after they approve, then run doctor again. If unauthenticated, run `save-to-spotify setup` directly (do not ask the user to run it — just run it). The setup command handles auth + TTS detection in one pass and auto-detects headless environments. **Then confirm a TTS engine is available** via the `tts_engines` field in the doctor output: if one is already set up or the user has a preference, use it; otherwise check for an existing API key (`OPENAI_API_KEY`, `ELEVENLABS_API_KEY`) and suggest that engine first — no install, higher quality. If no key is present, **ask the user** whether to install **Kokoro** (free, local, ~340 MB, [Apache-2.0 licensed](https://raw.githubusercontent.com/hexgrad/kokoro/refs/heads/main/LICENSE)) — put the license link in the question text above the choices, where markdown renders it clickable; never inside option labels, which are plain text. Do not install silently. If they accept, run `save-to-spotify tts setup`. Confirm an engine is *available* before scripting; the interactive voice pick and preview can be deferred until the content is approved — content before audio. Voice previews use the player page in [references/local-preview.md](references/local-preview.md) ("Voice preview page"), never auto-play.
1. **Interview** — Ask the user about preferences, including companion-image source. Present a plan and **wait for confirmation**
2. **Script** — Present a short chapter overview (compact numbered list, bold chapter names, one line each, no blank lines between items) and get it approved first (revising an outline is free; never dump a full transcript on the user), then write the 
...[truncated 25 chars]
Confidence
86% confidence
Finding
The skill explicitly instructs the agent to run setup directly when unauthenticated instead of asking the user to do so, enabling autonomous initiation of authentication and environment-modifying flows. In context, this is more dangerous because the same preflight section also covers binary installation, TTS setup, and browser-based auth, so a generic trigger could cascade into account-linked actions without a fresh consent checkpoint.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document provides concrete instructions for using cloud TTS providers such as OpenAI, ElevenLabs, and Google Cloud, which necessarily transmit user-supplied text to third-party services, but it does not pair those instructions with an explicit privacy or data-handling warning. In this skill context, users may synthesize unpublished scripts, sensitive source material, or personal data, so omission of a transmission warning can lead to unintentional disclosure to external vendors.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The standard workflow directs the agent to download companion images, generate images with external services, upload media, and set the timeline, but it does not explicitly warn that these actions involve outbound network requests and third-party uploads. In this context, source URLs, generated assets, and local image files may contain confidential or copyrighted material, so users could unknowingly cause external disclosure or platform upload of sensitive content.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# macOS Apple Silicon
gh release download --repo spotify/save-to-spotify --pattern "save-to-spotify-darwin-arm64"
chmod +x save-to-spotify-darwin-arm64
sudo mv save-to-spotify-darwin-arm64 /usr/local/bin/save-to-spotify

# macOS Intel
gh release download --repo spotify/save-to-spotify --pattern "save-to-spotify-darwin-amd64"
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# macOS Apple Silicon
gh release download --repo spotify/save-to-spotify --pattern "save-to-spotify-darwin-arm64"
chmod +x save-to-spotify-darwin-arm64
sudo mv save-to-spotify-darwin-arm64 /usr/local/bin/save-to-spotify

# macOS Intel
gh release download --repo spotify/save-to-spotify --pattern "save-to-spotify-darwin-amd64"
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Linux x86_64
gh release download --repo spotify/save-to-spotify --pattern "save-to-spotify-linux-amd64"
chmod +x save-to-spotify-linux-amd64
sudo mv save-to-spotify-linux-amd64 /usr/local/bin/save-to-spotify
```

### Build from source
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# Linux x86_64
gh release download --repo spotify/save-to-spotify --pattern "save-to-spotify-linux-amd64"
chmod +x save-to-spotify-linux-amd64
sudo mv save-to-spotify-linux-amd64 /usr/local/bin/save-to-spotify
```

### Build from source
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The `tts add` example normalizes registering arbitrary external check commands, including inline Python execution. In an agent setting, this can become a command-execution primitive if untrusted input is ever incorporated into engine registration or copied blindly from docs.

Static analysis

No suspicious patterns detected.