Back to skill

Security audit

simple-ledger

Security checks for vulnerabilities and agentic risk

Overview

This finance skill is mostly coherent, but it needs Review because its scripts do not enforce the declared ledger-folder boundary for some reads and writes.

Install only if you are comfortable with a local finance tool that can execute its Python scripts and modify persistent ledger files. Keep ledgers under the declared data directory, avoid passing arbitrary file paths, and consider pinning or isolating akshare before enabling live quotes.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/parse_entry.py:314
Finding

Path Prefix Validation Allows Writes Outside the Authorized Workspace

Content
View full analysis
bool: """Security validation: only permit paths within ~/.openclaw/workspace/.""" try: if str(path.resolve()).startswith( str(Path.home() / ".openclaw" / "workspace") ): return True except Exception: pass return False ``` The result is used to authorize a user-supplied ledger path: ```python path = Path(ledger_path) if not _safe_write_path(path): return False path.parent.mkdir(parents=True, exist_ok=True) account = entry["account"] with open(path, "a", encoding="utf-8-sig") as f: _ensure_account_defined(path, account, f) f.write(line + "\n") ``` The same vulnerable containment pattern is used by other scripts. For example, `scripts/generate_report.py` uses it before writing a report: ```python if args.output: out_path = Path(args.output) if not _safe_write_path(out_path): print(f"❌ Not permitted to write to: {args.output}") sys.exit(1) out_path.write_text(report, encoding="utf-8") ``` ### Technical Analysis The validation compares canonical path strings with `str.startswith()`. A string-prefix check does not establish a filesystem directory boundary. For example, if the authorized root is: ```text /home/user/.openclaw/workspace ``` then this unauthorized sibling also passes the check: ```text /home/user/.openclaw/workspace-evil/output.csv ``` Its canonical string starts with the authorized root string even though it is not a descendant of that directory. The checks are also broader than the permission declared in `SKILL.md`, which limits wr ...[truncated 1504 chars]
Remediation
View remediation
Path | None: try: resolved = candidate.expanduser().resolve() if resolved == ALLOWED_ROOT or resolved.is_relative_to(ALLOWED_ROOT): return resolved except (OSError, RuntimeError): pass return None ``` 3. For older Python versions, use `os.path.commonpath()`: ```python if os.path.commonpath([str(resolved), str(ALLOWED_ROOT)]) != str(ALLOWED_ROOT): return None ``` 4. Apply the same helper consistently to all read, write, append, delete, and migration operations. 5. Where practical, reject symlinks for destination files and parent directories, and use safe file-opening patterns that reduce time-of-check/time-of-use races. 6. Add regression tests covering: - `workspace-evil/file.csv` - `ledger-backup/file.csv` - `..` traversal - symlinks resolving outside the ledger directory - the exact authorized root - valid descendants of the authorized root ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/query_ledger.py:87
Finding

User-Controlled Ledger Paths Permit Reads Outside the Declared Data Directory

Content
View full analysis
list: """Read all transaction entries from the ledger.""" path = Path(ledger_path) if not path.exists(): print(f"❌ File does not exist: {ledger_path}") sys.exit(1) entries = [] for line in path.read_text(encoding="utf-8-sig").splitlines(): e = parse_csv_line(line) if e and 'balance_entry' not in e: entries.append(e) return entries def read_balances(ledger_path: str) -> dict: """ Read initial balance configuration. """ path = Path(ledger_path) if not path.exists(): return {} balances = {} for line in path.read_text(encoding="utf-8-sig").splitlines(): stripped = line.strip() # Parsing continues... ``` The report generator has the same issue: ```python def read_entries(filepath: str) -> list: path = Path(filepath) if not path.exists(): print(f"⚠️ File does not exist: {filepath}", file=sys.stderr) return [] entries = [] for line in path.read_text(encoding="utf-8-sig").splitlines(): e = parse_csv_line(line) if e: entries.append(e) return entries ``` ### Technical Analysis These commands accept a path directly from a positional command-line argument and read it without checking whether the canonical path is under: ```text ~/.openclaw/workspace/data/ledger/ ``` This contradicts the file-read permissions and security guarantees declared in `SKILL.md`. Any regular file readable by the Agent process can be supplied. The parsers only retain lines matching expected ledger formats. This limits the usefulness of reading arbitrary unstructured ...[truncated 1668 chars]
Remediation
View remediation
Path: candidate = Path(path_text).expanduser().resolve(strict=True) if not candidate.is_relative_to(ALLOWED_ROOT): raise PermissionError("Ledger path is outside the authorized directory") if not candidate.is_file(): raise ValueError("Ledger path must be a regular file") return candidate ``` 2. Use one shared path-authorization module across all scripts so read and write policies cannot diverge. 3. Resolve and validate symlinks before opening files. If symlinks are not required, reject them explicitly. 4. Prefer accepting a ledger filename relative to the fixed ledger root instead of an arbitrary filesystem path. 5. If importing an external ledger is a legitimate feature, implement it as a distinct operation that: - requests explicit user approval, - clearly displays the source path, - copies the file into the authorized ledger directory, - validates its format before subsequent processing. 6. Add security tests for absolute paths, traversal, prefix-confusable siblings, and symlink escapes. ]]>

T08 · Insecure Dependencies

Note
Location
README.md:65
Finding

Optional Network Dependency Is Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
``` 2. Use a lock file that includes all transitive dependencies. 3. Generate and verify package hashes, for example through a hash-locked requirements file: ```bash pip install --require-hashes -r requirements.txt ``` 4. Document the approved package index and avoid untrusted mirrors or additional package sources. 5. Install the optional dependency in an isolated virtual environment with only the filesystem and network permissions required for quote lookup. 6. Review and update the pinned version on a controlled schedule after testing network behavior and transitive dependency changes. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (70)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The code’s primary function is narrower than the declared description: it is specifically a budget configuration and budget-progress/alert tool operating on an existing CSV ledger. It parses CSV only to total monthly expense amounts by category, then compares those totals against budget values saved in a sidecar .budget.json file. The declared description claims a broader personal-finance assistant that can log expenses, check balances, track budgets, set savings goals, and monitor investments from natural language, including optional real-time quotes. None of the expense-entry, balance computation, natural-language parsing, investment monitoring, or quote-fetching behavior appears in this code. While the budget-tracking portion is consistent with the description, the code omits several major declared capabilities and therefore the description does not accurately represent what this chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding

