Back to skill

Security audit

A股分时量能分析

Security checks for vulnerabilities and agentic risk

Overview

This A-share stock tool is mostly purpose-aligned, but it stores and mutates local portfolio financial records with limited safeguards.

Review this skill before installing if you plan to record real holdings. It will store stock codes, costs, quantities, and timestamps locally in plaintext, and an agent can add, update, or remove those records when using the portfolio commands. Keep backups of important portfolio data and avoid storing sensitive holdings on shared machines unless you are comfortable with the local file exposure risk.

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
scripts/portfolio.py:27
Finding

Portfolio Financial Data Is Stored Without Enforced Access Controls or Safe File Replacement

Content
View full analysis

Vulnerability Details

File Location: scripts/portfolio.py, lines 27–49
Vulnerability Type: Plaintext sensitive-data storage, insecure file permissions, and unsafe file replacement
Risk Level: Medium

python
PORTFOLIO_FILE = Path.home() / ".clawdbot" / "skills" / "a-stock-analysis" / "portfolio.json"


def load_portfolio() -> dict:
    """加载持仓数据"""
    if PORTFOLIO_FILE.exists():
        with open(PORTFOLIO_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return {"positions": [], "updated_at": None}


def save_portfolio(data: dict):
    """保存持仓数据"""
    PORTFOLIO_FILE.parent.mkdir(parents=True, exist_ok=True)
    data["updated_at"] = datetime.now().isoformat()
    with open(PORTFOLIO_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

Technical Analysis

The portfolio file contains financially sensitive information, including stock identifiers, acquisition costs, quantities, and timestamps. The application serializes this information as unencrypted JSON in the user's home directory.

The directory and file are created without explicit restrictive permissions. Consequently, their effective permissions depend on the process umask and any permissions already assigned to an existing file or directory. A permissive environment may therefore make portfolio information accessible to other local users or processes.

The application also opens the final destination directly with write mode. It does not verify that the destination is a regular file owned by the current user, reject symbolic links, or perform an atomic replacement through a securely created temporary file. Directly truncating and rewriting the destination introduces the possibility of corruption during an interrupted write and may enable link-based redirection if an attacker can modify the containing path.

Attack Path

A confidentiality attack can proceed as follows:

  1. The victim runs a portfolio command that ...[truncated 1703 chars]
Remediation
View remediation

Remediation Suggestions

  1. Create the portfolio directory with owner-only permissions and verify its ownership and type:
python
PORTFOLIO_FILE.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(PORTFOLIO_FILE.parent, 0o700)
  1. Reject symbolic links and unexpected file types before reading or replacing the destination. Use lstat() for validation rather than following links.

  2. Write through a securely created temporary file in the same directory, enforce mode 0600, flush it, and atomically replace the destination:

python
import os
import tempfile

directory = PORTFOLIO_FILE.parent
directory.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(directory, 0o700)

fd, temporary_path = tempfile.mkstemp(
    prefix=".portfolio-",
    suffix=".tmp",
    dir=directory,
)

try:
    os.fchmod(fd, 0o600)
    with os.fdopen(fd, "w", encoding="utf-8") as stream:
        json.dump(data, stream, ensure_ascii=False, indent=2)
        stream.flush()
        os.fsync(stream.fileno())

    if PORTFOLIO_FILE.is_symlink():
        raise RuntimeError("Refusing to replace a symbolic link")

    os.replace(temporary_path, PORTFOLIO_FILE)
    os.chmod(PORTFOLIO_FILE, 0o600)
finally:
    if os.path.exists(temporary_path):
        os.unlink(temporary_path)
  1. Validate that an existing portfolio file is a regular file owned by the current user before loading it.

  2. Consider encrypting the portfolio at rest using an operating-system credential store or a well-reviewed encryption mechanism if confidentiality against local file disclosure is required.

  3. Handle malformed or partially written JSON explicitly so file corruption produces a controlled error rather than terminating the command unexpectedly.

Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The remove command permanently deletes a portfolio entry immediately after matching the stock code, with no confirmation prompt, dry-run mode, or undo capability. In a portfolio-management context, accidental invocation, mistyped codes, or automation mistakes can silently destroy user-maintained records and lead to loss of tracking data.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The natural-language content throughout the file is Chinese, which effectively forces a specific language without user opt-in. The policy allows locale constraints when they are explicitly documented and justified, but this file does not state that the skill is intentionally Chinese-only or offer an alternative language option.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill explicitly documents commands that add, update, and remove entries from a persistent local portfolio file, but it does not warn the user that these operations modify data on disk. This can lead to unintended local data changes or deletion if an agent invokes the commands without clear confirmation, especially because the storage path is persistent across sessions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The module docstring, usage text, CLI descriptions, and user-facing output are all written exclusively in Chinese, indicating the skill is designed to communicate in a single language by default. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly documented and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script's natural-language interface and usage text are entirely in Chinese and specifically target A-share holdings, with no indication that users can select another language or locale. This can violate language/locale policy when the skill imposes a single language experience without explicit opt-in or justification.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.