Back to skill

Security audit

Neomano X (Tweet Publisher + Image)

Security checks for vulnerabilities and agentic risk

Overview

This skill is not clearly malicious, but it should go to Review because it can publish to X and handle reusable tokens without enforcing its promised approval or protecting secrets.

Review before installing. Use only with a dedicated low-privilege X app/account, avoid running the OAuth helper in logged terminals, rotate any tokens already printed, and do not rely on the documented approval word as a technical safeguard because direct publish invocation can still post immediately.

Vulnerability Patterns
  • 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
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/x_post.py:65
Finding

Mandatory Human Approval Is Not Enforced by the Publishing Code

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:8-11, scripts/run.py:22-24, scripts/x_post.py:65-94
Vulnerability Type: Missing authorization and approval enforcement
Risk Level: High

The Skill documentation requires the user to respond with the exact uppercase word PUBLICAR or PUBLISH before content is posted. This safety requirement exists only as a natural-language instruction and is not enforced by the executable publishing path.

markdown
## Safety / approval rule (mandatory)

- Never publish unless the user explicitly replies with **PUBLICAR** or **PUBLISH** (uppercase).
- Before publishing, always show the final text + the image you will use.

scripts/run.py forwards all supplied arguments directly to the publishing script without validating approval:

python
target = base_dir / "scripts" / "x_post.py"
cmd = [str(venv_py), str(target), *sys.argv[1:]]
print("+", " ".join(cmd))
return subprocess.call(cmd)

scripts/x_post.py accepts publish as an ordinary command and performs the network operations immediately:

python
def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("command", choices=["dry-run", "publish"])
    ap.add_argument("--text", required=True)
    ap.add_argument("--image", required=False)
    args = ap.parse_args()

    text = args.text.strip()
    image_path = Path(args.image).expanduser() if args.image else None
    if image_path and not image_path.exists():
        raise SystemExit(f"ERROR: image not found: {image_path}")

    if args.command == "dry-run":
        out = {
            "ok": True,
            "mode": "dry-run",
            "text": text,
            "image": str(image_path) if image_path else None,
            "note": "Not published. Use 'publish' to post.",
        }
        print(json.dumps(out, ensure_ascii=False, indent=2))
        return 0

    # publish
    oauth = build_oauth1()

...[truncated 1883 chars]
Remediation
View remediation

Remediation Suggestions

  • Enforce approval inside x_post.py immediately before media upload and post creation.
  • Generate a short-lived, single-use approval artifact only after displaying the final text and image to the user.
  • Bind the artifact cryptographically to:
    • The exact post text.
    • A cryptographic hash of the image.
    • The destination account.
    • An expiration time and unique nonce.
  • Reject missing, expired, reused, or content-mismatched approval artifacts.
  • Keep dry-run as the default operation and require a distinct protected authorization path for publication.
  • If an interactive confirmation is used, display the final content again and require exact confirmation. Do not rely on a command-line flag alone as proof of user approval.
  • Add automated tests demonstrating that direct publish invocation fails without valid, content-bound approval.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/oauth1_flow.py:59
Finding

OAuth Tokens, Secrets, and Verifiers Are Exposed in Plaintext

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:52-56, scripts/oauth1_flow.py:59-60, scripts/oauth1_flow.py:91-92, scripts/oauth1_flow.py:113-115
Vulnerability Type: Plaintext credential exposure through standard output and process arguments
Risk Level: High

The documented OAuth completion command places sensitive values directly in command-line arguments:

bash
{baseDir}/.venv/bin/python {baseDir}/scripts/oauth1_flow.py auth-finish \
  --oauth-token "..." \
  --oauth-token-secret "..." \
  --oauth-verifier "..."

The OAuth start flow prints the temporary request-token secret to standard output:

python
print("AUTH_URL=" + auth_url)
print("OAUTH_TOKEN=" + oauth_token)
print("OAUTH_TOKEN_SECRET=" + oauth_token_secret)
print("\nNext: open AUTH_URL, approve, then copy oauth_verifier from the redirect URL.")

The OAuth finish flow prints the resulting long-lived access credentials in plaintext:

python
print("X_ACCESS_TOKEN=" + access_token)
print("X_ACCESS_TOKEN_SECRET=" + access_token_secret)
if screen_name:
    print("SCREEN_NAME=" + screen_name)

Sensitive values are explicitly accepted through command-line options:

python
p2 = sub.add_parser("auth-finish")
p2.add_argument("--oauth-token", required=True)
p2.add_argument("--oauth-token-secret", required=True)
p2.add_argument("--oauth-verifier", required=True)

Technical Analysis

Command-line arguments may be retained in shell history and can be exposed through process inspection, command auditing, terminal recording, automation logs, or agent transcripts. Standard output is also frequently persisted by terminals, gateways, CI systems, orchestration platforms, or conversational execution environments.

