Back to skill

Security audit

Expense Tracker v2

Security checks for vulnerabilities and agentic risk

Overview

This expense-tracking skill mostly does what it says, but it handles financial records and backend credentials with under-disclosed and unsafe secret/data practices that users should review before installing.

Review this skill carefully before installing. Use only test or narrowly scoped Notion/Supabase credentials, avoid the pass <password> command, do not enter secrets while screen-sharing or in recorded terminals, and assume local expense records may be stored unencrypted unless the implementation is fixed.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/expense-tracker.js:248
Finding

Unrestricted Supabase Endpoint Can Expose API Credentials and Financial Records

Content
View full analysis
{ config.supabase = { url: url.trim() }; rl.question('Anon Key: ', (key) => { config.supabase.api_key = key.trim(); if (saveConfig(config)) { log('Supabase configured', 'green'); } rl.close(); }); }); ``` ```javascript async function supabaseAdd(config, record) { const { url, api_key } = config.supabase; const response = await fetch(`${url}/rest/v1/expenses`, { method: 'POST', headers: { 'Authorization': `Bearer ${api_key}`, 'Content-Type': 'application/json', 'apikey': api_key }, body: JSON.stringify(record) }); return response.ok; } async function supabaseList(config) { const { url, api_key } = config.supabase; const response = await fetch(`${url}/rest/v1/expenses?order=date.desc`, { headers: { 'Authorization': `Bearer ${api_key}`, 'apikey': api_key } }); if (!response.ok) return []; return await response.json(); } ``` ### Technical Analysis The Supabase URL is accepted as arbitrary text and subsequently used as the destination for authenticated requests. The implementation does not parse the value with `URL`, require HTTPS, restrict the destination to an expected Supabase host, or reject loopback, private, link-local, and cloud metadata addresses. Both request functions attach the configured Supabase key as an `Authorization` bearer token and an `apikey` header. `supabaseAdd` additionally transmits the complete financial record, including its amount, category, note, date, and identifier. Remote synchronization is consistent with the Skill's declared functionality when the user intentionally selects Supabase. Therefore, the network operation is not inher ...[truncated 1656 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/expense-tracker.js:463
Finding

Master Password Accepted Through Process Command-Line Arguments

Content
View full analysis
', 'yellow'); } break; ``` The same insecure invocation is documented in `SKILL.md`, lines 30-34: ```bash expense-tracker pass ``` ### Technical Analysis The `pass` command reads the master password directly from `process.argv`. Command-line arguments are not an appropriate secret-input channel because they may be retained in shell history, exposed to process inspection mechanisms, recorded by endpoint monitoring products, or captured in command telemetry and audit logs. The master password protects the encrypted configuration containing Notion or Supabase credentials. AES-256-GCM protection of the configuration does not mitigate disclosure of the password through a separate plaintext channel. A further functional limitation is that `masterPassword` exists only in the current Node.js process. The `pass` command sets it and then exits, so a later invocation does not inherit it. This encourages repeated password entry through the exposed command-line mechanism without providing persistent process-local authentication. ### Attack Path 1. A user follows the documented syntax and runs `expense-tracker pass `. 2. The plaintext password becomes part of the shell command line. 3. The command is saved in shell history or captured by process inspection, monitoring, logging, or session-recording software. 4. An attacker with access to that data obtains the master password. 5. The attacker reads `~/.openclaw/expense-tracker/config.enc`. 6. The attacker uses the disclosed password to decrypt the configuration and recover backend credentials and identifiers. ### Impa ...[truncated 550 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/expense-tracker.js:94
Finding

Interactive Password and API-Key Prompts Echo Secrets to the Terminal

Content
View full analysis
{ setPassword(password); rl.close(); }); ``` ```javascript rl.question('Set master password: ', (password) => { const confirm = rl.question('Confirm password: '); if (password !== confirm) { log('Passwords do not match', 'red'); rl.close(); return; } ``` ```javascript case '2': config.backend = 'notion'; rl.question('Notion API Key: ', (key) => { config.notion = { api_key: key.trim() }; rl.question('Database ID: ', (dbId) => { config.notion.database_id = dbId.trim(); if (saveConfig(config)) { log('Notion configured', 'green'); } rl.close(); }); }); break; case '3': config.backend = 'google_sheet'; rl.question('Credentials file path: ', (credPath) => { config.google_sheet = { credentials: credPath.trim() }; rl.question('Spreadsheet ID: ', (sheetId) => { config.google_sheet.spreadsheet_id = sheetId.trim(); if (saveConfig(config)) { log('Google Sheet configured', 'green'); } rl.close(); }); }); break; case '4': config.backend = 'supabase'; rl.question('Supabase URL: ', (url) => { config.supabase = { url: url.trim() }; rl.question('Anon Key: ', (key) => { config.supabase.api_key = key.trim(); if (saveConfig(config)) { log('Supabase configured', 'green'); } rl.close(); }); }); ``` ### Technical Analysis Node.js `readline.question()` performs ordinary terminal input and does not mask sensitive values. Consequently, th ...[truncated 1796 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill description overstates its protections and behavior: it implies broad encrypted handling, but only the config file is encrypted while local transaction data may remain plaintext, and it omits that passwords can be supplied via CLI arguments. This can mislead users into exposing sensitive financial data or credentials under a false sense of security, especially in a finance-related skill where confidentiality expectations are high.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill advertises support for networked backends such as Notion and Supabase, but it does not declare any explicit tool scope or allowed-tools metadata. This weakens transparency and policy enforcement, making it easier for a skill to perform outbound network actions beyond what a reviewer or runtime expects.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

Accepting the master password on the command line exposes it to process listings, shell history, audit logs, and terminal recording mechanisms. Because this password protects stored backend credentials, disclosure can cascade into compromise of Notion, Supabase, or other connected accounts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code prompts the user for a Notion API key and Supabase key, then saves them to an encrypted local config file, but provides no disclosure that secrets will be stored on disk or that terminal input may expose them on screen/history. For code files, accessing and storing sensitive credentials should have at least some visible warning, prompt context, or explanatory comment unless clearly disclosed elsewhere.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Expense records are saved to a local JSON file in plaintext under the user's home directory, exposing sensitive financial history to any local user, malware, backup system, or process with file access. Because this skill handles personal finance data, plaintext at-rest storage materially increases privacy and confidentiality risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This function sends expense record contents, including note text and dates, to the Notion API over the network, but the code provides no user-facing disclosure that local financial data will leave the machine. For code files, network transmission of user data should have some visible warning or documentation unless the behavior is clearly disclosed as the skill's stated purpose.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/expense-tracker.js (reported line 225)May include surrounding context.

js
// Notion API
async function notionAdd(config, record) {
  const { api_key, database_id } = config.notion;
  const response = await fetch(`https://api.notion.com/v1/pages`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${api_key}`,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/expense-tracker.js (reported line 248)May include surrounding context.

js
// Notion API
async function notionAdd(config, record) {
  const { api_key, database_id } = config.notion;
  const response = await fetch(`https://api.notion.com/v1/pages`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${api_key}`,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Supabase requests are sent to a user-configurable URL, meaning sensitive financial records and API keys may be transmitted to an arbitrary endpoint if the URL is malicious, mistyped, or compromised. In this skill context, accepting and using an unrestricted remote base URL for finance data meaningfully raises the risk of credential exfiltration and privacy loss.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest says the skill supports multi-backend storage including Google Sheet, and the setup flow lets users configure a Google Sheet backend. However, both googleSheetAdd and googleSheetList only log "not implemented" and return failure/empty results, which directly contradicts the claimed capability.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.