Back to skill

Security audit

Finance Lite (Beta): Daily Macro + Market Move Brief

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent finance briefing tool, but it can expose configured market-data API keys in error output if provider requests fail.

Review before installing if you will use real paid or sensitive API keys. The finance behavior itself is disclosed and scoped, but provider failures may print full URLs containing API credentials; use restricted keys, avoid running in transcript-heavy or shared logging environments, and rotate keys if a failure log may have captured them.

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

Warning
Location
scripts/finance_lite/brief.mjs:232
Finding

Finnhub API Key Disclosure Through Error Logging

Content
View full analysis

Vulnerability Details

File Location: scripts/finance_lite/brief.mjs, lines 232–234, 414–435, and 867–869
Vulnerability Type: Sensitive credential exposure through exception messages
Risk Level: Medium

Vulnerable Code

js
async function fetchJson(url) {
  const res = await fetch(url, {
    headers: {
      accept: "application/json",
      "user-agent":
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36",
    },
  });
  const ct = (res.headers.get("content-type") || "").toLowerCase();
  const text = await res.text();
  if (!res.ok) throw new Error(`HTTP ${res.status} ${url} :: ${text.slice(0, 120)}`);
  if (!ct.includes("application/json")) {
    throw new Error(`Non-JSON response for ${url} (content-type=${ct}) :: ${text.slice(0, 120)}`);
  }
  return JSON.parse(text);
}
js
async function quote(symbol) {
  return await cached(`quote_${symbol}`, 60, async () => {
    const url = `${FINNHUB}/quote?symbol=${encodeURIComponent(symbol)}&token=${encodeURIComponent(FINNHUB_KEY)}`;
    return fetchJson(url);
  });
}

async function marketNews() {
  return await cached("market_news_general", 30 * 60, async () => {
    const url = `${FINNHUB}/news?category=general&token=${encodeURIComponent(FINNHUB_KEY)}`;
    return fetchJson(url);
  });
}

async function companyNews(symbol) {
  const to = new Date();
  const from = new Date(Date.now() - 7 * 86400 * 1000);
  return await cached(`company_news_${symbol}_${ymd(from)}_${ymd(to)}`, 30 * 60, async () => {
    const url =
      `${FINNHUB}/company-news` +
      `?symbol=${encodeURIComponent(symbol)}` +
      `&from=${encodeURIComponent(ymd(from))}` +
      `&to=${encodeURIComponent(ymd(to))}` +
      `&token=${encodeURIComponent(FINNHUB_KEY)}`;
    return fetchJson(url);
  });
}
js
main().catch((e) => {
  console.error("Finance Lite error:", e?.message ?? e);
  process.exit(1);
});

Technical Analysis

The Fin ...[truncated 2051 chars]

Remediation
View remediation

Remediation Suggestions

  1. Never place raw credential-bearing URLs in exception messages. Log only a sanitized endpoint name, HTTP method, status code, and non-sensitive diagnostic information.
  2. Redact sensitive query parameters before any URL is logged:
js
function sanitizeUrl(rawUrl) {
  const parsed = new URL(rawUrl);
  for (const key of ["token", "api_key", "key", "access_token"]) {
    if (parsed.searchParams.has(key)) parsed.searchParams.set(key, "[REDACTED]");
  }
  return parsed.toString();
}
  1. Use the sanitizer in all failure branches:
js
if (!res.ok) {
  throw new Error(
    `HTTP ${res.status} ${sanitizeUrl(url)} :: ${text.slice(0, 120)}`
  );
}

if (!ct.includes("application/json")) {
  throw new Error(
    `Non-JSON response for ${sanitizeUrl(url)} (content-type=${ct})`
  );
}
  1. Avoid logging response bodies unless required. Upstream error bodies can also contain sensitive or attacker-controlled content; restrict them to a small sanitized diagnostic field.
  2. Prefer an authorization header instead of a query parameter if the provider supports it, because query strings are commonly retained by proxies and logs.
  3. Add automated tests that use sentinel credentials and assert that thrown errors and stderr never contain those values.
  4. Rotate the Finnhub API key if existing logs or transcripts may already contain a failed request.
  5. Validate FINNHUB_API_KEY only for commands that perform Finnhub requests. Local-only operations such as list and remove do not require network credentials and should remain available without loading or validating the key.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (13)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 23)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 24)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 25)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 26)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 27)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 28)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 29)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 30)May include surrounding context.

md
- `brief` → `node ./scripts/finance_lite/brief.mjs brief`

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

A finance briefing skill would be expected to make network requests, but spawning a system binary through execFileSync is a stronger capability than ordinary HTTP fetching and is not disclosed in the manifest. This increases operational reach and dependency on host tooling without being necessary to the user-facing intent.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest describes a briefing skill focused on producing daily macro and market summaries with source/freshness notes and fallback behavior. However, the code also implements persistent watchlist modification via saveWatchlist, which writes local state and supports add/remove/list workflows beyond merely generating a brief.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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/finance_lite/brief.mjs (reported line 380)May include surrounding context.

js
async function nasdaqEtfFund(ticker) {
  if (!NASDAQ_KEY) return null;
  const url =
    `https://data.nasdaq.com/api/v3/datatables/ETFG/FUND.json` +
    `?ticker=${encodeURIComponent(ticker)}` +
    `&api_key=${encodeURIComponent(NASDAQ_KEY)}`;
  return await cached(`nasdaq_etfg_fund_${ticker}`, 24 * 3600, async () => curlJson(url));

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The inline comment states that events/calendar sync is intentionally disabled, suggesting no sync behavior occurs. Yet the watchlist mutation paths still call syncEventsAndCalendarAfterWatchlistChange(), which creates intent ambiguity by preserving synchronization hooks despite the comment's assertion.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This code sends ticker queries to external services (Finnhub and Nasdaq Data Link elsewhere in the file) and persists watchlist data to disk, but it does not clearly disclose to the user that their requested symbols and locally configured lists are transmitted to third parties and stored locally. The visible output includes operational status messages, but not a user-facing warning about these privacy- and persistence-relevant behaviors.

Content

No source excerpt is available for this finding.