Back to skill

Security audit

Unexpand Tool

Security checks for vulnerabilities and agentic risk

Overview

This small text-conversion skill is mostly coherent, but its bundled script can accidentally treat piped input as a local file path and print that file's contents.

Review before installing. Use only with explicit file paths in a trusted local context, and fix stdin handling before exposing it to automation, services, or untrusted input.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/unexpand.py:3
Finding

Arbitrary Local File Read Through Standard Input

Content
View full analysis

Vulnerability Details

File Location: scripts/unexpand.py, line 3
Vulnerability Type: Attacker-controlled file path / arbitrary local file read
Risk Level: Medium

Vulnerable Code

python
print(open(sys.argv[1] if len(sys.argv)>1 else sys.stdin.read()).read().replace('        ', '\t'))

Technical Analysis

When no command-line filename is supplied, the expression sys.stdin.read() is used directly as the argument to open(). Consequently, standard input is interpreted as a local filesystem path rather than as text to be converted.

This behavior contradicts the documented pipeline usage, cat spaces.txt | unexpand-tool, and creates a file-disclosure primitive wherever an attacker can control standard input and observe the program's output. The script performs no path validation or restriction before opening the selected file.

Attack Path

  1. A service or automation process invokes scripts/unexpand.py without a filename argument.
  2. An attacker controls the data supplied to the process through standard input.
  3. The attacker submits the path of a sensitive local file, such as an application configuration file.
  4. The script passes that path to open() and reads the file with the privileges of its current process.
  5. The file content, with sequences of eight spaces replaced by tabs, is printed to standard output.
  6. If the output is returned to the attacker, logged in an accessible location, or otherwise exposed, sensitive information is disclosed.

Impact Assessment

An attacker can read any local file for which the executing account has read permission. The vulnerability does not independently grant higher operating-system privileges or permit file modification, but its scope inherits the permissions of the invoking process. If the tool runs under a privileged service account, exposed data may include configuration files, credentials, tokens, source code, or other secrets available to that account.

Exploitation req ...[truncated 146 chars]

Remediation
View remediation

Remediation Suggestions

Handle file arguments and standard input as distinct input sources so that piped content is never treated as a path:

python
#!/usr/bin/env python3
import sys

if len(sys.argv) > 1:
    with open(sys.argv[1], encoding="utf-8") as source:
        content = source.read()
else:
    content = sys.stdin.read()

print(content.replace("        ", "\t"), end="")

Additionally:

  • Use argparse to validate supported options and reject unexpected arguments.
  • If this utility is exposed by a service, restrict file access to an explicitly permitted directory and resolve paths before enforcing that boundary.
  • Run the tool with the minimum filesystem permissions required.
  • Add tests confirming that piped text is transformed directly and cannot select a file.
  • Reconcile the implementation with SKILL.md: the documented -t, -a, and --first-only options are currently not implemented, and the conversion presently replaces exactly eight spaces globally.
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • 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
Findings (1)

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

When no filename argument is provided, the code reads all stdin content and passes that content to open() as if it were a path. This lets piped text be interpreted as a filesystem path, causing unintended file reads, denial of service via crashes on invalid paths, and behavior that contradicts the tool's stated purpose of transforming input text.

Content

No source excerpt is available for this finding.