Back to skill

Security audit

UniFuncs Deep Search

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a real deep-search API client, but it has under-disclosed background execution and optional public-sharing behavior that users should review carefully.

Install only if you are comfortable sending deep-search queries and related prompt content to UniFuncs with your API key. Avoid --push-to-share and --set-public unless you intentionally want results shared or public, and avoid custom --stream-file paths in shared directories. Be aware that report mode may continue running in the background after the visible command returns.

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
deep-search-report.py:185
Finding
Unsafe User-Specified Stream File Handling Enables Local Disclosure and Symlink-Based File Modification<![CDATA[ ## Vulnerability Details **File Location**: `deep-search-report.py`, lines 185–196 and 278–306 **Vulnerability Type**: Unsafe file creation, permissive file permissions, and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python def resolve_stream_file_path(specified_path: Optional[str]) -> Optional[str]: """Return stream file path, preferring user-specified path.""" if specified_path: abs_path = os.path.abspath(specified_path) parent_dir = os.path.dirname(abs_path) or "." if not os.path.isdir(parent_dir): raise UniFuncsDeepSearchError(f"Stream file directory does not exist: {parent_dir}") if not os.access(parent_dir, os.W_OK): raise UniFuncsDeepSearchError(f"Stream file directory is not writable: {parent_dir}") if not os.path.exists(abs_path): with open(abs_path, "w", encoding="utf-8"): pass return abs_path return create_temp_stream_file() ``` The returned path is subsequently reopened in append mode and populated with API response data: ```python temp_path = resolve_stream_file_path(stream_file_path) content_parts: list[str] = [] done = False started_at = time.monotonic() try: with urllib.request.urlopen(req, timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) as response: writer = open(temp_path, "a", encoding="utf-8") if temp_path else None try: while True: if time.monotonic() - started_at >= stream_timeout_seconds: break line = response.readline() if not line: done = True break decoded = line.decode("utf-8", errors="replace") if writer: writer.write(decoded) ``` ### Technical Analysis When `--stream-file` is supplied, the implementation validates the path and ...[truncated 2253 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create user-selected stream files atomically with mode `0600`. - Use `os.open()` with appropriate flags, such as `O_WRONLY`, `O_CREAT`, `O_EXCL`, and `O_NOFOLLOW`, where supported. - Convert the securely opened file descriptor into a Python file object with `os.fdopen()` rather than closing and reopening it by pathname. - If existing stream files must be supported, use `lstat()` to reject symbolic links and verify the opened descriptor with `os.fstat()`. - Require the parent directory to be owned by or exclusively writable by the invoking user; reject unsafe shared directories. - Apply `os.chmod(path, 0o600)` to existing approved files before writing sensitive stream content. - Keep the descriptor open for the entire streaming lifecycle to eliminate the validation-to-open race. - Prefer the existing `tempfile.mkstemp()` behavior unless the user explicitly requires a custom location. - Add tests covering symbolic links, path replacement races, unsafe directory permissions, and restrictive file modes. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
Findings (27)

Tainted flow: 'req' from os.environ.get (line 121, 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 56, 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
94% confidence
Finding
The description presents the skill as a general-purpose deep search capability for comprehensive information gathering. However, this code does not initiate searches or conduct investigations; it only polls or retrieves the result of a previously created Deep Search task via `query_task`. That is a materially narrower primary purpose than advertised. Additionally, the code depends on network access to the UniFuncs API and use of `UNIFUNCS_API_KEY`, which is inconsistent with the empty declared permissions/resources. While making an HTTP request is a supporting implementation detail, the undisclosed need for authenticated external API access is still a notable resource mismatch.

Lp1

High
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The code makes external HTTPS calls but apparently does not declare network capability in permissions metadata. In an agent ecosystem, hidden network capability is a real security concern because it enables silent transmission of user prompts and related data to third-party services without clear authorization boundaries.

Lp1

High
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The code makes external HTTPS calls but apparently does not declare network capability in permissions metadata. In an agent ecosystem, hidden network capability is a real security concern because it enables silent transmission of user prompts and related data to third-party services without clear authorization boundaries.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
parser.add_argument("--max-depth", type=int, help="Maximum research depth.")
    parser.add_argument("--domain-scope", type=str, help="Comma-separated domain allowlist.")
    parser.add_argument("--domain-blacklist", type=str, help="Comma-separated domain blocklist.")
    parser.add_argument("--output-prompt", type=str, help="Custom output prompt template.")
    parser.add_argument("--important-urls", type=str, help="Comma-separated important URLs.")
    parser.add_argument("--important-keywords", type=str, help="Comma-separated important keywords.")
    parser.add_argument("--important-prompt", type=str, help="Important prompt content.")
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
parser.add_argument("--max-depth", type=int, help="Maximum research depth.")
    parser.add_argument("--domain-scope", type=str, help="Comma-separated domain allowlist.")
    parser.add_argument("--domain-blacklist", type=str, help="Comma-separated domain blocklist.")
    parser.add_argument("--output-prompt", type=str, help="Custom output prompt template.")
    parser.add_argument("--important-urls", type=str, help="Comma-separated important URLs.")
    parser.add_argument("--important-keywords", type=str, help="Comma-separated important keywords.")
    parser.add_argument("--important-prompt", type=str, help="Important prompt content.")
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
parser.add_argument("--max-depth", type=int, help="Maximum research depth.")
    parser.add_argument("--domain-scope", type=str, help="Comma-separated domain allowlist.")
    parser.add_argument("--domain-blacklist", type=str, help="Comma-separated domain blocklist.")
    parser.add_argument("--output-prompt", type=str, help="Custom output prompt template.")
    parser.add_argument("--important-urls", type=str, help="Comma-separated important URLs.")
    parser.add_argument("--important-keywords", type=str, help="Comma-separated important keywords.")
    parser.add_argument("--important-prompt", type=str, help="Important prompt content.")
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
95% confidence
Finding
The code can spawn a new process via subprocess.Popen, but shell/process-execution capability is not declared. In agent settings, undeclared process execution materially expands the skill's power and is especially concerning here because it is used to detach ongoing activity from the immediate user interaction.

Lp1

High
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The code can spawn a new process via subprocess.Popen, but shell/process-execution capability is not declared. In agent settings, undeclared process execution materially expands the skill's power and is especially concerning here because it is used to detach ongoing activity from the immediate user interaction.

Lp1

High
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The code can spawn a new process via subprocess.Popen, but shell/process-execution capability is not declared. In agent settings, undeclared process execution materially expands the skill's power and is especially concerning here because it is used to detach ongoing activity from the immediate user interaction.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The CLI exposes options to push results to a share space and mark them public, which goes beyond a narrow 'information gathering' function. In context, search results may include sensitive user queries or collected data, so publication features materially increase disclosure risk.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The request payload can instruct the remote API to share and publicize results, enabling transmission of user-derived content beyond returning a search response. This is particularly risky because the skill's stated purpose is research, not distribution, so users may not anticipate publication of outputs.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The manifest says to use the skill for "deep search, broad investigation, or in-depth topic coverage," which are relatively broad natural-language triggers and do not define clear exclusion conditions. This may cause the skill to be invoked for ordinary information requests rather than only for genuinely expensive deep-search tasks.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The documented flags `--push-to-share` and `--set-public` introduce a publication path that can expose generated reports outside the local execution context. In a research skill, results may contain sensitive user queries, proprietary summaries, or collected data, so making sharing/publication available without strong guardrails increases data leakage risk.

External Transmission

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

CREATE_TASK_ENDPOINT = "https://api.unifuncs.com/deepsearch/v1/create_task"
DEFAULT_MODEL = "s3"
DEFAULT_REQUEST_TIMEOUT_SECONDS = 180
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/deepsearch/v1/create_task"
DEFAULT_MODEL = "s3"
DEFAULT_REQUEST_TIMEOUT_SECONDS = 180
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/deepsearch/v1/create_task"
DEFAULT_MODEL = "s3"
DEFAULT_REQUEST_TIMEOUT_SECONDS = 180
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The presence of --push-to-share and --set-public enables publishing results externally, which exceeds simple information retrieval and can expose sensitive user queries or generated content to a broader audience. In this skill context, that is more dangerous because deep-search tasks may include proprietary, personal, or confidential research topics.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The CLI exposes options that can publish data externally without an explicit warning about disclosure consequences at the point of use. This is risky because users may treat the tool as a private research utility while unintentionally making search outputs publicly accessible.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The skill starts a detached background worker to continue streaming after the apparent request lifecycle ends. This is dangerous in an agent context because it creates hidden ongoing execution, network use, and file writes that may outlive user awareness or platform supervision.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd.append("--push-to-share")
    if args.set_public:
        cmd.append("--set-public")
    subprocess.Popen(
        cmd,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
Confidence
95% confidence
Finding
The code spawns a detached background subprocess that re-executes the same script with user-influenced arguments and no user-visible confirmation. Although it does not invoke a shell and avoids classic command injection, it introduces persistence-like behavior and makes network/file activity continue after the main request flow, which is risky for an agent skill.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Background subprocess execution occurs without user-facing warning or confirmation at the moment it is started. Silent continuation of execution is risky because users may believe the operation ended while the skill keeps performing network requests and writing to disk.

Static analysis

No suspicious patterns detected.