Back to skill

Security audit

stream-formatter

Security checks for vulnerabilities and agentic risk

Overview

The skill is a plausible stream formatter, but it stores streamed text in shared module memory without stream or user isolation, which could mix or expose conversations in a shared runtime.

Install only if the skill will run in a strictly isolated per-user or per-conversation process, or after it is changed to keep per-stream state, enforce chunk and buffer limits, expire/reset abandoned streams, and bind reset/configuration to the correct caller. The Chinese-only documentation and remote zod import should also be reviewed for fit with your environment.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
index.ts:26
Finding
Shared Global State Enables Cross-Session Data Leakage and Stream Corruption## Vulnerability Details **File Location**: `index.ts`, lines 26-34 and 54-75 **Vulnerability Type**: Shared mutable state without session isolation **Risk Level**: High ### Vulnerable Code ```typescript // Global state let buffer = ""; let lastOutput = ""; let config = { buffer_size: 10, format_markdown: true, fix_incomplete_sentences: true, remove_duplicates: true }; ``` ```typescript export default async function streamFormatter(params: StreamFormatterParams) { const validatedParams = StreamFormatterParams.parse(params); switch (validatedParams.action) { case "init": { config = { ...config, ...validatedParams.options }; buffer = ""; lastOutput = ""; return { success: true, config }; } case "process": { const { chunk, flush } = validatedParams; // Add to buffer buffer += chunk; // Duplicate removal if (config.remove_duplicates && buffer.includes(lastOutput) && lastOutput.length > 0) { buffer = buffer.replace(lastOutput, ""); } ``` ### Technical Analysis The formatter stores its buffer, previous output, and configuration in module-level mutable variables. Every invocation handled by the same runtime therefore operates on the same state. The API does not accept a stream identifier, session identifier, or caller-specific state object. Calling `init` resets the shared state and changes the shared configuration. Calling `process` appends data to the same shared `buffer`, regardless of which user or conversation supplied it. In a persistent or concurrent multi-user runtime, one conversation can consequently read, modify, flush, reset, or corrupt another conversation's pending output. Asynchronous function syntax does not provide isolation. Interleaved calls can access this state between separate requests, even though the function contains no explicit asynchronous operation a ...[truncated 1459 chars]
Remediation
## Remediation Suggestions - Remove module-global mutable stream state. - Create a separate formatter instance for each stream and keep `buffer`, `lastOutput`, and `config` in that instance. - If the hosting interface requires a single exported function, require an authenticated, unpredictable stream identifier and store state in a map keyed by both caller identity and stream identity. - Do not trust a caller-supplied stream identifier by itself; bind it to the authenticated caller to prevent enumeration or cross-user access. - Delete stream state immediately after a final flush, reset, timeout, or cancellation. - Apply expiration and capacity limits to any server-side state map. - Prevent one caller from resetting or reconfiguring another caller's stream. - Add concurrency tests that interleave `init`, `process`, `flush`, and `reset` operations from multiple simulated users and verify that no content or configuration crosses session boundaries.

T09 · Insecure Skill Coding Practices

Warning
Location
index.ts:10
Finding
Unbounded Stream Buffer Allows Memory-Exhaustion Denial of Service## Vulnerability Details **File Location**: `index.ts`, lines 10-14 and 68-109 **Vulnerability Type**: Uncontrolled memory allocation and unbounded input accumulation **Risk Level**: Medium ### Vulnerable Code ```typescript z.object({ action: z.literal("process"), chunk: z.string(), flush: z.boolean().optional().default(false), }), ``` ```typescript case "process": { const { chunk, flush } = validatedParams; // Add to buffer buffer += chunk; // Duplicate removal if (config.remove_duplicates && buffer.includes(lastOutput) && lastOutput.length > 0) { buffer = buffer.replace(lastOutput, ""); } let output = ""; // Adaptive buffer size let adaptiveBufferSize = config.buffer_size; if (buffer.includes("```") || buffer.includes("$")) { adaptiveBufferSize = 50; } else if (chunk.length > 0 && SENTENCE_ENDINGS.has(chunk[chunk.length - 1])) { adaptiveBufferSize = 5; } // Process output if the buffer is sufficiently large or flushing is forced if (buffer.length >= adaptiveBufferSize || flush) { // Repair incomplete sentences if (config.fix_incomplete_sentences && !flush) { // Find the last complete sentence let lastEndIndex = -1; for (let i = buffer.length - 1; i >= Math.max(0, buffer.length - 20); i--) { if (SENTENCE_ENDINGS.has(buffer[i])) { lastEndIndex = i + 1; break; } } if (lastEndIndex > 0) { // Emit the complete sentence output = buffer.slice(0, lastEndIndex); // Retain the incomplete portion buffer = buffer.slice(lastEndIndex); } else if (flush) { // Emit all content during a forced flush output = buffer; buffer = ""; } } else { // Emit the entire buffer when sentence repair is disabled output = buffer; buffer = " ...[truncated 2255 chars]
Remediation
## Remediation Suggestions - Add a strict maximum length to `chunk` in the validation schema, such as `z.string().max(MAX_CHUNK_SIZE)`. - Enforce a separate maximum accumulated buffer size for every stream. - Reject oversized input before concatenation to avoid temporarily allocating an oversized combined string. - When the accumulated limit is reached, safely emit a bounded segment, return a controlled error, or truncate according to a clearly documented policy. - Ensure forced segmentation makes forward progress even when no sentence-ending character is present. - Use per-caller quotas, request-size limits, rate limiting, and runtime memory limits as defense-in-depth controls. - Clear abandoned buffers after a short inactivity timeout and when streams are cancelled. - Add tests using very large single chunks and repeated chunks without punctuation, verifying that memory use remains bounded. - Correct the documentation claiming memory usage below 1 KB, because actual memory use depends on attacker-controlled buffered content.
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 (5)

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The entire skill description is written only in Chinese, including headings, usage, and parameter explanations, with no indication that users may choose another language or that the skill is intentionally limited to a Chinese-speaking context. Under the policy, a language or locale constraint should be optional or explicitly justified rather than implicitly imposed.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The skill's user-facing documentation is written entirely in Chinese, including headings and parameter explanations, with no indication that users may choose another language or that the skill is region-specific. This can violate language/locale policy requirements when a skill is presented to a broader audience without opt-in.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This file contains natural-language descriptions and comments such as the type-definition comment and skill description in Chinese, but does not indicate that the skill is intended only for Chinese-speaking users or provide any language opt-in. That can violate language/locale policy because it implicitly forces one language for maintainers or users interacting with the skill.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The docstring describing the skill is entirely in Chinese and appears to be the primary natural-language description of the skill's purpose. Because the file does not offer an alternative language or explain a justified region-specific constraint, it may impose a language choice without user opt-in.

Intent-Code Divergence

Low
Confidence
95% confidence
Finding
Inside the `if (config.fix_incomplete_sentences && !flush)` block, the nested `else if (flush)` can never execute because `flush` is already known to be false. The inline comment says forced flush will output all content, but the actual forced-flush behavior is handled by the outer `else` branch at L107-L110 instead.

Static analysis

No suspicious patterns detected.