The request-token secret and verifier are sensitive during the OAuth exchange. More importantly, the resulting X_ACCESS_TOKEN and X_ACCESS_TOKEN_SECRET are reusable credentials. Printing th ...[truncated 1488 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not pass OAuth secrets or verifiers through command-line arguments.
  • Read sensitive values using hidden interactive input, protected environment variables, or a permission-restricted temporary credential file.
  • Avoid printing access tokens, access-token secrets, request-token secrets, and verifiers to standard output.
  • Store resulting credentials directly in an approved secret manager or a local file created with restrictive permissions such as 0600.
  • Display only a success message and non-sensitive account identifier after authorization.
  • Ensure debug logs, exception messages, and HTTP error handling redact OAuth parameters and authorization headers.
  • Document token revocation and rotation procedures for users who may already have executed the plaintext workflow.
  • If a callback listener is introduced, validate the returned OAuth token against the originally issued token and keep transient secrets only in memory.

T08 · Insecure Dependencies

Warning
Location
scripts/bootstrap_venv.py:28
Finding

Unpinned Dependencies Are Installed from the Ambient Package Index

Content
View full analysis

Vulnerability Details

File Location: scripts/bootstrap_venv.py:28-29
Vulnerability Type: Mutable and unverified third-party dependency installation
Risk Level: Medium

The bootstrap script upgrades pip and installs dependencies without fixed versions or hashes:

python
run([py, "-m", "pip", "install", "--upgrade", "pip"])
run([py, "-m", "pip", "install", "requests", "requests_oauthlib"])

Technical Analysis

The installed code is not fully determined by the reviewed project. Every bootstrap execution resolves the latest compatible releases of pip, requests, requests_oauthlib, and their transitive dependencies from the package index configured in the user's environment.

This weakens reproducibility and allows effective runtime behavior to change after the Skill is audited. An overridden package index, compromised package release, or compromised transitive dependency could introduce code that executes during installation or import. Because these libraries operate in the credential-bearing process, compromised dependency code could access X API secrets and posted content.

No evidence was found that the named packages are typosquatted or intentionally malicious. The finding concerns the unsafe, mutable installation process.

Attack Path

  1. A user runs scripts/bootstrap_venv.py.
  2. The script unconditionally upgrades pip using the ambient package-index configuration.
  3. It resolves unpinned current versions of requests, requests_oauthlib, and transitive dependencies.
  4. A compromised release or attacker-controlled configured index supplies malicious package code.
  5. The package is installed into the Skill's virtual environment.
  6. When OAuth or publishing runs, the compromised package is imported in a process containing X credentials.
  7. Malicious dependency code can read those credentials, alter requests, or transmit sensitive content using the process's network and filesystem ...[truncated 428 chars]
Remediation
View remediation

Remediation Suggestions

  • Create and commit a reviewed dependency lock file containing exact versions.
  • Include cryptographic hashes and install with:
    bash
    pip install --require-hashes -r requirements.txt
    
  • Pin all transitive dependencies, not only direct dependencies.
  • Remove the unconditional runtime upgrade of pip; instead, document and validate a known-compatible installer version.
  • Use an explicitly configured trusted package index rather than silently inheriting arbitrary index settings.
  • Add automated dependency vulnerability scanning and a controlled update process.
  • Re-review and regenerate hashes whenever dependencies are intentionally upgraded.
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This code chunk’s primary purpose is environment preparation, not social media content creation or publishing. While dependency installation could be a supporting implementation detail in a larger skill, this specific chunk does not itself implement any of the declared behavior: it does not interact with the X API, process tweet/post content, handle images, or enforce a human approval step before publishing. Because the declared purpose is specifically about drafting/revising/publishing X posts and the supplied code is only bootstrap/setup logic, the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill drafts, revises, and publishes X posts with an image and requires a human approval step before posting. The supplied code does none of that. It is purely a credential acquisition utility for X's OAuth 1.0a flow, used to obtain access tokens and secrets from the X API. There is no code for drafting content, attaching images, creating tweets/posts, or checking for a “PUBLICAR”/“PUBLISH” confirmation step. This is a materially different primary purpose and includes an undeclared capability related to authentication/token retrieval, so it should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

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 · SKILL.md (reported line 33)May include surrounding context.

md
Note: the `.venv/` directory is intentionally *not* shipped in the skill package; each user creates it locally.

### 2) If you do NOT have Access Token Secret (UI changed)

Use the OAuth 1.0a 3‑legged flow to obtain `X_ACCESS_TOKEN` + `X_ACCESS_TOKEN_SECRET`.

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The script's documented purpose is to obtain access tokens and secrets, which is a credential-access capability. Within a skill meant only to draft/revise/publish posts after approval, adding credential acquisition materially increases danger because the skill package now facilitates reusable account access rather than a narrowly bounded posting action.

Content

Scanner excerpt · scripts/oauth1_flow.py (reported line 10)May include surrounding context.

python
Flow:
  1) auth-start  -> prints authorize URL
  2) user visits URL, logs in, is redirected to callback with oauth_verifier
  3) auth-finish -> exchanges oauth_verifier for access token + secret

