Back to skill

Security audit

Beancount

Security checks for vulnerabilities and agentic risk

Overview

This is a local bookkeeping skill, but it stores sensitive financial records and an extra command history on disk without clear permission hardening or full disclosure.

Review before installing if you plan to store real financial information. The skill appears local-only, but users should know it writes personal finance data and logs to disk and does not explicitly protect those files with private permissions. Avoid entering secrets in command arguments, and consider tightening permissions on ~/.bookkeeping and the beancount data directory if used.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/book.sh:17
Finding

Financial records are created without restrictive filesystem permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/book.sh:17-43 and scripts/book.sh:233-241
Vulnerability Type: Sensitive local data exposure through process-default permissions
Risk Level: Medium

Vulnerable Code

python
def ensure_data_dir():
    if not os.path.exists(DATA_DIR):
        os.makedirs(DATA_DIR)

def load_records():
    ensure_data_dir()
    if os.path.exists(RECORDS_FILE):
        with open(RECORDS_FILE, 'r') as f:
            return json.load(f)
    return []

def save_records(records):
    ensure_data_dir()
    with open(RECORDS_FILE, 'w') as f:
        json.dump(records, f, ensure_ascii=False, indent=2)

def load_budgets():
    ensure_data_dir()
    if os.path.exists(BUDGETS_FILE):
        with open(BUDGETS_FILE, 'r') as f:
            return json.load(f)
    return {}

def save_budgets(budgets):
    ensure_data_dir()
    with open(BUDGETS_FILE, 'w') as f:
        json.dump(budgets, f, ensure_ascii=False, indent=2)
python
def save_goals(goals):
    ensure_data_dir()
    goals_file = os.path.join(DATA_DIR, "goals.json")
    with open(goals_file, 'w') as f:
        json.dump(goals, f, ensure_ascii=False, indent=2)

Technical Analysis

The application stores personal financial information in ~/.bookkeeping, including transaction amounts, income and expense categories, free-form transaction notes, budgets, and savings goals. Neither os.makedirs() nor the calls to open(..., 'w') specify or enforce restrictive permissions.

Consequently, permissions depend on the process umask. With a common umask of 022, a newly created directory may receive mode 0755, while files may receive mode 0644. If the user's home directory is traversable by other accounts, or if the data directory is otherwise exposed through shared storage, these permissions allow other local users to read the financial data.

Existing files are also not checked ...[truncated 1356 chars]

Remediation
View remediation

Remediation Suggestions

  • Set a restrictive process umask, such as 0o077, before creating bookkeeping data.
  • Create ~/.bookkeeping with mode 0700.
  • Create all financial data files with mode 0600.
  • Check and repair permissions on existing directories and files.
  • Use atomic writes through a temporary file in the same protected directory, followed by os.replace(), to avoid partial or corrupted JSON files.
  • Reject symbolic links or unexpected non-regular files before writing sensitive data.

Example hardening approach:

python
import os
import stat
import tempfile

os.umask(0o077)

def ensure_data_dir():
    os.makedirs(DATA_DIR, mode=0o700, exist_ok=True)
    os.chmod(DATA_DIR, 0o700)

def secure_json_write(path, value):
    ensure_data_dir()

    if os.path.lexists(path) and not stat.S_ISREG(os.lstat(path).st_mode):
        raise RuntimeError("Refusing to overwrite a non-regular data file")

    fd, temporary_path = tempfile.mkstemp(dir=DATA_DIR)
    try:
        os.fchmod(fd, 0o600)
        with os.fdopen(fd, "w") as output:
            json.dump(value, output, ensure_ascii=False, indent=2)
            output.flush()
            os.fsync(output.fileno())
        os.replace(temporary_path, path)
        os.chmod(path, 0o600)
    except Exception:
        try:
            os.unlink(temporary_path)
        except FileNotFoundError:
            pass
        raise

T09 · Insecure Skill Coding Practices

