Back to skill

Security audit

Unifuncs Deep Research

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real UniFuncs deep-research client, but its default report mode quietly starts a detached background process and can leave research output in temporary files.

Review before installing. Use it only for topics you are comfortable sending to UniFuncs, avoid confidential or regulated material unless approved, prefer non-streaming mode for sensitive work, and manually check/delete any unifuncs-deep-research stream files after use.

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

Warning
Location
deep-research-report.py:189
Finding
Completed research results persist in temporary stream files## Vulnerability Details **File Location**: `deep-research-report.py`, lines 189–197 and 424–453 **Vulnerability Type**: Sensitive data retained in temporary files **Risk Level**: Medium ### Vulnerable Code ```python def create_temp_stream_file() -> Optional[str]: """Create a writable temp file path for stream payload, if possible.""" tmp_dir = tempfile.gettempdir() if not os.access(tmp_dir, os.W_OK): return None fd, path = tempfile.mkstemp(prefix="unifuncs-deep-research-", suffix=".stream", dir=tmp_dir) os.close(fd) return path ``` ```python if args.stream: temp_path = resolve_stream_file_path(args.stream_file) if not temp_path: raise UniFuncsDeepResearchError("No writable stream file available for streaming mode.") if args.background_worker: stream_chat(payload, api_key, 24 * 60 * 60, temp_path) return start_background_worker(args, temp_path) started_at = time.monotonic() while time.monotonic() - started_at < args.timeout: if is_stream_done(temp_path): break time.sleep(0.5) output = extract_text_from_stream_file(temp_path) if not is_stream_done(temp_path): command = ( f'python3 "{os.path.abspath(__file__)}" --read-stream-file --stream-file "{temp_path}"' ) notice_lines = [ "", "", f"[Unfinished] No complete response within {args.timeout}s; returning received partial content.", "[Background] Streaming continues in the background.", f"[Stream File] {temp_path}", "[Read Later] Run this command to read received content:", command, ] output += "\n".join(notice_lines) print(output) ``` ### Technical Analysis Streaming responses are written to a file created in the system temporary directory. Although `tempfile.m ...[truncated 1812 chars]
Remediation
## Remediation Suggestions - Track whether the stream file was automatically created or explicitly supplied by the user. - Delete automatically created files after a completed response has been rendered. - Perform cleanup in a `finally` block so files are also handled on parsing failures, network errors, and interruption. - Retain an automatically created file only while an unfinished background request genuinely requires it. - Preserve user-specified `--stream-file` paths only when documented as an explicit retention request. - Apply an explicit restrictive mode such as `0o600` when opening any user-specified stream file. - Document the storage location, retention period, cleanup behavior, and sensitivity of stream files. - Consider storing only extracted report text rather than the complete raw event stream if raw chunks are unnecessary. A suitable lifecycle is: create a restricted temporary file, stream into it, render the result, and securely remove it immediately when completion is confirmed.

T09 · Insecure Skill Coding Practices

