Back to skill

Security audit

X Manager

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent X/Twitter account manager, but it stores powerful account tokens in local JSON files and can perform public account actions without strong account-ownership or confirmation safeguards.

Install only if you are comfortable giving the skill access to X/Twitter credentials that may post, reply, like, retweet, and read account data. Use a dedicated low-privilege account or tokens where possible, keep real credential files out of the package and source control, restrict file permissions, rotate/revoke tokens if exposed, and require human approval before any public posting or engagement action.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/get_timeline.py:10
Finding

Unvalidated User Identifier Enables Cross-User Credential Access and Path Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/get_timeline.py:10-16; the same credential-loading pattern appears in scripts/get_user_tweets.py:10-16, scripts/like_tweet.py:10-16, scripts/post_tweet.py:14-21, scripts/reply_tweet.py:10-16, scripts/retweet.py:10-16, and scripts/search_tweets.py:11-17
Vulnerability Type: Path traversal and broken tenant authorization
Risk Level: High

Vulnerable Code

python
def load_credentials(user_id):
    cred_path = os.path.join(SKILL_DIR, 'credentials', f'{user_id}.json')
    if not os.path.exists(cred_path):
        raise Exception(f"No credentials found for user {user_id}. Configure Twitter credentials first.")

    with open(cred_path, 'r') as f:
        creds = json.load(f)

    if 'twitter' not in creds:
        raise Exception("Twitter credentials not found")

    return creds['twitter']

The identifier is supplied directly through a command-line argument:

python
parser.add_argument('user_id')

Technical Analysis

Every script constructs a credential path by interpolating the caller-controlled user_id into a filename. The value is not restricted to a canonical identifier format, resolved against a trusted root, or checked against the identity of the authenticated caller.

This creates two related authorization weaknesses:

  1. A caller who knows or guesses another tenant's identifier can select that tenant's credential file directly.
  2. A value containing directory traversal components, such as ../../other/location/account, can cause the resolved path to leave the intended credentials directory and access another JSON file whose name ends in .json.

Merely checking whether the resulting path exists does not ensure that it remains inside the credential directory. The scripts then use the selected credentials for X/Twitter API requests. Consequently, exploitation does not require the secret values to be printed: the ...[truncated 1500 chars]

Remediation
View remediation

Remediation Suggestions

  1. Validate user_id against a strict allowlist before using it in a path, for example ^[A-Za-z0-9_-]+$, and reject empty, absolute, dotted, or separator-containing values.
  2. Resolve the candidate path with pathlib.Path.resolve() and verify that it is a descendant of the resolved credential directory before opening it.
  3. Do not treat a caller-provided user identifier as authorization. Derive the credential owner from an authenticated execution context or verify that the caller is authorized to use the requested account.
  4. Open credential files defensively and reject symbolic links where supported.
  5. Centralize credential loading in one hardened module instead of duplicating the vulnerable implementation across all scripts.
  6. Record security-relevant access attempts without logging credentials or tokens.
  7. Add tests covering traversal values, absolute paths, symbolic links, encoded separators, and attempts to select another tenant's identifier.

Example containment validation:

python
import re
from pathlib import Path

USER_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$")

def credential_path(user_id):
    if not USER_ID_PATTERN.fullmatch(user_id):
        raise ValueError("Invalid user identifier")

    credential_root = (Path(SKILL_DIR) / "credentials").resolve()
    candidate = (credential_root / f"{user_id}.json").resolve()

    if candidate.parent != credential_root:
        raise ValueError("Credential path escapes the credential directory")

    return candidate

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:31
Finding

Long-Lived X/Twitter Credentials Are Stored in Plaintext Without Permission Enforcement

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:31-49, credentials/example.json:1-9, and the credential loaders in all scripts
Vulnerability Type: Insecure storage of sensitive credentials
Risk Level: Medium

Vulnerable Configuration

SKILL.md directs users to store account secrets directly in project-local JSON files:

