Back to skill

Security audit

Hledger

Security checks for vulnerabilities and agentic risk

Overview

This skill is a thin hledger wrapper, but its implementation can execute unintended shell commands from skill input despite saying it only runs hledger commands.

Review before installing. Only use this in a tightly sandboxed environment with non-sensitive test ledgers unless it is changed to call hledger without a shell, validate arguments, restrict file paths, and accurately document what local commands and files it can access.

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

Error
Location
index.js:4
Finding
OS Command Injection Through Untrusted Skill Input## Vulnerability Details **File Location**: `index.js:4-8` **Vulnerability Type**: OS command injection **Risk Level**: Critical ### Vulnerable Code ```js async run({ input }) { const { exec } = require("child_process"); return new Promise((resolve, reject) => { exec(`hledger ${input}`, (err, stdout, stderr) => { if (err) return reject(stderr || err.message); ``` ### Technical Analysis The skill interpolates attacker-controlled `input` directly into a command passed to Node.js `child_process.exec()`. This API executes the resulting string through a system shell. Consequently, shell metacharacters and constructs within `input`, including `;`, `&&`, `|`, redirections, and command substitutions such as `$()`, are interpreted by the shell rather than treated exclusively as arguments to `hledger`. Prefixing the command with `hledger` does not restrict execution to that binary. The claim in `SKILL.md` that the skill does not permit arbitrary shell execution is inconsistent with the implementation. ### Attack Path 1. An attacker supplies crafted skill input containing a valid or attempted `hledger` argument followed by a shell operator and another command. 2. For example, the attacker submits: ```text balance; id ``` 3. The application constructs the following command: ```sh hledger balance; id ``` 4. `exec()` invokes a shell, which treats the semicolon as a command separator. 5. The shell runs both `hledger balance` and `id`. 6. More harmful commands could then read or modify files, access process-available secrets, invoke network utilities, or alter the host within the service account's permissions. ### Impact Assessment Successful exploitation provides arbitrary command execution with the operating-system privileges of the OpenClaw/Node.js process. The accessible scope may include ledger and other files readable by that account, writable application or user data, environment variables, locally available credentials, an ...[truncated 298 chars]
Remediation
## Remediation Suggestions 1. Replace `child_process.exec()` with `execFile()` or `spawn()` and ensure shell execution is disabled: ```js const { execFile } = require("child_process"); execFile("hledger", validatedArgs, { shell: false }, (err, stdout, stderr) => { if (err) return reject(stderr || err.message); resolve(stdout || stderr); }); ``` 2. Do not split raw input with a simple whitespace expression because that mishandles quoting and can produce ambiguous arguments. Prefer a structured input contract in which the caller supplies an argument array. 3. Validate every argument's type and length. Enforce an allowlist of supported `hledger` subcommands and options where the intended feature set permits it. 4. Restrict file-related options such as `-f`/`--file` to explicitly authorized ledger paths. Resolve and normalize paths before comparing them against approved directories. 5. Run the skill under a dedicated least-privileged account with access only to required ledger files. Limit its environment variables, filesystem permissions, and outbound network access. 6. Add regression tests using inputs containing `;`, `&&`, `|`, backticks, `$()`, newlines, and redirection operators. Verify that these values are either rejected or passed only as literal arguments without causing secondary commands to execute. 7. Correct the security documentation in `SKILL.md` so it accurately reflects the implementation and its input restrictions.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Vague Triggers

Medium
Confidence
89% confidence
Finding
The skill documentation presents very broad invocation examples such as raw `balance`, `register Assets`, and `balance -f myledger.journal` without defining a strict trigger format or clear boundary between normal chat text and executable skill input. Because this skill forwards user-controlled arguments to a local CLI that can access host files via options like `-f`, ambiguous activation increases the risk of unintended command execution or misuse against sensitive ledger data in multi-agent or chat-integrated contexts.

Missing User Warnings

Medium
Confidence
99% confidence
Finding
This skill passes untrusted user input directly into child_process.exec using a shell command template, which enables command injection in addition to undisclosed subprocess execution. An attacker can append shell metacharacters or additional commands to run arbitrary code on the host, making the risk far more severe in this skill context than a simple missing warning.

Vague Triggers

Low
Confidence
89% confidence
Finding
The manifest descriptions say only "Run hledger CLI commands" and "Execute hledger commands and return output" without defining specific trigger phrases, scope limits, or exclusion conditions. In a manifest file, this broad wording can make activation conditions unclear and increases the chance of unintended invocation for general accounting-related requests.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
index.js:7