T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/portfolio.py:45
- Finding
- Portfolio Data Stored Without Explicit Owner-Only Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/portfolio.py`, lines 45–49 **Vulnerability Type**: Plaintext sensitive-data storage with insufficiently enforced file permissions **Risk Level**: Medium ### Vulnerable Code ```python 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 codes, purchase costs, quantities, and timestamps. The code creates its parent directories and writes the file using process-default permissions. It does not explicitly require owner-only directory permissions such as `0700` or file permissions such as `0600`. When the file is first created, its permissions are determined by the operating system defaults and the process umask. With a common umask of `022`, the file may be created as `0644`, allowing other local users to read it. The parent directories may similarly be accessible. Reopening an existing file with `open(..., "w")` truncates it but does not correct previously insecure permissions. The portfolio is stored in plaintext at: ```text ~/.clawdbot/skills/a-stock-analysis/portfolio.json ``` No remote exposure is demonstrated. Exploitation requires another local account or process capable of traversing the relevant home-directory path and reading the resulting file. ### Attack Path 1. A user runs a portfolio command such as `add`, `update`, or `analyze`. 2. The command invokes `save_portfolio()`. 3. Python creates or rewrites `portfolio.json` without explicitly enforcing owner-only permissions. 4. Under a permissive umask or pre-existing insecure permissions, the file remains readable by other local users. 5. A local attacker traverses the portfolio directory and reads the JSON ...[truncated 606 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create the portfolio directory with owner-only permissions: ```python PORTFOLIO_FILE.parent.mkdir(parents=True, exist_ok=True, mode=0o700) PORTFOLIO_FILE.parent.chmod(0o700) ``` 2. Create replacement files with mode `0600` and write them atomically: ```python import os import tempfile def save_portfolio(data: dict): PORTFOLIO_FILE.parent.mkdir(parents=True, exist_ok=True, mode=0o700) PORTFOLIO_FILE.parent.chmod(0o700) data["updated_at"] = datetime.now().isoformat() fd, temporary_path = tempfile.mkstemp( dir=PORTFOLIO_FILE.parent, prefix=".portfolio-", suffix=".tmp", ) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) f.flush() os.fsync(f.fileno()) os.replace(temporary_path, PORTFOLIO_FILE) PORTFOLIO_FILE.chmod(0o600) except Exception: try: os.unlink(temporary_path) except FileNotFoundError: pass raise ``` 3. Correct permissions on existing installations by applying `0700` to the portfolio directory and `0600` to `portfolio.json`. 4. Avoid relying solely on the process umask for sensitive files. 5. If the local threat model includes privileged processes, account compromise, or untrusted backup storage, consider encrypting portfolio data using an operating-system credential store or a user-controlled encryption key. ]]>