Env required:
  X_API_KEY, X_API_SECRET

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code obtains request tokens and access tokens and then exposes them to the operator via stdout. In the context of a posting skill, credential harvesting/handling is more dangerous because it grants reusable account access beyond a single approved post, enabling account takeover or unauthorized posting if logs, terminals, or transcripts are exposed.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The access token and access token secret are long-lived sensitive credentials, and the script prints them directly to stdout. There is no confirmation prompt or visible warning explaining the sensitivity of this output or the risk of terminal/log exposure.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill declares capabilities that require shell, network, and environment access but does not constrain them with an explicit tool scope such as permissions or allowed-tools. In an agent setting, this increases the attack surface because the skill can invoke bootstrap, OAuth, and publish-related commands without a narrowly declared boundary, making accidental or unauthorized execution more likely.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/bootstrap_venv.py (reported line 16)May include surrounding context.

python
def run(cmd):
    print("+", " ".join(cmd))
    subprocess.check_call(cmd)


def main():

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill is described as drafting and publishing X posts with a human approval step, but this file adds a separate OAuth credential-acquisition workflow. Expanding scope from posting to token procurement increases privilege and attack surface, especially in an agent ecosystem where users may not expect the skill to handle long-lived account credentials.

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/oauth1_flow.py (reported line 44)May include surrounding context.

python
api_secret = require_env("X_API_SECRET")
    callback = os.environ.get("X_OAUTH_CALLBACK", "https://example.com/callback")

    url = "https://api.twitter.com/oauth/request_token"
    oauth = OAuth1(api_key, client_secret=api_secret, callback_uri=callback)

    r = requests.post(url, auth=oauth, timeout=30)

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/oauth1_flow.py (reported line 57)May include surrounding context.

python
api_secret = require_env("X_API_SECRET")
    callback = os.environ.get("X_OAUTH_CALLBACK", "https://example.com/callback")

    url = "https://api.twitter.com/oauth/request_token"
    oauth = OAuth1(api_key, client_secret=api_secret, callback_uri=callback)

    r = requests.post(url, auth=oauth, timeout=30)

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/oauth1_flow.py (reported line 73)May include surrounding context.

python
api_secret = require_env("X_API_SECRET")
    callback = os.environ.get("X_OAUTH_CALLBACK", "https://example.com/callback")

    url = "https://api.twitter.com/oauth/request_token"
    oauth = OAuth1(api_key, client_secret=api_secret, callback_uri=callback)

    r = requests.post(url, auth=oauth, timeout=30)

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/x_post.py (reported line 56)May include surrounding context.

python
api_secret = require_env("X_API_SECRET")
    callback = os.environ.get("X_OAUTH_CALLBACK", "https://example.com/callback")

    url = "https://api.twitter.com/oauth/request_token"
    oauth = OAuth1(api_key, client_secret=api_secret, callback_uri=callback)

    r = requests.post(url, auth=oauth, timeout=30)

Tainted flow: 'oauth' from requests.post (line 74, network input) → requests.post (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/oauth1_flow.py (reported line 47)May include surrounding context.

python
url = "https://api.twitter.com/oauth/request_token"
    oauth = OAuth1(api_key, client_secret=api_secret, callback_uri=callback)

    r = requests.post(url, auth=oauth, timeout=30)
    if r.status_code >= 300:
        raise SystemExit(f"ERROR: request_token failed: {r.status_code} {r.text}")

Tainted flow: 'oauth' from requests.post (line 74, network input) → requests.post (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/oauth1_flow.py (reported line 81)May include surrounding context.

python
url = "https://api.twitter.com/oauth/request_token"
    oauth = OAuth1(api_key, client_secret=api_secret, callback_uri=callback)

    r = requests.post(url, auth=oauth, timeout=30)
    if r.status_code >= 300:
        raise SystemExit(f"ERROR: request_token failed: {r.status_code} {r.text}")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code emits OAuth request-token material directly to standard output, including the token secret. Although the module docstring explains the flow, it does not warn users that sensitive credentials will be printed to the terminal and may be captured in shell history, logs, or CI output.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/run.py (reported line 25)May include surrounding context.

python
target = base_dir / "scripts" / "x_post.py"
    cmd = [str(venv_py), str(target), *sys.argv[1:]]
    print("+", " ".join(cmd))
    return subprocess.call(cmd)


if __name__ == "__main__":

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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/x_post.py (reported line 60)May include surrounding context.

python
payload = {"text": text}
    if media_id:
        payload["media"] = {"media_ids": [media_id]}
    r = requests.post(url, auth=oauth, json=payload, timeout=120)
    if r.status_code >= 300:
        raise SystemExit(f"ERROR: tweet create failed: {r.status_code} {r.text}")
    return r.json()

Static analysis

No suspicious patterns detected.