Back to skill

Security audit

Xpoz Setup

Security checks for vulnerabilities and agentic risk

Overview

The skill's Xpoz authentication purpose is coherent, but its manual OAuth script has a real local code-execution flaw when handling pasted authorization codes.

Review this skill before installing. The Xpoz setup behavior is mostly disclosed and purpose-aligned, but the remote/headless OAuth script should be fixed before use: do not paste untrusted authorization-code text into this flow, require full callback state validation, and ensure tokens and mcporter config are acceptable for your environment.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/oauth-remote.sh:71
Finding

Authorization Code Injection Enables Arbitrary Local Code Execution

Content
View full analysis
}" python3 -c " import json, urllib.request, urllib.parse, subprocess, os, sys with open('${STATE_FILE}') as f: oauth = json.load(f) data = urllib.parse.urlencode({ 'grant_type': 'authorization_code', 'code': '${CODE}', 'redirect_uri': oauth['redirect_uri'], 'client_id': oauth['client_id'], 'code_verifier': oauth['verifier'], }).encode() req = urllib.request.Request( '${MCP_URL}/oauth/token', data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) resp = json.loads(urllib.request.urlopen(req).read()) token = resp.get('access_token', '') if not token: print('ERROR: No access token received', file=sys.stderr) sys.exit(1) subprocess.run(['mcporter', 'config', 'remove', 'xpoz'], capture_output=True) result = subprocess.run( ['mcporter', 'config', 'add', 'xpoz', '${MCP_URL}/mcp', '--header', f'Authorization=Bearer {token}'], capture_output=True, text=True ) " ``` The corresponding instruction directs the Agent to pass user-controlled content to this command: ```bash bash "$(dirname "$0")/../xpoz-setup/scripts/oauth-remote.sh" exchange AUTH_CODE_HERE ``` ### Technical Analysis The OAuth authorization code originates from the user's chat response and is assigned to the shell variable `CODE`. Although shell argument handling itself is quoted, the value is subsequently inserted directly into the source text passed to `python3 -c`. The interpolation occurs inside a Python string literal: ```python 'code': '${CODE}', ``` An authorization-code value containing Python quote delimiters and additional expressions can terminate the intended string and modify the generated Python program. For ex ...[truncated 1840 chars]
Remediation
View remediation
}" python3 - "$STATE_FILE" "$MCP_URL" "$CODE" <<'PY' import json import sys import urllib.parse import urllib.request state_file = sys.argv[1] mcp_url = sys.argv[2] code = sys.argv[3] with open(state_file, encoding="utf-8") as f: oauth = json.load(f) data = urllib.parse.urlencode({ "grant_type": "authorization_code", "code": code, "redirect_uri": oauth["redirect_uri"], "client_id": oauth["client_id"], "code_verifier": oauth["verifier"], }).encode() PY ``` Additional hardening should include: 1. Move the Python logic into a standalone, reviewed script instead of constructing it with `python3 -c`. 2. Treat all chat-provided OAuth values as untrusted input. 3. Parse callback URLs with a URL parser instead of performing textual extraction. 4. Reject malformed codes according to the authorization server's documented syntax, while retaining argument separation as the primary defense. 5. Add regression tests containing quotes, newlines, Python expressions, shell metacharacters, and Unicode input. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/oauth-remote.sh:27
Finding

OAuth State Is Generated but Not Validated

