Back to skill

Security audit

SoundCloud Watcher

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real SoundCloud monitoring skill, but its token handling has a concrete credential-leak risk that should be reviewed before installation.

Review this before installing. Use only credentials you are comfortable revoking, protect ~/.openclaw/secrets/soundcloud.env, avoid sharing terminal output from that file, and consider waiting for a fix that validates SoundCloud API URLs before sending OAuth tokens.

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

Error
Location
openclaw-soundcloud-watcher/soundcloud_watcher.ts:403
Finding
OAuth Access Token Can Be Forwarded to an Untrusted Pagination URL<![CDATA[ ## Vulnerability Details **File Location**: `openclaw-soundcloud-watcher/soundcloud_watcher.ts`, lines 403-420 and 505-509 **Vulnerability Type**: Unvalidated redirect target causing OAuth credential disclosure **Risk Level**: High ### Vulnerable Code ```ts let fullUrl: string; if (url.startsWith("/")) fullUrl = `${API_BASE}${url}`; else if (url.startsWith("http")) fullUrl = url; else fullUrl = `${API_BASE}/${url}`; if (params) { const sep = fullUrl.includes("?") ? "&" : "?"; const query = new URLSearchParams( Object.fromEntries( Object.entries(params).map(([k, v]) => [k, String(v)]) ) ).toString(); fullUrl = `${fullUrl}${sep}${query}`; } const headers: Record<string, string> = {}; if (this.config.accessToken) { headers["Authorization"] = `OAuth ${this.config.accessToken}`; } try { const resp = await fetch(fullUrl, { headers, signal: AbortSignal.timeout(API_TIMEOUT_MS), }); ``` The remotely supplied pagination URL reaches the method above through this code: ```ts const nextHref = data.next_href; if (nextHref && nextHref !== nextUrl) { nextUrl = nextHref; params = undefined; } else { break; } ``` ### Technical Analysis The API client accepts any string beginning with `http` as a complete request URL. It then attaches the SoundCloud OAuth access token to that request without checking the URL's scheme, hostname, port, or origin. The follower pagination logic obtains `next_href` from a remote API response and passes it back into this generic request method. Consequently, the trust decision about where an authenticated request may be sent is indirectly controlled by remote response data. This violates the security requirement that bearer-style credentials must only be transmitted to explicitly trusted origins. The check also accepts both `http://` and `https://` strings because it only tests `startsWith("http")`; therefore, a supplied HTTP URL could additionally expose the token in plaintext over the netw ...[truncated 1951 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse every outbound URL with `new URL()` rather than relying on string-prefix checks. 2. Require the `https:` protocol. 3. Maintain an explicit allowlist of SoundCloud API hosts, preferably only `api.soundcloud.com` unless additional official hosts are strictly necessary. 4. Reject user- or response-derived URLs containing unexpected credentials, ports, hosts, or protocols. 5. Attach the OAuth header only after confirming that the final destination is an approved SoundCloud origin. 6. Validate `next_href` before assigning or following it. 7. Consider accepting only relative pagination paths and reconstructing the URL against the fixed API base. 8. Disable automatic redirect following or validate every redirect destination, because an approved URL could otherwise redirect an authenticated request to another origin. 9. Add tests proving that tokens are not sent to: - Arbitrary HTTPS hosts - Plain HTTP URLs - Lookalike SoundCloud domains - URLs containing crafted host syntax - Cross-origin redirect destinations A hardened approach should resemble: ```ts private buildTrustedUrl(input: string): URL { const parsed = input.startsWith("/") ? new URL(input, API_BASE) : new URL(input); if ( parsed.protocol !== "https:" || parsed.hostname !== "api.soundcloud.com" || parsed.port !== "" ) { throw new Error("Rejected untrusted SoundCloud API URL"); } return parsed; } ``` The authorization header should only be created after `buildTrustedUrl()` succeeds. Redirects should be disabled with `redirect: "manual"` unless each destination is independently revalidated. ]]>
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 (22)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```bash
openclaw plugins disable soundcloud-watcher
rm -rf ~/.openclaw/extensions/soundcloud-watcher
```

Optionally remove data:
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```bash
openclaw plugins disable soundcloud-watcher
rm -rf ~/.openclaw/extensions/soundcloud-watcher
```

Optionally remove data:
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
Optionally remove data:
```bash
rm ~/.openclaw/secrets/soundcloud.env
rm ~/.openclaw/data/artists.json
rm ~/.openclaw/data/soundcloud_tracking.json
```
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
Optionally remove data:
```bash
rm ~/.openclaw/secrets/soundcloud.env
rm ~/.openclaw/data/artists.json
rm ~/.openclaw/data/soundcloud_tracking.json
```
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```bash
rm ~/.openclaw/secrets/soundcloud.env
rm ~/.openclaw/data/artists.json
rm ~/.openclaw/data/soundcloud_tracking.json
```

## Changelog
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented purpose is narrower than the behavior implied by the file: it instructs users to place secrets in a local path, exposes management commands, and relies on capabilities not clearly disclosed. This mismatch is dangerous because users may grant trust or install the skill for simple notifications while it actually requires broader local and external access.

Credential Access

High
Category
Privilege Escalation
Content
this.clientSecret = clientSecret;
    this.myUsername = username;

    // Load persisted access token from env file if it exists
    if (fs.existsSync(CONFIG_FILE)) {
      for (const line of fs.readFileSync(CONFIG_FILE, "utf-8").split("\n")) {
        if (!line.includes("=") || line.startsWith("#")) continue;
Confidence
88% confidence
Finding
The code reads an access token from a local secrets file and then uses it for API access. Accessing credentials is expected for this integration, but the file-based retrieval pattern broadens exposure if the secrets file is readable by other users, copied into backups, or reused across tools without isolation.

Credential Access

High
Category
Privilege Escalation
Content
private async ensureToken(): Promise<string | null> {
    // Proactive refresh: check if token exists AND is not expiring soon
    if (!(await this.api.ensureValidToken())) {
      return "Failed to get/refresh access token. Check your clientId and clientSecret.";
    }
    return null;
  }
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
### 3. Configure

Create the credentials file:

```bash
nano ~/.openclaw/secrets/soundcloud.env
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README tells users to run `cat ~/.openclaw/secrets/soundcloud.env`, which will print the SoundCloud client secret and username to the terminal. This can expose secrets to shoulder-surfing, terminal scrollback, session recording, or shared support logs if users copy/paste output while troubleshooting.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises capabilities that imply network and shell use, but it does not declare any explicit tool scope or permissions. In agent/plugin systems, missing permission declarations undermine least-privilege controls and can cause users or orchestrators to approve a skill without understanding the real execution surface.

Session Persistence

Medium
Category
Rogue Agent
Content
## Configuration

Create `~/.openclaw/secrets/soundcloud.env`:

```
SOUNDCLOUD_CLIENT_ID=your_client_id
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
1. Get credentials from https://soundcloud.com/you/apps

2. Create config file:
\`\`\`bash
nano ~/.openclaw/secrets/soundcloud.env
\`\`\`
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The skill stores OAuth token material in a persistent local secrets file under the user's home directory. Persisting credentials is common for integrations, but doing so without clear disclosure, encryption, or permission hardening increases the risk of local credential compromise if the host or account is shared or later breached.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The manifest description limits the skill to monitoring the account, tracking artist releases, and notifying about new followers and likes. The implemented code goes beyond that scope by detecting lost followers and renamed followers (L0608-L0633), repost engagement (L0697-L0702), and exposing add/remove/list artist management operations (L0990-L1017), which are not reflected in the manifest description.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The code saves the SoundCloud access token directly into a local env-style file without any visible permission setting, encryption, or user-facing disclosure. A local attacker, another process running as the same user, or backups/logging workflows could recover the token and use it to access the associated SoundCloud account or API quota.

External Transmission

Medium
Category
Data Exfiltration
Content
const body = new URLSearchParams({
        grant_type: "client_credentials",
      });
      const resp = await fetch("https://secure.soundcloud.com/oauth/token", {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Low
Confidence
85% confidence
Finding
The skill asks users to store client credentials in a local secrets file but does not clearly warn that the skill will access and use those sensitive values. While this is common for integrations, lack of disclosure reduces informed consent and increases the chance of mishandling or overtrust in environments where multiple skills or agents may access local files.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown-like user-facing output tells users to obtain and store a client ID and client secret in a file under their home directory, but it does not include any warning that these values are sensitive or should be protected. For a skill that handles credentials, a brief disclosure about safeguarding the file or limiting access would improve user safety.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"node": ">=22.0.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "typescript": "^5.9.3"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/node": "^22.0.0",
    "typescript": "^5.9.3"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Missing User Warnings

Low
Confidence
86% confidence
Finding
This helper performs file writes that are later used to persist account follower data, engagement state, artist lists, and backoff metadata. The code includes no user-facing notice at the write sites or in exported method documentation that running the watcher stores account-related monitoring data on disk.

Static analysis

Detected: suspicious.destructive_delete_command, suspicious.exposed_secret_literal

Documentation contains a destructive delete command without an explicit confirmation gate.

Warn
Code
suspicious.destructive_delete_command
Location
README.md:144

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
openclaw-soundcloud-watcher/index.ts:51