Note
Location
deep-research-report.py:275
Finding
Detached worker exposes research queries through process arguments and outlives the caller## Vulnerability Details **File Location**: `deep-research-report.py`, lines 275–319 and 424–435 **Vulnerability Type**: Sensitive command-line data exposure and unmanaged background execution **Risk Level**: Low ### Vulnerable Code ```python def start_background_worker(args: argparse.Namespace, stream_file_path: str) -> None: """Start detached background worker to keep streaming after timeout.""" cmd = [ sys.executable, os.path.abspath(__file__), "--background-worker", "--stream-file", stream_file_path, "--model", args.model, "--timeout", str(args.timeout), "--output-type", args.output_type, "--output-length", str(args.output_length), ] if args.query: cmd.append(args.query) if args.introduction: cmd.extend(["--introduction", args.introduction]) if args.plan_approval: cmd.append("--plan-approval") if args.reference_style: cmd.extend(["--reference-style", args.reference_style]) if args.max_depth is not None: cmd.extend(["--max-depth", str(args.max_depth)]) if args.domain_scope: cmd.extend(["--domain-scope", args.domain_scope]) if args.domain_blacklist: cmd.extend(["--domain-blacklist", args.domain_blacklist]) if args.output_prompt: cmd.extend(["--output-prompt", args.output_prompt]) subprocess.Popen( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True, ) ``` ```python if args.background_worker: stream_chat(payload, api_key, 24 * 60 * 60, temp_path) return start_background_worker(args, temp_path) started_at = time.monotonic() while time.monotonic() - started_at < args.timeout: if is_stream_done(temp_path): break t ...[truncated 2783 chars]
Remediation
## Remediation Suggestions - Perform streaming in the foreground by default. - Create a detached worker only after an actual timeout, or behind an explicit and documented user option. - Require clear user consent before allowing a request to continue after the invoking process exits. - Do not transmit sensitive queries through command-line arguments. Pass the request through standard input, an anonymous pipe, or a permission-restricted temporary file that is deleted immediately after the child reads it. - Construct a minimal child environment instead of inheriting all parent environment variables. - If the API key must be inherited, limit worker lifetime and ensure the process performs only the intended single request. - Add cancellation support using a PID file, process handle, or explicit cancellation command. - Terminate and reap the worker when the parent is interrupted unless the user explicitly requested continued background execution. - Surface worker errors instead of discarding all standard error output. - Document background execution, maximum lifetime, API-cost implications, stream-file retention, and cancellation procedures.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (22)

Tainted flow: 'req' from os.environ.get (line 138, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) as response:
            body = response.read().decode("utf-8")
            return json.loads(body)
    except urllib.error.HTTPError as err:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 52, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="GET",
    )
    try:
        with urllib.request.urlopen(req, timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) as response:
            body = response.read().decode("utf-8")
            return json.loads(body)
    except urllib.error.HTTPError as err:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 340, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) as response:
            body = response.read().decode("utf-8")
            try:
                return json.loads(body)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 340, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) as response:
            body = response.read().decode("utf-8")
            try:
                return json.loads(body)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description presents the skill as one that performs deep research and produces long-form reports. However, the supplied code only calls the query_task endpoint with a task_id, validates the API response, and prints the returned data. It does not create a research task, conduct analysis, synthesize findings, or generate a report. This is a materially narrower and different primary purpose than declared.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
--output-type {report,summary,wechat-article,xiaohongshu-article,toutiao-article,zhihu-article,zhihu-answer,weibo-article}
                        Desired output style (default: report).
  --output-prompt OUTPUT_PROMPT
                        Custom output prompt template.
  --output-length OUTPUT_LENGTH
                        Expected output length hint (default: 10000).
  --raw-response        Print full API response JSON.
Confidence
85% confidence
Finding
The `--output-prompt` option allows arbitrary custom prompt templates to be passed through to the external research system, creating a direct channel for prompt injection, policy bypass attempts, or exfiltration-oriented instructions if user-controlled input is inserted there. In a skill that brokers requests to a remote LLM-like API, exposing raw prompt-template control materially increases the risk of unsafe instruction forwarding.

Lp1

High
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The script performs outbound network access to api.unifuncs.com, but the network capability is not declared in permissions. In a skill ecosystem, undeclared network access reduces transparency and can enable silent data transmission of user queries and metadata to third-party services.

Lp1