Note
Location
scripts/script.sh:5
Finding

Undocumented utility data and history files inherit potentially permissive permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/script.sh:5-7, scripts/script.sh:35, and scripts/script.sh:62-65
Vulnerability Type: Local data exposure through process-default permissions
Risk Level: Low

Vulnerable Code

bash
VERSION="2.0.0"
DATA_DIR="${BEANCOUNT_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/beancount}"
DB="$DATA_DIR/data.log"
mkdir -p "$DATA_DIR"
bash
_log() { echo "$(date '+%m-%d %H:%M') $1: $2" >> "$DATA_DIR/history.log"; }
bash
cmd_add() {
    echo "$(date +%Y-%m-%d) $*" >> "$DB"; echo "  Added: $*"
    _log "add" "${1:-}"
}

Technical Analysis

The additional utility script creates a configurable data directory and appends user-supplied entries to data.log and command metadata to history.log. It does not establish a restrictive umask or explicitly restrict file and directory permissions.

Under a common umask of 022, the directory and files may be readable by other local accounts when parent-directory permissions permit access. Because cmd_add records all supplied arguments in data.log, users may inadvertently store financial or otherwise sensitive text in a locally readable file. The history log also retains command names and the first argument supplied to logged operations.

The script is not documented as the main bookkeeping interface in SKILL.md, but it is included in the distributed project and can be invoked directly.

Attack Path

  1. A user invokes scripts/script.sh add with financial or sensitive text.
  2. The script creates its data directory and log files using process-default permissions.
  3. The process runs with a permissive umask and the selected parent directory is accessible to another local account.
  4. The other account reads data.log or history.log.
  5. The attacker obtains stored entries or command metadata.

This path requires local filesystem access and sufficiently permissive parent-directory p ...[truncated 449 chars]

Remediation
View remediation

Remediation Suggestions

  • Add umask 077 before creating the data directory or files.
  • Create the data directory with mode 0700 and repair its permissions after creation.
  • Ensure data.log and history.log use mode 0600.
  • Validate that the configured data directory is an expected directory owned by the current user.
  • Refuse to write through symbolic links or to non-regular files.
  • Document the utility's storage behavior and warn users not to place secrets in command arguments.
  • Consider removing scripts/script.sh if it is unrelated to the documented bookkeeping functionality.

Example:

bash
umask 077
mkdir -p -- "$DATA_DIR"
chmod 700 -- "$DATA_DIR"

touch -- "$DB" "$DATA_DIR/history.log"
chmod 600 -- "$DB" "$DATA_DIR/history.log"
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • 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 (6)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

This code presents core user interaction text in Chinese and continues that pattern throughout the script, but provides no opt-in, locale selection, or documented justification for enforcing a specific language. That matches the policy category for language or locale constraints without user choice.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script persistently records user-supplied arguments to a history file under the user's data directory without notice, and multiple commands pass arbitrary user input into that log. If users supply secrets, personal data, search terms, or operational details on the command line, those values are retained on disk and may later be exposed to other local processes, backups, or forensic review.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill metadata and description present instructions in both Chinese and English, but do not state any user-selectable language preference or opt-in. This can violate a language/locale policy when a skill imposes or defaults to a language experience without explicitly offering a choice.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The help output describes book.sh report as producing a monthly report with '分类饼图' (category pie chart). However, enhanced_report() only renders textual summaries, percentages, and block-character bars, with no pie chart generation or visualization logic anywhere in the file. This is an active documentation-to-code mismatch rather than a mere omission.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The help text and command list imply functional operations such as 'remove', 'config', 'run', and 'init'. In the implementation, these commands mostly just echo status text and append to a history log, and 'remove' does not actually remove data at all. This is a direct mismatch between the documented command intent and code behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

SQP-3 applies to all file types and covers language or locale policy violations. This markdown content presents all guidance only in Chinese and does not provide any user opt-in, alternative language, or justification that it is intended solely for a Chinese-language audience.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.