Back to skill

Security audit

X Mobile Longshot / X 真机感长截图导出

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent X screenshot tool, but it needs review because unsafe filename handling can run local code and the browser can be pointed at non-X or internal URLs.

Install only if you trust the users and prompts that will control its command arguments. Avoid using user-supplied output filenames, run it in an isolated workspace with limited network access, and prefer a version that validates X URLs, blocks internal network destinations, and passes paths to Python as data rather than generated code.

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)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/render_x_longshot.js:126
Finding

Arbitrary Python Code Execution Through Unsafely Interpolated Output Paths

Content
View full analysis

Vulnerability Details

File Location: scripts/render_x_longshot.js, lines 126-150
Vulnerability Type: Python source-code injection
Risk Level: High

Vulnerable Code

javascript
    const py = `
from PIL import Image
raw = Image.open(r'''${rawPng}''').convert('RGB')
top = Image.open(r'''${topPng}''').convert('RGB')
out = raw.copy()
crop_h = min(${args.topFixPx}, top.size[1], out.size[1])
out.paste(top.crop((0,0,top.size[0],crop_h)), (0,0))
out.save(r'''${args.outPng}''')
print(r'''${args.outPng}''')
`;
    runPython(py);

    if (!args.noPdf) {
      const pyPdf = `
from PIL import Image
img = Image.open(r'''${args.outPng}''').convert('RGB')
img.save(r'''${args.outPdf}''', 'PDF', resolution=144.0)
print(r'''${args.outPdf}''')
`;
      runPython(pyPdf);
    }

The generated programs are executed by the following helper:

javascript
function runPython(code) {
  const r = spawnSync('python3', ['-c', code], { encoding: 'utf8' });
  if (r.status !== 0) {
    throw new Error(r.stderr || r.stdout || 'python3 failed');
  }
  return r.stdout.trim();
}

Technical Analysis

The values supplied through --out-png and --out-pdf are interpolated directly into Python source code enclosed by raw triple-quoted string literals. They are then passed to python3 -c.

Although spawnSync is invoked without a shell, this only prevents shell metacharacter expansion. It does not prevent injection into the Python program itself. A path containing a terminating ''' sequence can close the intended string literal and append arbitrary Python statements.

The PDF path is a direct exploitation point because the attacker can terminate the path passed to img.save, append a Python expression such as an os or subprocess invocation, and comment out the remaining generated source. The earlier image processing succeeds using a legitimate PNG path before the malicious PDF- ...[truncated 1591 chars]

Remediation
View remediation

Remediation Suggestions

Do not embed caller-controlled values in executable Python source.

  1. Move the Pillow operations into a fixed Python helper script and pass all paths as ordinary command-line arguments:

    javascript
    spawnSync('python3', [
      helperScript,
      '--raw', rawPng,
      '--top', topPng,
      '--output', args.outPng,
      '--top-fix-px', String(args.topFixPx)
    ]);
    

    The helper should retrieve these values through argparse or sys.argv, where they remain data rather than Python syntax.

  2. Alternatively, send a JSON object to a fixed Python program through standard input and parse it with json.load(sys.stdin).

  3. Validate and normalize output paths before use. Require a conservative filename policy and constrain resolved paths to an approved output directory.

  4. Reject null bytes, control characters, newline characters, and unexpected path components. This validation should be defense in depth rather than the primary injection mitigation.

  5. Add regression tests using filenames containing quotes, triple quotes, backslashes, newlines, Unicode characters, and Python metacharacters. Verify that these values are either safely handled as filenames or explicitly rejected and never interpreted as code.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/render_x_longshot.js:109
Finding

Server-Side Request Forgery Through Unrestricted Browser Navigation

Content
View full analysis

Vulnerability Details

File Location: scripts/render_x_longshot.js, lines 21 and 109-121
Vulnerability Type: Unrestricted URL navigation and server-side request forgery
Risk Level: Medium

Vulnerable Code

The URL is accepted without validating its scheme, hostname, destination address, or redirects:

javascript
    if (a === '--url') out.url = next();

It is then opened twice from the execution environment:

javascript
  try {
    await page.goto(args.url, { waitUntil: 'domcontentloaded', timeout: 45000 });
    await page.waitForTimeout(args.waitMs);
    await clickIgnore(page);
    await page.waitForTimeout(700);
    await hideOverlays(page);
    await page.waitForTimeout(1000);

    const rawPng = args.outPng.replace(/\.png$/i, '.raw.png');
    const topPng = args.outPng.replace(/\.png$/i, '.top.png');

    await page.screenshot({ path: rawPng, fullPage: true });

    await page.goto(args.url, { waitUntil: 'domcontentloaded', timeout: 45000 });

Technical Analysis

The skill is documented as an X/Twitter renderer, but it does not enforce that the supplied URL belongs to an approved X domain. Any destination accepted by Playwright may be passed to page.goto.

Consequently, the headless browser can initiate requests using the network access of the machine running the skill. This may include HTTP services bound to loopback interfaces, private network addresses, link-local services, administrative interfaces, or cloud metadata endpoints.

Restricting only the initial string would also be insufficient because an approved public URL could redirect to a prohibited destination. DNS resolution can similarly map a hostname to an internal address or change between validation and connection. Both browser requests and redirects therefore require enforcement.

The full-page screenshot and optional PDF provide a direct mechanism for returning rendered response content to the caller ...[truncated 1881 chars]

Remediation
View remediation

Remediation Suggestions

  1. Enforce an explicit destination allowlist consistent with the skill's documented purpose. For example, permit only HTTPS URLs on approved X hostnames such as x.com, plus any explicitly required and reviewed aliases.

  2. Reject:

    • Non-HTTPS schemes.
    • URLs containing embedded credentials.
    • Loopback, private, link-local, multicast, unspecified, reserved, and cloud-metadata addresses.
    • Hostnames outside the approved allowlist.
    • Unexpected ports.
  3. Resolve the hostname and validate every returned IPv4 and IPv6 address before navigation. Do not rely solely on textual hostname checks.

  4. Intercept browser requests with Playwright routing and apply the same policy to every navigation, redirect, subresource request, popup, worker request, and dynamically loaded resource:

    javascript
    await context.route('**/*', async route => {
      const requestUrl = new URL(route.request().url());
      if (!(await isAllowedDestination(requestUrl))) {
        return route.abort();
      }
      return route.continue();
    });
    
  5. Revalidate redirect destinations rather than trusting the original URL.

  6. Run Chromium in an isolated network namespace or container with outbound access restricted to the minimum required public destinations. Application-level validation should be combined with network-level egress controls.

  7. Add tests for loopback addresses, private IPv4 ranges, IPv6 loopback and private ranges, decimal or encoded IP representations, DNS rebinding scenarios, redirects to internal services, embedded credentials, and non-HTTPS schemes.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (2)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The markdown sets defaults to zh-CN and Asia/Shanghai, which imposes a language/locale configuration on all uses of the skill. The file does not offer a user choice or explain why this locale constraint is necessary for a region-specific purpose, so it conflicts with the language/locale policy requirement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code hard-codes locale: 'zh-CN' and timezoneId: 'Asia/Shanghai' for every run, which imposes a specific language/locale setting on users. The file does not show any opt-in, configurability, or justification that would make this locale restriction acceptable under the stated policy.

Content

No source excerpt is available for this finding.