T09 · Insecure Skill Coding Practices
- Location
scripts/queue_processor.py:101- Finding
Shell Command Injection Through Queue-Controlled iMessage Chat ID
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill has a coherent local image-generation purpose, but its queued background delivery and shell fallback create real privacy and command-execution risks.
Install only if you are comfortable with sensitive images being sent to the configured bridge and potentially queued on disk. Use a trusted local or authenticated bridge, avoid plaintext non-loopback HTTP for private or face images, disable or tightly control the queue daemon and iMessage delivery, verify the iMessage recipient, and do not run the queue processor until the shell fallback and queue path validation are fixed.
scripts/queue_processor.py:101Shell Command Injection Through Queue-Controlled iMessage Chat ID
scripts/queue_processor.py:171Queue Processor Permits Arbitrary File Reads, Network Uploads, and File Overwrites
scripts/comfyui_generate.py:107Sensitive Biometric Images Are Transmitted Over Plaintext HTTP and Retained Indefinitely
SKILL.md:32Installation and Runtime Dependencies Are Retrieved From Mutable, Incompletely Pinned Sources
The bridge URL is taken directly from an environment variable and used for network requests without validation. In this skill's context, that means whoever controls the environment can redirect all prompts, uploaded images, and downloaded results to an unintended host, creating SSRF-like behavior and data exfiltration risk, especially because the tool handles local images and face data.
# Health check first
try:
r = httpx.get(f"{BRIDGE_URL}/health", timeout=5)
r.raise_for_status()
health = r.json()
print(f"Bridge online: {BRIDGE_URL}")
The script downloads image content from a URL built from the same unvalidated bridge base URL and writes it directly to the requested output path. A malicious bridge destination could return arbitrary content, enabling silent poisoning of outputs and untrusted file writes under the guise of generated media.
download_url = f"{BRIDGE_URL}/download/{prompt_id}"
print(f"Downloading result directly from bridge...")
try:
img_resp = httpx.get(download_url, timeout=30)
img_resp.raise_for_status()
output_path.write_bytes(img_resp.content)
downloaded = True
Retry download logic again fetches untrusted content from the environment-derived bridge and writes it to disk. This compounds the risk by repeating trust in a potentially attacker-controlled destination and can overwrite the output with arbitrary data.
retry_downloaded = False
if retry_prompt_id:
try:
r2 = httpx.get(f"{BRIDGE_URL}/download/{retry_prompt_id}", timeout=30)
r2.raise_for_status()
output_path.write_bytes(r2.content)
retry_downloaded = True
This POST sends user prompts and generation parameters to a host derived from an untrusted environment variable. In the context of an image-generation bridge, redirection to a malicious endpoint can capture sensitive prompts, metadata, and influence returned content while appearing legitimate to the caller.
print(f"Generating image ({mode_str}, model={args.model})...")
print(f"Prompt: {args.prompt}")
try:
r = httpx.post(
f"{BRIDGE_URL}/generate",
json=payload,
timeout=120,
This endpoint uploads a user-supplied local image to the bridge URL from the environment, so a maliciously set destination can exfiltrate local image contents. Because the skill explicitly operates on personal photos and edits, the privacy impact is significant.
"seed": str(args.seed),
"enhance": str(not args.no_enhance).lower(),
}
r = httpx.post(
f"{BRIDGE_URL}/img2img",
files=files,
data=data,
Faceswap pipeline mode sends source and target face images to a destination controlled by COMFYUI_BRIDGE_URL. Since this mode handles especially sensitive biometric-style face data, an attacker controlling the environment can cause highly sensitive image exfiltration.
"restore_visibility": str(args.restore_visibility),
"enhanced": str(args.enhanced).lower(),
}
r = httpx.post(
f"{BRIDGE_URL}/faceswap/pipeline",
files=files,
data=data,
This faceswap request uploads face images to an environment-controlled server without host validation. In context, the sensitivity is elevated because the transmitted data includes identifiable facial imagery, making unintended disclosure more serious than generic prompt leakage.
"restore_visibility": str(args.restore_visibility),
"enhanced": str(args.enhanced).lower(),
}
r = httpx.post(
f"{BRIDGE_URL}/faceswap",
files=files,
data=data,
LivePortrait mode uploads portrait images to a host chosen via environment variable, enabling redirection of sensitive personal images to an attacker-controlled service. The skill's LAN/self-hosted positioning makes such hidden redirection more dangerous because users may assume data never leaves the local machine.
"rotate_yaw": str(args.yaw),
"rotate_roll": str(args.roll),
}
r = httpx.post(
f"{BRIDGE_URL}/liveportrait",
files=files,
data=data,
Style-transfer mode uploads a reference image to the bridge selected by COMFYUI_BRIDGE_URL. If an attacker can influence that environment variable, the script becomes a convenient exfiltration channel for local image files while the user expects a self-hosted local workflow.
"style_weight": str(args.style_weight),
"enhance": str(not args.no_enhance).lower(),
}
r = httpx.post(
f"{BRIDGE_URL}/style-transfer",
files=files,
data=data,
Restyle mode uploads both a reference and input image to the environment-derived bridge URL, doubling the amount of potentially sensitive user media exposed. This is a real trust-boundary issue because the destination is configurable outside the script and not constrained to the advertised LAN use case.
"style_weight": str(args.style_weight),
"strength": str(args.strength),
}
r = httpx.post(
f"{BRIDGE_URL}/restyle",
files=files,
data=data,
Targeted faceswap sends source and target images, plus face selection metadata, to an unvalidated bridge URL. Given the biometric and personal nature of the data, environment-based redirection can have serious privacy and misuse consequences.
"codeformer_weight": str(args.codeformer_weight),
"restore_visibility": str(args.restore_visibility),
}
r = httpx.post(
f"{BRIDGE_URL}/faceswap/targeted",
files=files,
data=data,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
try:
import urllib.request
req = urllib.request.Request(f"{BRIDGE_URL}/health")
resp = urllib.request.urlopen(req, timeout=5)
data = json.loads(resp.read())
return data.get("status") == "ok" and data.get("comfyui") == "reachable"
except Exception:
The manifest presents the skill as a LAN/self-hosted image tool, but the documentation reveals additional behavior: persistent offline queuing, background processing, and automatic delivery via iMessage. This is a material expansion of functionality into messaging and durable data handling, which can surprise users and operators, enable unintended exfiltration of generated or source images, and bypass expectations about when and where content is transmitted.
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
import httpx as _httpx
if isinstance(e, _httpx.TimeoutException):
print(f"Error: {operation} timed out. ComfyUI may be overloaded or processing a large job.", file=sys.stderr)
print(f"Check queue: curl {BRIDGE_URL}/diagnostics | python -m json.tool", file=sys.stderr)
elif isinstance(e, _httpx.HTTPStatusError):
status = e.response.status_code
try:
The code invokes an iMessage CLI and deliberately sets HOME so it can access the user's Messages database/context when run as a LaunchAgent. That grants the skill messaging capability unrelated to the core LAN bridge description, increasing the blast radius to privacy loss, unauthorized message sending, and covert exfiltration of generated content.
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
return False
# Pass HOME explicitly so imsg finds ~/Library/Messages/chat.db
# even when running as a LaunchAgent with restricted environment
send_env = {**os.environ, "HOME": str(Path.home())}
r = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=send_env)
if r.returncode == 0:
log(f"Delivered to chat {chat_id}")
Using shell=True with queue-influenced parameters creates a clear tool-parameter abuse path. Because queued request fields can control command construction and one field is concatenated without safe quoting, an attacker able to inject a queue item could execute arbitrary shell commands under the user's account.
shell_cmd += " --text " + subprocess.list2cmdline([text])
if image_path and Path(image_path).exists():
shell_cmd += " --file " + subprocess.list2cmdline([str(image_path)])
r2 = subprocess.run(shell_cmd, shell=True, capture_output=True, text=True, timeout=30, env=send_env)
if r2.returncode == 0:
log(f"Delivered to chat {chat_id} (shell fallback)")
return True
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
cmd.extend(["-f", output_file])
# Ensure uv and homebrew binaries are in PATH (launchd has minimal PATH)
proc_env = {**os.environ, "COMFYUI_BRIDGE_URL": BRIDGE_URL}
extra_path = str(Path.home() / ".local" / "bin") + ":/opt/homebrew/bin:/usr/local/bin"
proc_env["PATH"] = extra_path + ":" + proc_env.get("PATH", "/usr/bin:/bin")
The skill documentation describes capabilities that require shell execution, network access, environment-variable use, and local file reads/writes, but it does not declare any explicit tool scope or permission boundaries. This creates an overbroad, implicit trust model where the agent may invoke sensitive capabilities without transparent user/admin review, increasing the risk of misuse or prompt-triggered abuse.
The skill supports faceswap and automatic delivery behaviors without prominent consent, privacy, or outbound-sharing warnings. Because faceswap involves biometric/identity-sensitive imagery and the queue can transmit outputs automatically, users may unknowingly process or distribute manipulated personal images without adequate notice or consent controls.
The skill claims LAN-only, self-hosted image operations, yet its offline queue automatically delivers results later via iMessage. That contradicts the stated trust boundary and can expose user images or transformed content to a different channel than expected, especially if users assume processing remains strictly local and manually controlled.
The routing guide uses broad natural-language triggers such as 'generate an image of' or 'make this look like,' without clear activation boundaries or disambiguation rules. In an agent environment, this can cause over-triggering of powerful file/network/shell-backed actions, potentially processing images, modifying files, or contacting the bridge when the user did not intend to invoke this skill.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
if image_path.suffix.lower() in (".heic", ".heif"):
jpg_path = image_path.with_suffix(".jpg")
try:
result = _sp.run(
["sips", "-s", "format", "jpeg", str(image_path), "--out", str(jpg_path)],
capture_output=True, timeout=15
)
The script sends generated faceswap outputs to a separate Ollama vision service for QA, which is an additional data sink beyond the documented ComfyUI bridge function. Even though it is hardcoded to localhost, this expands the trust boundary and can expose sensitive face imagery to another local service without clear consent or necessity.
The QA check transmits image data to a separate local HTTP service without a clear user warning. In a self-hosted image-processing skill, undisclosed forwarding of face images to another process or model is a meaningful privacy concern even if it stays on localhost.
No suspicious patterns detected.