json
{
  "twitter": {
    "api_key": "",
    "api_secret": "",
    "access_token": "",
    "access_token_secret": "",
    "bearer_token": ""
  }
}

The scripts read those files without checking ownership, permissions, symbolic-link status, or whether the storage location provides adequate protection:

python
cred_path = os.path.join(SKILL_DIR, 'credentials', f'{user_id}.json')
if not os.path.exists(cred_path):
    raise Exception(f"No credentials found for user {user_id}. Configure Twitter credentials first.")

with open(cred_path, 'r') as f:
    creds = json.load(f)

Technical Analysis

API secrets, access tokens, access-token secrets, and bearer tokens are long-lived authentication material. The documented architecture stores them as unencrypted plaintext under the Skill installation directory.

No audited code creates files with restrictive permissions, checks that files are owned by the expected service account, rejects insecure permission modes, or integrates with a dedicated secret-management service. If credentials are provisioned with permissive defaults, copied into an artifact, backed up insecurely, or made visible to another local process, they can be extracted and reused independently of the Skill.

The repository contains only an empty example credential file, so no hardcoded live secret was identified. The vulnerability concerns the documented production storage model and the absence of runtime protection for populated files.

Attack Path

  1. A user or deployment process places active X/Twitter credentials in `credentials/{USER_I ...[truncated 985 chars]
Remediation
View remediation

Remediation Suggestions

  1. Store production credentials in an operating-system keychain, managed secret store, or platform-provided encrypted credential service rather than project-local JSON files.
  2. If filesystem storage is unavoidable, place credentials outside the package directory and encrypt them at rest using a key maintained separately from the files.
  3. Create credential files atomically with mode 0600 and ensure the credential directory is accessible only to the service account.
  4. Before reading a file, verify its owner, permission mode, regular-file status, and resolved location. Reject symbolic links and files readable by unintended users.
  5. Ensure real credential files are excluded from source control, build artifacts, logs, diagnostics, backups, and support bundles.
  6. Use narrowly scoped and short-lived tokens where the X/Twitter API supports them.
  7. Establish token rotation and immediate revocation procedures for suspected exposure.
  8. Avoid returning raw upstream error bodies where they could disclose sensitive account or authorization details.
  9. Document secure provisioning requirements and add deployment checks that fail when credential permissions are too broad.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (31)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documents capabilities that require filesystem reads and network access, but it does not declare any explicit tool scope or permission boundaries. That makes the effective authority of the skill ambiguous and increases the risk of overbroad execution, especially because it handles credentials and performs external actions on behalf of users.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill description is broad enough to encourage activation for many loosely related requests involving X/Twitter, without clear trigger constraints or user-confirmation boundaries. In a skill ecosystem, this can cause over-invocation of a capability that can post, engage, search, and analyze data, increasing the chance of unintended account actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The documentation normalizes local storage of highly sensitive credentials without warning users about plaintext secret exposure, file permission risks, or safer alternatives. This is dangerous because users may adopt insecure storage practices that directly expose tokens capable of posting, reading account data, and impersonating account owners.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs storing per-user Twitter API secrets in local JSON files under predictable paths, which creates a concentrated local secret store. If the host, workspace, logs, backups, or neighboring skills are exposed, attackers could recover credentials and take over users' X accounts or abuse API access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill documents autonomous reply behavior driven by keywords and templates, but does not warn about automatic posting risks, reputation damage, spam, policy violations, or the need for explicit user consent. In this context, the danger is elevated because the skill can act publicly on social media at scale and could be triggered repeatedly without human review.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script silently loads stored bearer credentials and transmits them to the Twitter API without any user-facing notice, confirmation, or audit signal in this file. In an agent skill context that can act on behalf of multiple users, hidden credential use reduces transparency and can enable unexpected access to account data if invoked without the operator fully understanding that stored tokens are being used.

Content

No source excerpt is available for this finding.

Tainted flow: 'url' from requests.get (line 36, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/get_timeline.py (reported line 37)May include surrounding context.

python
# Get timeline
    url = f"https://api.twitter.com/2/users/{user_id_twitter}/timelines/reverse_chronological?max_results={count}"
    response = requests.get(url, headers=headers)
    
    if response.status_code == 200:
        return {"success": True, "tweets": response.json()}

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script silently loads stored Twitter credentials from a local credentials directory based solely on a supplied user_id, with no in-script consent prompt, disclosure, or authorization check. In an agent-skill context, this increases the risk of unintended use of another configured account’s bearer token if the caller can influence user_id or invoke the script without clear user awareness.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script makes authenticated outbound API calls to Twitter without any user-facing notice at execution time. In an agent environment, undisclosed network actions using stored credentials can surprise users, leak behavioral metadata, and normalize silent third-party interactions under their account context.

Content

No source excerpt is available for this finding.

Tainted flow: 'tweets_url' from requests.get (line 39, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/get_user_tweets.py (reported line 40)May include surrounding context.

python
# Get tweets
    tweets_url = f"https://api.twitter.com/2/users/{twitter_user_id}/tweets?max_results={count}"
    response = requests.get(tweets_url, headers=headers)
    
    if response.status_code == 200:
        return {"success": True, "tweets": response.json()}

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This script performs a state-changing Twitter action on behalf of a user by loading stored bearer credentials and issuing a like request with no confirmation, authorization check, or policy guardrail. In an agent skill context, that makes unintended or unauthorized account activity easy if the skill is invoked with attacker-influenced inputs or by a confused deputy workflow.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
91% confidence
Finding

The POST sends a state-changing request to Twitter using stored bearer credentials, causing an external side effect on the linked account. While contacting Twitter is expected for this skill, the dangerous part is that the transmission directly triggers account activity without validation of caller intent, ownership, or user confirmation.

Content

Scanner excerpt · scripts/like_tweet.py (reported line 43)May include surrounding context.

python
user_id_twitter = me_resp.json()['data']['id']
    
    data = {"tweet_id": tweet_id}
    response = requests.post(url, headers=headers, json=data)
    
    if response.status_code == 200:
        return {"success": True}

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script loads stored Twitter credentials and immediately uses them to publish content without any user-facing confirmation, consent gate, or clear notice at the point of use. In an agent/skill context, this increases the risk of unauthorized or surprising actions being performed on behalf of a user if upstream inputs are manipulated or the tool is invoked unexpectedly.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
91% confidence
Finding

This code transmits user-supplied tweet content to Twitter over the network using stored bearer-token authentication. While external transmission is expected for a posting tool, it is still security-relevant because there is no validation, approval step, or guard against misuse, so an attacker controlling invocation inputs could cause unauthorized posts or data disclosure to a third party.

Content

Scanner excerpt · scripts/post_tweet.py (reported line 61)May include surrounding context.

python
data = {"text": content}
    
    response = requests.post(url, headers=headers, json=data)
    
    if response.status_code == 201:
        return response.json()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This function reads stored Twitter credentials and posts attacker- or user-supplied content to an external social-media account without any in-code authorization, confirmation, policy checks, or scope restriction. In an agent skill context, that creates a real risk of unauthorized actions on behalf of a user, spam, impersonation, or reputational harm if another component can invoke it with arbitrary content.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/get_timeline.py (reported line 31)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/get_timeline.py (reported line 36)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/get_user_tweets.py (reported line 31)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/get_user_tweets.py (reported line 39)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/like_tweet.py (reported line 28)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/like_tweet.py (reported line 35)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/post_tweet.py (reported line 53)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/reply_tweet.py (reported line 28)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/retweet.py (reported line 29)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/retweet.py (reported line 34)May include surrounding context.

python
import requests
    
    url = "https://api.twitter.com/2/tweets"
    headers = {
        "Authorization": f"Bearer {creds['bearer_token']}",
        "Content-Type": "application/json"

Static analysis

No suspicious patterns detected.