T01 · Skill Instruction Hijacking
- Location
scripts/twitter_listen_comment.py:91- Finding
Untrusted Tweet Content Can Influence a Browser-Enabled Agent
- Content
View full analysis
Vulnerability Details
File Location:
scripts/twitter_listen_comment.py:91-124, with the second-stage browser invocation atscripts/twitter_listen_comment.py:147-178
Vulnerability Type: Indirect prompt injection through externally controlled tweet content
Risk Level: HighVulnerable Code
Tweet text received from the external API is retained without validation:
python return { 'id': tweet_id, 'text': latest.get('text', ''), 'createdAt': latest.get('createdAt', ''), 'url': f'https://x.com/{username}/status/{tweet_id}', 'userScreenName': latest.get('userScreenName', username), 'favoriteCount': latest.get('favoriteCount', 0), 'replyCount': latest.get('replyCount', 0), 'retweetCount': latest.get('retweetCount', 0), }The untrusted text is interpolated directly into the reply-generation prompt:
python f'Author: @{tweet.get("userScreenName", "")}\n' f'Tweet URL: {tweet.get("url", "")}\n' f'Tweet content: {tweet.get("text", "")}'The resulting prompt is passed to an Agent:
python res = run([ OPENCLAW_BIN, 'agent', '--to', agent_target, '--message', build_reply_prompt(tweet, style), '--json' ])The generated reply is subsequently embedded in a second prompt and sent to an Agent authorized to use the authenticated browser:
python f'Tweet URL: {tweet.get("url", "")}\n' f'Reply content: {reply_text}'python res = run([ OPENCLAW_BIN, 'agent', '--to', agent_target, '--message', build_post_prompt(tweet, reply_text), '--json', '--timeout', '180' ])Technical Analysis
Tweet text is externally controlled by any monitored X account. The implementation places that content directly into a natural-language Agent prompt without a structured trust boundary, escaping, instruction-neutral encoding, or an explicit ...[truncated 3015 chars]
- Remediation
View remediation
Remediation Suggestions
-
Replace natural-language browser delegation with deterministic automation
- Pass only a validated X status URL and a validated plain-text reply to a narrowly scoped browser routine.
- Do not ask a general-purpose Agent to interpret generated content while it has browser capabilities.
-
Create an explicit trust boundary
- Represent tweet content and generated replies as structured data fields rather than concatenating them into instructions.
- If an Agent must be used, clearly delimit untrusted fields and state that their contents must never be treated as commands.
- Use separate trusted instruction and untrusted data channels where supported.
-
Validate generated replies before browser use
- Enforce a conservative maximum character count.
- Reject URLs, markup, tool names, browser directives, role markers, encoded payloads, and command-like language.
- Reject control characters and unexpected line structures.
- Require human confirmation when validation fails or content appears instruction-like.
-
Constrain browser capabilities
- Allowlist only
https://x.com/and the exact expected status URL. - Limit the browser workflow to opening the specified tweet, entering text into the reply field, and submitting it.
- Prevent navigation to unrelated origins and deny unrelated account actions.
- Use a dedicated browser profile containing no unrelated authenticated sessions.
- Allowlist only
-
Add independent postcondition verification
- Do not rely solely on Agent-generated JSON claiming
status: submitted. - Verify that the browser remained on the expected X origin and status page.
- Confirm that the submitted reply appears under the intended tweet and matches the validated reply text.
- Do not rely solely on Agent-generated JSON claiming
-
Reduce automation risk
- Add an optional approval step before publication, particularly for newly monitored accounts.
- Rate-limit replies ...[truncated 181 chars]
-