The declared description presents a broad personal-finance assistant with natural-language input, transaction logging, balances, budgets, savings goals, and investment monitoring. The supplied code chunk only implements monthly report generation from an existing CSV ledger: parsing entries, aggregating income/expenses by category, computing simple metrics, and printing or saving a report. That is related to finance reporting, but it is materially narrower than the declared purpose and lacks most advertised capabilities. Additionally, the code can write an output report file locally, while no permissions are declared. This is enough to classify as a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The declared description presents a broad personal-finance assistant covering expenses, balances, budgets, savings goals, and investments. The supplied code chunk is much narrower: it is specifically an investment tracker. It handles securities buy/sell/dividend records, portfolio/return summaries, manual price updates, optional network quote refresh, and code lookup. Those investment-related behaviors do align with the investment-monitoring portion of the description, including offline-first CSV storage and opt-in networking. However, the chunk does not implement the other prominently declared finance capabilities (expenses, balances, budgets, savings goals), so the description is materially broader than what this code actually does. Additionally, this code writes an auto-refresh setting to a .env file and invokes another script for code lookup—capabilities not clearly reflected in the declared purpose. Therefore this chunk does not accurately represent the full declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description presents a broad offline-first personal finance tracker with optional real-time investment quotes. The supplied code instead is a focused lookup utility that searches stock and fund identifiers from online sources. While investment monitoring could plausibly need symbol/code lookup as a supporting feature, this chunk materially centers on external code search rather than the declared main functions. It also clearly performs network/data-source access through akshare and an auxiliary subprocess, despite no declared permissions and an offline-first framing. Therefore this code is not accurately represented by the description as provided.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The supplied code is narrowly focused on natural-language expense/income parsing and CSV ledger appending. It extracts dates, amounts, transaction type, category, account, formats CSV lines, validates them, and writes to a local file under a safe path. While this supports part of the declared purpose—logging transactions with plain-text CSV storage and some account handling—it does not implement several prominently declared capabilities: checking balances, tracking budgets, setting savings goals, or monitoring investments/quotes. There is also no network access in this chunk despite the description mentioning optional real-time quotes. Because the declared description presents a substantially broader finance-management skill than the code actually performs, this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents a broader personal-finance assistant with natural-language interaction, write/log capabilities, budgeting, savings-goal management, and investment monitoring. The supplied code chunk is much narrower: it is a read-only command-line ledger query tool for CSV files. It accurately supports checking balances and summarizing expenses/income across multiple accounts stored in plain-text CSV offline, but it does not implement natural-language parsing, transaction logging, budgets, savings goals, investment tracking, or any network access. Because several core advertised capabilities are absent and the primary purpose is narrower than declared, this is a material description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code is related to a bookkeeping skill, but its primary purpose is testing, not end-user finance operations. It includes a CSV ledger parser and query/report logic consistent with expense and balance analysis, and it supports multi-account CSV records. However, the supplied chunk does not implement the declared user-facing features such as logging from natural language, budget tracking, savings goals, or investment monitoring. Instead, it runs automated validation against YAML fixtures and outputs test reports, which is an undeclared capability and a materially different primary purpose for this code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for an offline-first personal finance and investment tracking skill. The supplied code does not implement any finance-related behavior such as expense logging, balance calculation, budget tracking, savings goals, account handling, CSV storage, or quote retrieval. Instead, it is a validation script for SKILL.md format compliance. This is a materially different primary purpose, so the description does not accurately represent the code.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/invest.py (reported line 213)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/invest.py (reported line 1102)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/invest.py (reported line 1104)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 854)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 860)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 866)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 872)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 878)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 883)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 913)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/invest.py (reported line 214)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False
    try:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/invest.py (reported line 1166)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False
    try:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/invest.py (reported line 1182)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False
    try:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 863)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False
    try:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 869)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False
    try:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_comprehensive.py (reported line 875)May include surrounding context.

python
def auto_refresh_enabled() -> bool:
    """检查用户是否开启了自动联网刷新(通过 .env 配置文件)"""
    env_file = DATA_DIR / ".env"
    if not env_file.exists():
        return False
    try:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_security.py (reported line 35)May include surrounding context.

python
self.assertIsNotNone(safe)

    def test_reject_absolute_outside_workspace(self):
        unsafe = _safe_ledger_path(Path("/etc/passwd"))
        self.assertIsNone(unsafe)

    def test_reject_parent_traversal(self):

Static analysis

No suspicious patterns detected.