Back to skill

Security audit

Portfolio Tool-基金组合研究工具

Security checks for vulnerabilities and agentic risk

Overview

This portfolio skill is not clearly malicious, but it uses a persistent device-derived identifier as an account credential and can perform portfolio/account write operations, so users should review it carefully before installing.

Install only if you are comfortable with the service deriving a long-lived account identifier from your device, storing it locally, and sending it to the portfolio backend for reads and writes. Avoid putting real passwords in the documented --data command-line form, review the configured PORTFOLIO_API before using the fallback CLI, and treat ~/.portfolio/aid and any MCP header containing X-Anonymous-Id like a credential.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/pf.py:31
Finding

Persistent Device Fingerprinting Used as an Authorization Credential

Content
View full analysis
str: """基于本机特征生成稳定匿名 ID(同一台机器恒定)。优先级同后端 mcp_server。""" parts = [] for p in ("/etc/machine-id", "/var/lib/dbus/machine-id", "/etc/hostid"): try: with open(p, "r", encoding="utf-8", errors="ignore") as f: val = f.read().strip() if val: parts.append(f"mid={val}") break except Exception: pass if not parts: try: import winreg with winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Cryptography", ) as k: parts.append("wg=" + winreg.QueryValueEx(k, "MachineGuid")[0]) except Exception: pass if not parts: import getpass import platform import socket import uuid as _uuid node = _uuid.getnode() mac = ":".join(f"{(node >> (8 * i)) & 0xff:02x}" for i in reversed(range(6))) parts.append( f"fb={socket.gethostname()}|{mac}|{getpass.getuser()}|{platform.system()}" ) raw = "|".join(parts) h = hashlib.sha256(raw.encode("utf-8")).hexdigest() return f"{h[0:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}" ``` ```python if AID: headers["X-Anonymous-Id"] = AID ``` ### Technical Analysis The script reads stable operating-system identifiers from `/etc/machine-id`, `/var/lib/dbus/machine-id`, `/etc/hostid`, or the Windows `MachineGuid`. If those values are unavailable, it collects the hostname, MAC address, local username, and operating-system name. This behavior constitutes local environment reconnaissance. Although the collected source string is hashed before t ...[truncated 1868 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pf.py:66
Finding

Bearer-Like Identity Credential Stored Without Explicit Permission Hardening

Content
View full analysis
str: """ 取得匿名用户 ID:PORTFOLIO_AID → ~/.portfolio/aid → 首次按本机特征生成并持久化。 保证兜底 CLI 在不同会话/重启后复用同一用户 ID。 """ aid = (os.environ.get("PORTFOLIO_AID") or "").strip() if aid: return aid aid_file = os.path.expanduser("~/.portfolio/aid") try: with open(aid_file, encoding="utf-8") as f: aid = f.read().strip() if aid: return aid except Exception: pass aid = _machine_fingerprint() try: os.makedirs(os.path.dirname(aid_file), exist_ok=True) with open(aid_file, "w", encoding="utf-8") as f: f.write(aid + "\n") except Exception: pass return aid ``` ### Technical Analysis The Skill documentation describes `X-Anonymous-Id` as the credential used for private portfolio access and write operations. The script stores that value in `~/.portfolio/aid` using ordinary directory and file creation, without explicitly enforcing directory mode `0700` or file mode `0600`. Actual accessibility depends on the operating system and process umask. A secure umask may make the resulting file private, but the implementation does not guarantee that property. The code also does not protect against pre-existing symbolic links or use atomic, exclusive creation. Broad exception suppression can hide failures and leave the user unaware of insecure or unexpected storage behavior. `SKILL.md` additionally instructs users to place the same value in an MCP configuration header. This creates another plaintext copy of a credential whose permissions are not validated by the Skill. ### Attack Path 1. The CLI creates `~/.portfolio/aid` under a permissive umask, or an attacker arranges a malicious pre-existing path or symbolic lin ...[truncated 950 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:155
Finding

Password Exposure Through Command-Line Arguments

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/pf.py (reported line 138)May include surrounding context.

python
req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=60) as resp:
            _print(resp.read().decode("utf-8", "replace"))
    except urllib.error.HTTPError as e:
        print(f"HTTP {e.code}")

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The manifest describes a narrow MCP skill for portfolio operations, but the content authorizes broader behavior: arbitrary HTTP METHOD/PATH use via pf.py plus host fingerprint collection and local persistence. This mismatch prevents users and policy systems from accurately understanding what the skill can do, making sensitive local access and generalized network activity harder to scrutinize.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill exposes capabilities that can read environment/local files, write persistent state, and access the network, but it declares no explicit tool scope or permission boundaries. In a shared agent environment this increases the chance of over-broad execution, especially because the documented workflow includes file reads/writes and remote requests to production endpoints.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill instructs the agent to derive a stable machine fingerprint from host-unique identifiers and store it persistently as an account identity. This exceeds the core portfolio-management purpose and creates a durable cross-session identifier tied to the device, raising privacy and tracking risks if leaked, reused, or sent to third-party services.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill describes generating and persisting a machine-derived identifier without any user-facing privacy notice, consent step, or explanation of retention and reset options. Users may unknowingly permit creation of a persistent device-linked identifier, which is sensitive because it can support tracking and account linkage over time.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The instructions direct access to sensitive local system files and the Windows registry to construct identity. Accessing machine-id files, registry keys, MAC address, username, and hostname expands the skill into host reconnaissance territory and exposes system attributes unrelated to normal portfolio queries.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill requires sending a persistent identity header to remote endpoints on every connection, but it does not warn users that a stable identifier is being disclosed to a remote service. This creates privacy risk and possible correlation across sessions/devices, especially because the identifier is machine-derived and used as an authentication credential.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script derives a stable identifier from machine-specific attributes such as machine-id, MachineGuid, hostname, MAC address, and username, then hashes and reuses it as an anonymous identifier. Although hashed, this creates a persistent cross-session device identifier that can enable user tracking and correlation across requests, undermining anonymity expectations and exposing privacy-sensitive host metadata semantics.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The tool silently creates and persists a stable anonymous ID in ~/.portfolio/aid without any user-facing notice or consent. In the context of a portfolio/fund-management tool, this can surprise users, enable long-term tracking, and conflict with privacy expectations for anonymous account usage.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script sends arbitrary request bodies and headers to the configured backend, and the usage examples include a login request containing username and password. While network access is part of the tool's purpose, there is no runtime disclosure when transmitting request data or the anonymous ID header, which is notable for potentially sensitive payloads.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The entire skill description and invocation guidance are written in Chinese, and there is no indication that users may choose another language or that the skill is intentionally limited to a Chinese-only locale. Under the stated policy, forcing a specific language without opt-in is a natural-language policy concern.

Content

No source excerpt is available for this finding.