High
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The script performs outbound network access to api.unifuncs.com, but the network capability is not declared in permissions. In a skill ecosystem, undeclared network access reduces transparency and can enable silent data transmission of user queries and metadata to third-party services.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
default=DEFAULT_OUTPUT_TYPE,
        help=f"Desired output style (default: {DEFAULT_OUTPUT_TYPE}).",
    )
    parser.add_argument("--output-prompt", type=str, help="Custom output prompt template.")
    parser.add_argument(
        "--output-length",
        type=int,
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
default=DEFAULT_OUTPUT_TYPE,
        help=f"Desired output style (default: {DEFAULT_OUTPUT_TYPE}).",
    )
    parser.add_argument("--output-prompt", type=str, help="Custom output prompt template.")
    parser.add_argument(
        "--output-length",
        type=int,
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp1

High
Category
MCP Least Privilege
Confidence
76% confidence
Finding
The code can spawn a detached background process, which is effectively process-execution capability, yet that capability is not reflected in declared permissions. In an agent ecosystem, undeclared process-spawning is dangerous because it can outlive the invoking session and operate with less visibility or control.

Lp1

High
Category
MCP Least Privilege
Confidence
76% confidence
Finding
The code can spawn a detached background process, which is effectively process-execution capability, yet that capability is not reflected in declared permissions. In an agent ecosystem, undeclared process-spawning is dangerous because it can outlive the invoking session and operate with less visibility or control.

Lp1

High
Category
MCP Least Privilege
Confidence
76% confidence
Finding
The code can spawn a detached background process, which is effectively process-execution capability, yet that capability is not reflected in declared permissions. In an agent ecosystem, undeclared process-spawning is dangerous because it can outlive the invoking session and operate with less visibility or control.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The activation wording is broad enough to route generic 'analysis' or 'comprehensive analysis' requests into a long-running external research workflow, potentially causing unnecessary data transmission, increased cost, and use of a more powerful tool than intended. In an agent setting, over-broad routing expands the situations where user prompts may be sent to an external API without strong necessity.

Skill Enumeration

Medium
Category
Agent Snooping
Content
## When to Use

You need deep, structured research on a topic.
You want a report-style output instead of search report, if the user has not explicitly requested a deep research, consider using [unifuncs-deep-search](https://github.com/UniFuncs/skills/blob/main/skills/unifuncs-deep-search/SKILL.md) instead.
Typical completion time is around **3-10 minutes**, depending on topic complexity.

## Usage Guidelines
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

External Transmission

Medium
Category
Data Exfiltration
Content
import urllib.request
from typing import Any, Dict, Optional

CREATE_TASK_ENDPOINT = "https://api.unifuncs.com/deepresearch/v1/create_task"
DEFAULT_MODEL = "u3"
DEFAULT_OUTPUT_TYPE = "report"
DEFAULT_OUTPUT_LENGTH = 10000
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
import urllib.request
from typing import Any, Dict, Optional

CREATE_TASK_ENDPOINT = "https://api.unifuncs.com/deepresearch/v1/create_task"
DEFAULT_MODEL = "u3"
DEFAULT_OUTPUT_TYPE = "report"
DEFAULT_OUTPUT_LENGTH = 10000
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
import urllib.request
from typing import Any, Dict, Optional

CREATE_TASK_ENDPOINT = "https://api.unifuncs.com/deepresearch/v1/create_task"
DEFAULT_MODEL = "u3"
DEFAULT_OUTPUT_TYPE = "report"
DEFAULT_OUTPUT_LENGTH = 10000
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script sends user-provided query text and optional prompt/domain parameters to an external API without any explicit user-facing notice at execution time. In a deep-research skill, users may paste proprietary or sensitive material, so undisclosed transmission to a third party creates a real privacy and compliance risk.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
User queries and optional fields such as introduction and output_prompt are sent to a third-party API, but the script provides no explicit warning or consent checkpoint about external transmission. In a research skill, users may submit proprietary or personal information, making silent off-device transfer materially risky.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd.extend(["--domain-blacklist", args.domain_blacklist])
    if args.output_prompt:
        cmd.extend(["--output-prompt", args.output_prompt])
    subprocess.Popen(
        cmd,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The streaming implementation persists potentially sensitive model output to a local file, sometimes in a temp directory, without prominently informing the user. This creates residual data exposure risk, especially on shared systems or when reports contain confidential research inputs or outputs.

Static analysis

No suspicious patterns detected.