T09 · Insecure Skill Coding Practices
- Location
scripts/dashboard.mjs:572- Finding
Saved reasoning-provider API keys may be stored with overly broad filesystem permissions
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a coherent trading-analysis dashboard, but it needs Review because optional reasoning credentials and trading context can be sent externally with insufficient endpoint and saved-key safeguards.
Install only if you are comfortable with a local trading dashboard that writes runtime data under ~/.trading-universe, polls market/news sources, and can send selected tickets or fundamentals packs to configured reasoning providers or local subscription CLIs. Avoid using Save key for API credentials unless you have verified local file permissions, and do not launch it from an untrusted wrapper or environment that could set REASONING_BASE_URL.
scripts/dashboard.mjs:572Saved reasoning-provider API keys may be stored with overly broad filesystem permissions
scripts/dashboard.mjs:648Unrestricted reasoning endpoint override can redirect API credentials and reasoning payloads
Referenced artifact was not completely inspected
· invalidation · "scan" · "any valid entries?" · "structure X" · "deep read" | `scripts/ict-levels.mjs` **only** — never web search |
Referenced artifact was not completely inspected
· invalidation · "scan" · "any valid entries?" · "structure X" · "deep read" | `scripts/ict-levels.mjs` **only** — never web search |
Referenced artifact was not completely inspected
| **DASHBOARD** | "dashboard" · "open the dashboard" | `scripts/dashboard.mjs` |
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
- **Tickets:** All / Valid / Stand-down, instrument search, live stats, best ticket and a separate feed-error box.
- **Structure:** four-timeframe heatmap with continuation scores and board read.
- **Trade log:** Pending/Open/Closed/Unfilled/Auto plus Long/Short/Wins/Losses filters, semantic search, collapsible green Active and amber History sections, asset/RR/pips/date sorting where relevant, R analytics, lessons and CSV export.
- **Alerts:** arbitrary price plus liquidity/FVG/OB edge, CE/mid and zone triggers; current price/latest 1-minute OHLC helpers; re-arm/delete/clear history; chime, toast, desktop notification and flashing title.
- **Automation controls:** Auto scan refreshes the open tab; Auto-track runs headlessly while the dashboard process is alive. Both use the selected 5/10/15/20/30/60-minute cadence. On restart, candle replay catches up fills, TP and SL events.
### Ticket and detail views
The file comments claim the dashboard 'has no AI' and only writes request files for an external agent, but the implementation later directly invokes remote LLM providers and local CLIs to fulfill reviews and fundamentals. This hidden capability changes the trust boundary: sensitive market context, user-entered API keys, and locally stored trading data can be transmitted or processed in ways an operator would not expect from the documented behavior.
The comments and API contract say the dashboard cannot compute fundamentals and only queues a request for an external agent, but the POST handler conditionally self-fulfills by calling fulfillFundamentalsAPI. This is dangerous because users or higher-level agents may rely on the documented separation when deciding what data can be exposed, while the server actually performs networked LLM-driven processing itself.
The verification request flow is documented as an external-agent handoff, yet the server directly fulfills reviews and writes verify-result.json when a provider is configured. That hidden execution path can send ticket details and fresh market evidence to third-party APIs or local CLIs without matching the advertised trust model, undermining reviewability and user consent.
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
function pruneOldAlertState(state) {
const cutoffMs = Date.now() - ALERT_STATE_MAX_AGE_MS;
const cutoffSec = Math.floor(cutoffMs / 1000); // lastEqAlert stores Yahoo (seconds) timestamps
for (const k of Object.keys(state.alerts)) if (state.alerts[k] < cutoffMs) delete state.alerts[k];
for (const k of Object.keys(state.lastEqAlert)) if (state.lastEqAlert[k] < cutoffSec) delete state.lastEqAlert[k];
}
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
function pruneOldAlertState(state) {
const cutoffMs = Date.now() - ALERT_STATE_MAX_AGE_MS;
const cutoffSec = Math.floor(cutoffMs / 1000); // lastEqAlert stores Yahoo (seconds) timestamps
for (const k of Object.keys(state.alerts)) if (state.alerts[k] < cutoffMs) delete state.alerts[k];
for (const k of Object.keys(state.lastEqAlert)) if (state.lastEqAlert[k] < cutoffSec) delete state.lastEqAlert[k];
}
The skill instructs the agent to use shell execution, network search/fetch, and local file/state interactions, but the manifest does not declare an explicit tool scope such as allowed-tools or permissions. That creates an over-privileged integration surface where a host may grant broader capabilities than the skill actually needs, increasing the blast radius if the skill is misrouted, prompt-injected, or modified later.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
2. Per watchlist asset: at most ONE asset-specific search, then score with the shared picture + that search.
3. Sort by `Net` descending. One line per asset: `🟢🟢🟢🟢⚪ Gold — Bullish 4/5 · rate-cut bets + safe-haven`.
4. Header `Market leaderboard — <date>`; footer = the mandatory as-of line.
5. **Save the board** so the dashboard can show it: write `{ asOf, context, items:[{ asset, direction, score, reason, factors:[…], flip }] }` to `<TRADE_DATA_DIR or ~/.trading-universe>/fundamentals.json`.
### Dashboard refresh requests (the "Refresh fundamentals" button)
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
2. Per watchlist asset: at most ONE asset-specific search, then score with the shared picture + that search.
3. Sort by `Net` descending. One line per asset: `🟢🟢🟢🟢⚪ Gold — Bullish 4/5 · rate-cut bets + safe-haven`.
4. Header `Market leaderboard — <date>`; footer = the mandatory as-of line.
5. **Save the board** so the dashboard can show it: write `{ asOf, context, items:[{ asset, direction, score, reason, factors:[...], flip }] }` to `<TRADE_DATA_DIR or ~/.trading-universe>/fundamentals.json`.
### Dashboard refresh requests (the "Refresh fundamentals" button)
The fallback refresh flow tells the agent to inspect fundamentals-request.json and overwrite request state with {"status":"done"}, which mutates local application state. Without a clear warning to the user that the agent will read and modify local files, this can cause unexpected state changes and weakens user awareness around persistence and automation.
The ticket review fallback reads pending review requests and writes verify-result.json, creating persistent artifacts that affect dashboard behavior. If users are not clearly warned, they may unknowingly trigger file inspection and state mutation beyond a simple chat-based review, which is risky in a tool-enabled environment.
The skill authorizes direct use of external reasoning-provider APIs, expanding the trust boundary beyond the local deterministic engine described in the skill metadata. This can cause unintended data exfiltration of trading context or locally derived artifacts to third parties, and may trigger network actions the user did not expect from this skill.
This is a session-persistence pattern: the skill directs the agent to use ~/.trading-universe/verify-request.json and verify-result.json to carry state across turns. Cross-session persistence increases risk because untrusted local state can survive boundaries between conversations and be reused as if it were trusted workflow input.
Dashboard "🔍 Review (reasoning)" button — with a reasoning provider configured in the dashboard (⚙ More → 🧠 Reasoning: NVIDIA NIM / OpenAI / OpenRouter, reasoning-capable models only), the dashboard fulfils the review itself by direct API call: a fresh single-asset engine run, then either the single-call checklist review above, or — in Collaborative Decision Review mode — a 2-round review where three specialists work the same evidence toward the best-supported decision, not against each other (Analyst: build the case to execute · Risk Analyst: surface concerns and refinements · Financial Advisor: check for a higher-expectancy alternative — round 2 re-tests it against the Analyst/Risk Analyst pair's REFINED position and requires a quantified edge, so a replacement is never just "a different ticket" · Judge: rule TAKE / MODIFY / WAIT / REPLACE / PASS with confidence, winner and per-role evidence scores; MODIFY = thesis right but levels change, REPLACE = the Advisor demonstrated a real efficiency gain over the refined original). The card's revised-ticket diff renders as labeled old→new chips, not a flat line of bare values.
Agent fallback (no provider configured): the click writes ~/.trading-universe/verify-request.json ({status:"pending", asset, ticket, nonce}) and you fulfil it. When it is pending (the user mentions the dashboard/a ticket, or at the start of a trading turn), run node ict-levels.mjs <asset>, run the checklist above over out.ohlc, and write ~/.trading-universe/verify-result.json:
{ "status":"done", "asset":"XAUUSD", "nonce":"<echo the request nonce>",
The playbook explicitly instructs the agent to read and write persistent files under ~/.trading-universe/ as part of normal operation, which exceeds a purely analytical skill and creates cross-session state. Persistent file writes can be abused to inject or retain untrusted review requests/results, influence later decisions, or clobber existing local data without clear user approval.
The instructions tell the agent to overwrite local verification files and even emphasize 'Never delete either file — overwrite,' but provide no nearby warning or consent checkpoint. Silent overwrites can destroy prior state, mask tampering history, and let stale or maliciously planted request/result files steer later behavior.
The script sends queued messages to Telegram via an external CLI, which is an outbound communication capability not clearly aligned with a supposedly local analysis/tracking skill. Even if intended for user alerts, this creates a data exfiltration path for market data, prompts, or other queued content if the queue is influenced by other components or untrusted inputs.
Invoking the external openclaw CLI to send Telegram messages introduces a non-local side effect and expands the trust boundary beyond this skill. If the external tool is misconfigured, compromised, or fed sensitive queue contents, the skill can transmit data off-host in a way users may not expect from the stated purpose.
This dashboard includes substantial LLM orchestration: provider selection, key handling, model fallback, CLI spawning, remote API calls, and prompt construction over trading data. That is broader than a local dashboard's stated purpose and increases attack surface, especially because prompts include fresh market data, fundamentals, and ticket context that may leave the machine or be exposed to external tools.
The code contains direct integration with external endpoints such as OpenAI, OpenRouter, NVIDIA, Yahoo Finance, RSS feeds, and ClawHub. External transmission is expected for these features, but it still represents a real data-exposure risk because prompts and telemetry can include trading tickets, fundamentals context, and user-entered secrets if mishandled.
],
},
openai: {
label: "OpenAI", base: "https://api.openai.com/v1",
models: ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-mini", "gpt-5.2"],
},
openrouter: {
Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.
// Awareness of auto-track additions. AUTO_SEEN persists which auto-tracked ticket
// ids have already been surfaced (so reloads don't repeat them). On the very first
// diff of a brand-new browser we seed silently, so the historical backlog doesn't
// flood the log/toasts. This path also catches HEADLESS adds (browser was closed)
// on the next reload. Verbosity follows ENGCFG.autoTrack.notify.
var AUTO_SEEN=(function(){try{return JSON.parse(localStorage.getItem('tuAutoSeen'))||{}}catch(e){return {}}})();
var AUTO_BOOTSTRAP=Object.keys(AUTO_SEEN).length===0;
The script reads a local live-trades JSON file from a path influenced by environment variables and mines its contents into 'lessons' and behavioral guidance. In an agent setting, this can expose sensitive historical trading data to downstream model outputs or other consumers without clear minimization or consent boundaries, increasing privacy and prompt-context leakage risk.
The manifest emphasizes deterministic scans using the bundled engine, a local dashboard, and trade tracking, with fresh web research only for fundamentals. This file instead continuously fetches live market data from Yahoo Finance and generates outbound alert messages via an alert queue for later delivery, which is a materially different operational behavior from the stated local/dashboard-oriented scope.
Detected: suspicious.dangerous_exec, suspicious.env_credential_access