Back to skill

Security audit

X Tag Responder

Security checks for vulnerabilities and agentic risk

Overview

The skill claims to help draft X replies, but bundled scripts can run broader background automation that likes and posts tweets and restarts an unrelated gateway without clear approval.

Install only after review or remediation. The safe version should remove the unrelated awareness daemon behavior, disable automatic likes/replies and gateway restarts, run in draft-only mode by default, disclose Gemini processing, and replace shell-string command execution with argument-array APIs. Treat the current package as requiring careful manual review before use with any real X account or OpenClaw workspace.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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 (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/reply-bot.js:148
Finding

Model-Generated Reply Enables Shell Command Injection

Content
View full analysis
&1`, { timeout: 15000 } ).toString(); return JSON.parse(result)?.data?.id; } ``` The value passed as `text` comes directly from model-generated output: ```js let replyText; try { replyText = await generateReply(effectiveTweet, context); console.log(`[reply] Generated: "${replyText}"`); } catch (e) { console.log(`[reply] Generation failed: ${e.message?.slice(0, 80)}`); continue; } if (dryRun) { console.log(`[reply] DRY RUN → tweet ${tweet.id}: "${replyText}"`); continue; } // Like the mention first — shows visibility, builds engagement likeTweet(tweet.id); // Post try { const replyId = postReply(tweet.id, replyText); ``` ### Technical Analysis `child_process.execSync()` passes the constructed command to a system shell. `JSON.stringify(text)` serializes the generated reply as a JavaScript string, but it does not safely escape the value for shell execution. In particular, command substitutions such as `$(command)` and backtick expressions remain active inside shell double quotes. Consequently, a generated reply containing shell syntax can cause local commands to execute before `xurl` receives the reply argument. The reply content originates from Gemini and is influenced by attacker-controlled tweet text and conversation context. Model output must therefore be treated as untrusted input. The combination of prompt injection, unvalidated model output, string-based shell command construction, and automatic execution creates a remote command-execution path. The tweet ID is also interpolated into the shell command without explicit numeric validation, although the primary confirmed path is through th ...[truncated 1413 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
scripts/reply-bot.js:85
Finding

Prompt Injection Can Control Automatically Published X Replies

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/reply-bot.js:239
Finding

Autonomous Likes and Replies Bypass the Documented Human-Approval Requirement

Content
View full analysis
*"Here's a draft reply. Should I post it?* > `[DRAFT TEXT]`" ``` ```md ### Step 6 — Post the Reply (with Human Approval) Once the human approves: ```bash xurl reply TWEET_ID "Your reply text here" ``` ``` The daemon nevertheless invokes the autonomous reply bot: ```js // Mentions + replies every 5 cycles (5 min) if (mentionTick % 5 === 0) { state = checkMentions(state); // Auto-reply to real mentions try { execSync(`node ${WORKSPACE}/scripts/reply-bot.js --check 2>&1`, { timeout: 30000 }); } catch(e) { console.log(`[reply] check failed: ${e.message?.slice(0,80)}`); } } ``` The reply bot performs account actions without checking approval: ```js // Like the mention first — shows visibility, builds engagement likeTweet(tweet.id); // Post try { const replyId = postReply(tweet.id, replyText); console.log(`[reply] ✅ Posted! ID: ${replyId}`); log.replies.push({ replyId, toTweetId: tweet.id, toAuthorId: tweet.author_id, replyText, originalText: tweet.text.slice(0, 100), repliedAt: new Date().toISOString(), }); saveLog(log); } catch (e) { console.log(`[reply] Post failed: ${e.message?.slice(0, 100)}`); } ``` ### Technical Analysis The Skill documentation establishes a consent boundary: replies must be drafted and shown to the human before posting. The executable behavior does not implement that boundary. Instead, daemon mode periodically invokes `reply-bot.js --check`, and the bot automatically likes mentions and publishes model-generated replies. Neither quiet hours, daily limits, spam filtering, nor per-user cooldowns represent authorization. They reg ...[truncated 1235 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/reply-bot.js:85
Finding

Tweet Content and Conversation Context Are Undisclosedly Sent to Gemini

Content
View full analysis
{ const req = https.request({ hostname: 'generativelanguage.googleapis.com', path: `/v1beta/models/gemini-2.5-flash-lite:generateContent?key=${apiKey}`, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) } }, res => { ``` ### Technical Analysis The bot transmits the complete mention text and up to 150 characters of conversation context to Google's Gemini API. `SKILL.md` does not disclose that content will be sent to Gemini and does not declare `GEMINI_API_KEY` as a prerequisite. Although many X posts are public, mentions can include personal information, protected-context content, deleted content retrieved before deletion, or information the account owner did not expect to be processed by an additional provider. The Gemini API key is included in the request URL. ...[truncated 1144 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/awareness.js:107
Finding

Tag-Response Skill Controls Unrelated Gateway and Workspace Automation

Content
View full analysis
&1', { timeout: 15000 }).toString(); // Parse WhatsApp status from the table // Looks for: │ WhatsApp │ ON │ OK │ linked ... // or: │ WhatsApp │ ON │ ERR │ ... const waMatch = result.match(/WhatsApp.*?(linked|disconnected|error|ERR|OFF|unlinked)/i); ``` ```js console.log(`[gateway] 🔄 Auto-restarting gateway after ${gw.flapCount} flaps...`); try { execSync('openclaw gateway restart 2>&1', { timeout: 30000 }); gw.lastRestartAt = now; gw.flapCount = 0; gw.disconnectTimes = []; gw.flapAlertSent = false; addAlert( 'gateway_restarted', 'Gateway auto-restarted after persistent WhatsApp flapping. Monitoring recovery.', 'high' ); ``` It also invokes unrelated programs outside the audited package: ```js function checkTweetQueue() { try { const result = execSync(`node ${WORKSPACE}/scripts/tweet-when-ready.js --check 2>&1`, { timeout: 30000 }).toString(); if (result.trim()) console.log(`[tweet] ${result.trim()}`); } catch (e) { console.log(`[tweet] queue check failed: ${e.message?.slice(0, 100)}`); } } function checkMemeQueue() { try { const result = execSync(`python3 ${WORKSPACE}/scripts/meme-maker.py --check 2>&1`, { timeout: 60000 }).toString(); if (result.trim()) console.log(`[meme] ${result.trim()}`); } catch (e) { console.log(`[meme] queue check failed: ${e.message?.slice(0, 100)}`); } } function checkFeeds() { try { const result = execSync(`/home/ubuntu/go/bin/blogwatcher scan 2>&1`, { timeout: 30000 }).toString(); ``` ### Technical Analysis The declared Skill function is to inspect X threads and prepare or post approved rep ...[truncated 1947 chars]
Remediation
View remediation

other

Warning
Location
scripts/awareness.js:21
Finding

Undeclared Background Monitoring and Persistent Storage of Social-Media Content

Content
View full analysis
{ console.log('[awareness] shutting down'); process.exit(0); }); ``` The reply bot persists tweet excerpts and generated replies: ```js log.replies.push({ replyId, toTweetId: tweet.id, toAuthorId: tweet.author_id, replyText, originalText: tweet.text.slice(0, 100), repliedAt: new Date().toISOString(), }); saveLog(log); ``` ### Technical Analysis The documented Skill is presented primarily as a request-driven X tag-response workflow. The scripts implement ongoing background polling and retain alerts, gateway state, author identifiers, tweet excerpts, generated replies, and timestamps across executions. The files are placed in the shared OpenClaw workspace rather than an isolated Skill-specific data directory. No explicit file mode is specified, no schema validation is applied when loading existing JSON, and no user-facing retention or deletion policy is documented. This finding does not establish that the scripts install their own startup service. Persistence here concerns retained data and continuous operation once daemon mode has already b ...[truncated 1184 chars]
Remediation
View remediation
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The description presents a narrowly scoped X/Twitter reply assistant focused on detecting when a human tags the AI in someone else's thread, gathering context, inferring intent, and generating a reply. This code instead implements a broad background monitoring daemon. Its dominant behavior includes polling WhatsApp gateway health, detecting flapping, and even auto-restarting the gateway—capabilities entirely absent from the declared purpose. It also checks local schedules, tweet/meme queues, and blog/feed updates, storing alerts and state on disk. For X, the code only polls mentions, filters spam, raises alerts, and periodically calls an external reply-bot script; the supplied chunk itself does not fetch thread context, perform intent inference, or compose replies. These are material differences in primary purpose and capabilities, so the description does not accurately represent the code.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The description is narrower and more specific than the code’s real behavior. The code does not specifically identify 'a human tags their AI familiar in someone else’s thread'; it processes any actionable mention containing the bot’s account, subject to spam/rate-limit filters. It also does not retrieve the full conversation context—only the conversation root tweet text when available. The claimed graceful handling of X reply-chain restrictions is not implemented in any substantive way; the script simply replies to mentions it can fetch and logs failures. Additionally, the code performs undeclared actions such as liking tweets and calling the Gemini API to generate responses. While crafting replies from mention context is aligned at a high level, the implemented capability set and primary scope differ materially from the declared purpose.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The file implements a persistent background watcher that monitors WhatsApp gateway state, X mentions, reminders, tweet/meme queues, and feeds, then takes actions automatically. That behavior is materially broader than the declared skill purpose of responding to X/Twitter thread tags, so installing this skill grants unexpected surveillance and automation capabilities that violate least privilege and increase the blast radius if the skill is misused or compromised.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

This code can automatically restart the WhatsApp gateway based on observed flapping, which is an infrastructure-control action unrelated to an X-thread responder. Autonomous service restarts can disrupt availability, mask root causes, and give a skill far more operational authority than its declared purpose suggests, making the context especially suspicious and dangerous.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script launches unrelated subprocess jobs for tweet queue processing, meme generation, feed scanning, and reply bot execution, none of which are necessary for merely detecting X tags in someone else's thread and drafting a response. This scope expansion creates hidden side effects and broad execution authority, increasing the chance of abuse, unintended posting, or execution of additional compromised scripts from the workspace.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

Broad trigger phrases like 'Weigh in on this' or 'Reply to this for me' can cause the skill to activate outside a clearly scoped X/Twitter-tag context. That increases the chance of acting on ambiguous or attacker-supplied content, potentially leading to unintended drafting or public-posting workflows in the wrong context.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The interval constant is 20 seconds, so the conditional block at every 5th cycle runs roughly every 100 seconds, not every 5 minutes. This directly contradicts the inline documentation and could mislead maintainers about how aggressively mention polling and reply actions occur.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The auto-restart path invokes a disruptive subprocess action without interactive confirmation, explicit operator approval, or clear safety controls. In the context of a skill advertised for X-tag response handling, this is especially dangerous because users would not reasonably expect it to restart external infrastructure, and automated restarts can cause outages or unsafe operational churn.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The comment above gatewayLoop states a 60-second polling cadence, but the configured interval is 20 seconds and mainLoop also invokes checkGateway on every cycle. This is an active contradiction between documentation and runtime behavior, especially relevant because it affects monitoring frequency and restart logic.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Incoming tweet text and fetched conversation context are sent to Gemini without any minimization, consent check, or disclosure mechanism. In an x-tag responder skill, third-party thread content may include personal or sensitive information, so forwarding full context to an external model increases privacy and data-handling risk.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The prompt construction directly embeds untrusted tweet text and conversation context into a third-party model request without semantic filtering. In this skill’s context, the bot intentionally pulls broader thread context, which makes accidental disclosure of unrelated participants’ sensitive content more likely and can also let adversarial content influence generated replies.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes detecting tags, fetching conversation context, inferring intent, and crafting an appropriate reply. This file also issues a separate engagement action by liking mentions before replying, which is not implied by the stated purpose of context-aware reply generation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
70% confidence
Finding

The drafting instructions prescribe stylistic conventions for replies but do not offer any language or locale choice to the user. If used with multilingual users or threads, this can impose an implicit language/style policy without opt-in.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The top-level documentation says the system 'Monitors mentions, filters spam, generates replies via Gemini, posts via xurl,' which omits the implemented behavior of liking tweets. Later code explicitly calls a like operation before posting, so the documentation understates the bot's side effects.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/awareness.js:111

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/reply-bot.js:136