Content
View full analysis
}" python3 -c " import json, urllib.request, urllib.parse, subprocess, os, sys with open('${STATE_FILE}') as f: oauth = json.load(f) data = urllib.parse.urlencode({ 'grant_type': 'authorization_code', 'code': '${CODE}', 'redirect_uri': oauth['redirect_uri'], 'client_id': oauth['client_id'], 'code_verifier': oauth['verifier'], }).encode() ``` The Skill instructions also tell the Agent to extract only the `code` parameter when a complete callback URL is supplied, discarding the returned state: ```text If they pasted a URL, extract the `code` parameter. If they pasted a raw string, use it directly. ``` ### Technical Analysis OAuth state binds the callback to the authorization transaction initiated by the client. This implementation generates a cryptographically random state ...[truncated 2087 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/oauth-remote.sh:71
Finding

PKCE State File Persists After Failed Exchange

Content
View full analysis
}" python3 -c " import json, urllib.request, urllib.parse, subprocess, os, sys with open('${STATE_FILE}') as f: oauth = json.load(f) data = urllib.parse.urlencode({ 'grant_type': 'authorization_code', 'code': '${CODE}', 'redirect_uri': oauth['redirect_uri'], 'client_id': oauth['client_id'], 'code_verifier': oauth['verifier'], }).encode() req = urllib.request.Request( '${MCP_URL}/oauth/token', data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) resp = json.loads(urllib.request.urlopen(req).read()) token = resp.get('access_token', '') if not token: print('ERROR: No access token received', file=sys.stderr) sys.exit(1) result = subprocess.run( ['mcporter', 'config', 'add', 'xpoz', '${MCP_URL}/mcp', '--header', f'Authorization=Bearer {token}'], capture_output=True, text=True ) if result.returncode == 0: print('OK: Xpoz configured successfully') else: print(f'ERROR: mcporter config failed: {result.stderr}', file=sys.stderr) sys.exit(1) " # Clean up state file rm -f "$STATE_FILE" ;; ``` ### Technical Analysis With `set -e`, a nonzero exit from the Python command terminates the shell script before execution reaches `rm -f "$STATE_FILE"`. Failure can occur because of an invalid code, network error, malformed server response, missing token, or failed `mcporter` configuration. In all such cases, `state.json` can remain on disk containing: - The PKCE verifier. - The OAuth state value. - The dynamically registere ...[truncated 1356 chars]
Remediation
View remediation
}" cleanup() { rm -f -- "$STATE_FILE" } trap cleanup EXIT HUP INT TERM python3 /path/to/oauth_exchange.py "$STATE_FILE" "$MCP_URL" "$CODE" ;; ``` Further hardening should include: 1. Delete or invalidate the state after any final exchange attempt. 2. If retry support is required, retain state only after an explicit, documented decision and impose a short expiration time. 3. Record a creation timestamp and reject stale state files. 4. Continue using restrictive directory and file permissions. 5. Avoid following symbolic links when opening the state file and verify that it is a regular file owned by the current user. 6. Ensure error messages never include the access token, authorization code, or PKCE verifier. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (7)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

The skill handles OAuth authorization codes, PKCE material, and access tokens, and instructs the agent to exchange user-provided credentials and configure persistent authentication in mcporter. Even though it says the token is not printed, this is still credential-handling behavior with meaningful security consequences if the agent environment, logs, or local config are not tightly controlled.

Content

Scanner excerpt · SKILL.md (reported line 187)May include surrounding context.

md
Expected output: `OK: Xpoz configured successfully`

**Security notes:**
- The access token is never printed to stdout or logs
- The OAuth state file (`~/.cache/xpoz-oauth/state.json`) is automatically deleted after exchange
- Tokens are stored only in the mcporter config

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/oauth-remote.sh (reported line 97)May include surrounding context.

sh
token = resp.get('access_token', '')
if not token:
    print('ERROR: No access token received', file=sys.stderr)
    sys.exit(1)

# Configure mcporter directly — never print the token

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill instructs the agent to perform shell commands, network access, and local file writes during OAuth setup, but it does not declare an explicit tool scope such as permissions or allowed-tools. That makes the effective capability boundary unclear to the user and host platform, increasing the risk of over-broad execution and reducing the chance of policy enforcement before the skill runs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill requests OAuth authorization for access to social media intelligence across multiple platforms but does not clearly warn the user what data the agent may access after consent. This can lead to uninformed authorization, especially because the instructions emphasize minimal user interaction and encourage quick approval of browser prompts.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Content

Scanner excerpt · SKILL.md (reported line 30)May include surrounding context.

md
## Agent Instructions

**If you're an agent reading this, follow these steps in order. The user should not need to run any commands.**

### Step 1: Check if already authenticated

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 188)May include surrounding context.

md
**Security notes:**
- The access token is never printed to stdout or logs
- The OAuth state file (`~/.cache/xpoz-oauth/state.json`) is automatically deleted after exchange
- Tokens are stored only in the mcporter config

Then proceed to **Step 5** to verify.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/oauth-remote.sh (reported line 20)May include surrounding context.

sh
# Ensure state directory exists with restricted permissions
mkdir -p "$STATE_DIR"
chmod 700 "$STATE_DIR"

case "${1:-}" in
  get-url)

Static analysis

No suspicious patterns detected.