Back to skill

Security audit

Wip X

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real X/Twitter API tool, but it exposes account-changing actions and local-file upload paths with too little built-in control for agent use.

Install only if you intend to let an agent operate a real X account. Use least-privilege credentials, prefer read-only bearer tokens unless posting is needed, avoid the 1Password fallback until the shell invocation is fixed, and require explicit human approval for posting, deleting, bookmarking, and any file upload path.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
auth.mjs:9
Finding
Shell Command Injection Through Configurable 1Password References<![CDATA[ ## Vulnerability Details **File Location**: `auth.mjs:9-19` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```javascript const OP_VAULT = process.env.X_OP_VAULT || 'Agent Secrets'; const OP_ITEM = process.env.X_OP_ITEM || 'X API Key - wip-01'; /** * Read a field from 1Password. * Returns null if op CLI is not available or field not found. */ function opRead(field) { try { const ref = `op://${OP_VAULT}/${OP_ITEM}/${field}`; return execSync(`op read "${ref}" 2>/dev/null`, { encoding: 'utf8' }).trim() || null; } catch { return null; } } ``` ### Technical Analysis The values of `X_OP_VAULT` and `X_OP_ITEM` are read from the process environment and interpolated directly into a command executed through `execSync()`. Because `execSync()` receives a command string, Node.js invokes a shell to parse it. Placing the generated reference inside double quotes does not prevent shell command substitution. For example, an environment value containing `$(attacker-command)` can cause the shell to execute that command while constructing the argument passed to the `op` CLI. An injected double quote could also terminate the intended argument and introduce additional shell syntax. The vulnerable path is reached automatically during credential resolution whenever the corresponding direct X credential is absent and `resolveAuth()` falls back to `opRead()`. ### Attack Path 1. An attacker gains control over the environment used to launch the Skill, MCP server, or CLI, including `X_OP_VAULT` or `X_OP_ITEM`. 2. The attacker supplies shell syntax such as command substitution in one of those variables. 3. A caller invokes any operation requiring construction of the X client. 4. `resolveAuth()` attempts to retrieve missing credentials from 1Password. 5. `opRead()` embeds the attacker-controlled value into an `execSync()` command string. 6. The operating-system shell evaluates the injected syntax and executes the a ...[truncated 612 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not invoke the 1Password CLI through a shell. Use an argument-array API: ```javascript import { execFileSync } from 'node:child_process'; function opRead(field) { try { const ref = `op://${OP_VAULT}/${OP_ITEM}/${field}`; return execFileSync('op', ['read', ref], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], }).trim() || null; } catch { return null; } } ``` 2. Validate `X_OP_VAULT` and `X_OP_ITEM` against a conservative allowlist. Reject control characters, newlines, shell metacharacters, and unexpected path separators. 3. Prefer explicit configuration over automatically querying a broadly named default vault. 4. Distinguish “CLI unavailable,” “item unavailable,” and validation failures instead of suppressing every error. 5. Add tests containing command-substitution syntax, quotes, newlines, semicolons, and other shell metacharacters to verify that no shell interpretation occurs. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
core.mjs:211
Finding
Unrestricted Local File Read and Network Upload Exposed Through MCP<![CDATA[ ## Vulnerability Details **File Location**: `core.mjs:211-233`; exposed through `mcp-server.mjs:102-109, 140-141` **Vulnerability Type**: Unrestricted file access and unintended data transmission **Risk Level**: High ### Vulnerable Code ```javascript export async function upload_media({ file_path, media_data, media_type, alt_text } = {}) { if (!file_path && !media_data) throw new Error('file_path or media_data is required'); const client = await getClient(); let data = media_data; if (file_path && !data) { const buffer = readFileSync(file_path); data = buffer.toString('base64'); } // Detect media type from extension if not provided if (!media_type && file_path) { const ext = file_path.split('.').pop().toLowerCase(); const types = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', mp4: 'video/mp4', webp: 'image/webp' }; media_type = types[ext] || 'application/octet-stream'; } const body = { mediaData: data, mediaType: media_type, }; const result = await client.media.upload({ body }); ``` The unrestricted path is directly exposed to MCP callers: ```javascript { name: 'x_upload_media', description: 'Upload media (image/video/gif) for use in tweets. Returns a media ID.', inputSchema: { type: 'object', properties: { file_path: { type: 'string', description: 'Path to the file to upload' }, media_type: { type: 'string', description: 'MIME type (auto-detected from extension if omitted)' }, }, required: ['file_path'], }, }, ``` ```javascript case 'x_upload_media': result = await upload_media(params); break; ``` ### Technical Analysis The MCP tool accepts an arbitrary filesystem path and passes it unchanged to `readFileSync()`. The implementation does not: - Restrict files to a designated upload directory. - Resolve and validate canonical paths. - reject traversal or symbolic-link escapes. - Require explicit user approval for the reso ...[truncated 2057 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict uploads to one or more explicitly configured directories. 2. Resolve the requested path with `realpath()` and verify that the canonical path remains under an approved root. 3. Define and document a policy for symbolic links; preferably reject symlinks for agent-initiated uploads. 4. Require an explicit user confirmation that displays the canonical path, file size, detected type, and destination before transmission. 5. Enforce conservative file-size limits before reading content. 6. Validate supported formats using file signatures rather than relying only on extensions or caller-provided MIME types. 7. Reject unknown formats instead of defaulting them to `application/octet-stream`. 8. Use asynchronous or streaming file handling where supported to avoid blocking the event loop and duplicating the entire file in memory. 9. Separate local-path upload from base64-data upload and grant the local-file capability only when specifically required. 10. Apply MCP host authorization controls so untrusted content cannot autonomously invoke data-transmitting tools. ]]>
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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Credential Access

High
Category
Privilege Escalation
Content
*   X_BEARER_TOKEN       ... app-only bearer token (read operations)
 *   X_API_KEY            ... OAuth 1.0a consumer key
 *   X_API_SECRET         ... OAuth 1.0a consumer secret
 *   X_ACCESS_TOKEN       ... OAuth 1.0a access token
 *   X_ACCESS_TOKEN_SECRET ... OAuth 1.0a access token secret
 *
 * 1Password (configurable via env):
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
*   X_BEARER_TOKEN       ... app-only bearer token (read operations)
 *   X_API_KEY            ... OAuth 1.0a consumer key
 *   X_API_SECRET         ... OAuth 1.0a consumer secret
 *   X_ACCESS_TOKEN       ... OAuth 1.0a access token
 *   X_ACCESS_TOKEN_SECRET ... OAuth 1.0a access token secret
 *
 * 1Password (configurable via env):
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
*   X_BEARER_TOKEN       ... app-only bearer token (read operations)
 *   X_API_KEY            ... OAuth 1.0a consumer key
 *   X_API_SECRET         ... OAuth 1.0a consumer secret
 *   X_ACCESS_TOKEN       ... OAuth 1.0a access token
 *   X_ACCESS_TOKEN_SECRET ... OAuth 1.0a access token secret
 *
 * 1Password (configurable via env):
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
bearerToken: process.env.X_BEARER_TOKEN || opRead('bearer token'),
    apiKey: process.env.X_API_KEY || opRead('api key'),
    apiSecret: process.env.X_API_SECRET || opRead('api secret'),
    accessToken: process.env.X_ACCESS_TOKEN || opRead('access token'),
    accessTokenSecret: process.env.X_ACCESS_TOKEN_SECRET || opRead('access token secret'),
  };
Confidence
82% confidence
Finding
This line reads an access token from the environment or 1Password into process memory so the skill can authenticate. In context this is legitimate auth handling, but it is still sensitive credential access and becomes dangerous because the same module grants the skill direct ability to pull secrets from a local password manager, increasing the chance of overbroad secret exposure if the skill is compromised or misused.

Credential Access

High
Category
Privilege Escalation
Content
apiKey: process.env.X_API_KEY || opRead('api key'),
    apiSecret: process.env.X_API_SECRET || opRead('api secret'),
    accessToken: process.env.X_ACCESS_TOKEN || opRead('access token'),
    accessTokenSecret: process.env.X_ACCESS_TOKEN_SECRET || opRead('access token secret'),
  };

  if (!auth.bearerToken && !auth.apiKey) {
Confidence
82% confidence
Finding
This line accesses the OAuth access token secret, which is highly sensitive because possession enables authenticated write operations to the X account. While necessary for OAuth 1.0a, embedding logic to retrieve it from environment variables or 1Password inside the skill increases risk compared with having the hosting platform provide an already-authorized client or narrowly scoped secret.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The code exposes destructive and account-level actions including tweet deletion and bookmark modification that are not reflected in the stated skill description. Undisclosed destructive capabilities are especially risky because downstream agents or users may treat the skill as less privileged than it really is, increasing the chance of unauthorized or surprise account-impacting actions.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README advertises account-affecting operations such as posting, deleting, bookmarking, and uploading media, but it does not clearly warn users or downstream agents that these actions are destructive or externally visible. In an agentic context, documentation often drives autonomous tool use, so omission of safety guidance can increase the likelihood of unintended actions on a real X account.

External Transmission

Medium
Category
Data Exfiltration
Content
homepage: https://github.com/wipcomputer/wip-x
metadata:
  category: social,api
  api_base: https://api.x.com/2
  capabilities:
    - api
    - fetch-posts
Confidence
50% 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

Medium
Confidence
95% confidence
Finding
The skill explicitly exposes posting, replying, quote-tweeting, bookmarking, media upload, and deletion capabilities but does not warn that these actions can create public, user-visible, or irreversible changes on the user's X account. In an agent setting, this increases the risk of unintended social-media actions, accidental reputational harm, or deletion of content without clear user awareness or confirmation.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The code executes the external 1Password CLI via `execSync`, which expands the skill's capabilities beyond simple X API access into local secret-store interaction and shell invocation. Although the command string is built from environment-controlled vault and item names and quoted, using a shell for secret retrieval increases attack surface and can expose host secrets to a skill that only needs credentials passed in by the runtime.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The CLI exposes a destructive `delete` action that immediately deletes a tweet based solely on a positional argument, with no confirmation prompt, dry-run mode, or safety interlock. In an agent or automation context, malformed input, command confusion, or prompt/parameter injection could cause irreversible deletion of user content with no chance for recovery.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The code implements bookmark read and write operations for the authenticated account, but the described skill metadata only mentions reading posts, searching, posting, and uploading media. This creates a capability-transparency gap: an orchestrator or user may authorize or invoke actions affecting account state without realizing the skill can access and modify bookmarks.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The delete_tweet function performs an irreversible destructive action immediately when given an ID, with no built-in confirmation, dry-run, or warning mechanism. In an agent setting, this raises the risk of accidental, induced, or prompt-manipulated deletion of user content.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The upload_media function can read arbitrary local files via file_path and transmit their contents to the X API, but there is no visible disclosure, path restriction, or consent check. In an agent environment, this can become a local file exfiltration primitive if an attacker can influence tool inputs.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This server exposes a destructive x_delete_tweet capability directly through the tool interface with no confirmation step, policy gate, or friction before execution. In an agent setting, a mistaken prompt interpretation, prompt injection, or unsafe automation flow could trigger irreversible deletion of the user's content without clear user awareness at the time of action.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "homepage": "https://github.com/wipcomputer/wip-x",
  "dependencies": {
    "@xdevplatform/xdk": "^0.4.0"
  }
}
Confidence
90% confidence
Finding
The dependency uses a caret range (^0.4.0), which permits automatic installation of newer compatible versions rather than a single audited build. If the upstream package is compromised or a breaking security issue is introduced in an allowed version, consumers of this skill could pull the affected release without code changes, which is more concerning here because the skill interfaces with the X platform and likely handles API credentials and posting actions.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
auth.mjs:19

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
auth.mjs:75