Back to skill

Security audit

Aicoin Market

Security checks for vulnerabilities and agentic risk

Overview

This AiCoin market-data skill is mostly coherent, but it needs Review because it stores API secrets in plaintext, reads broad .env files, and exposes broader account-analytics endpoints than its market-data framing suggests.

Review before installing. Prefer providing AiCoin credentials through the host UI or a scoped secret mechanism rather than the set-key command, and avoid storing secrets in shared workspace .env files. Be aware that this skill can call broad AiCoin endpoints, including Hyperliquid address and trader analytics, not only simple public price lookups. I found no evidence of trading, destructive actions, background persistence, or intentional exfiltration.

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/aicoin.mjs:129
Finding
API credentials exposed through command-line arguments<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:80-85`; `scripts/aicoin.mjs:129-149` **Vulnerability Type**: Credential exposure through process arguments and shell history **Risk Level**: Medium ### Vulnerable Code ```bash node scripts/aicoin.mjs set-key <key_id> <secret> node scripts/aicoin.mjs set-key '{"api_key":"<id>","access_key":"<secret>"}' ``` ```js const raw = rest.join(' ').trim(); if (raw.startsWith('{')) { try { const j = JSON.parse(raw); id = j.access_key_id || j.accessKeyId || j.key_id || j.api_key || j.key; secret = j.access_secret || j.accessSecret || j.secret_key || j.secret || j.access_key; } catch { return out({ ok: false, error: { code: 'bad_json', message: 'Invalid JSON argument' } }); } } else if (rest.length >= 2) { id = rest[0]; secret = rest[1]; } const r = await saveKey(id, secret); ``` ### Technical Analysis The documented `set-key` workflow requires users to place the AiCoin secret directly in a command-line argument. The implementation then reads that secret from `process.argv`. Command-line secrets can be exposed through: - Interactive shell history files. - Process inspection utilities while the command is running. - Process accounting and endpoint monitoring systems. - Terminal session recording. - Diagnostic logs that capture invoked commands. - Automation logs that echo command lines. The JSON input alternative has the same weakness because the entire JSON object, including the secret, remains part of the process argument list. This behavior is not required for the Skill's market-data functionality. Credentials can instead be supplied through masked interactive input, standard input, or a credential-management facility. ### Attack Path 1. A user follows the documented `set-key` example and enters the API key ID and secret in a terminal. 2. The shell records the command in its history, or the operating system exposes the arguments through process inspection. 3. A local use ...[truncated 845 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove secret-bearing command-line examples from `SKILL.md`. 2. Accept the secret through a masked interactive prompt using a terminal input mechanism that disables echo. 3. For non-interactive use, accept the secret through standard input or a file descriptor rather than `process.argv`. 4. Prefer integration with an operating-system credential store or the host agent's secret-management interface. 5. Ensure errors and debug logs never include the supplied secret or raw input object. 6. Clearly warn users to rotate any credential previously entered on the command line and to remove affected shell-history entries. 7. If command-line compatibility must temporarily remain, display a security warning and deprecate the interface. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
lib/client.mjs:134
Finding
API secrets are persisted without explicit restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `lib/client.mjs:134-150` **Vulnerability Type**: Insecure local credential storage **Risk Level**: Medium ### Vulnerable Code ```js export async function saveKey(keyId, secret) { const headers = authHeaders(keyId, secret); const res = await fetch(`${BASE}/api/v3/coins/tickers?coin_key=bitcoin`, { headers, signal: AbortSignal.timeout(15000) }); if (res.status === 401 || res.status === 403) return { ok: false, error: `key validation failed (HTTP ${res.status})` }; if (!res.ok) return { ok: false, error: `validation request failed (HTTP ${res.status})` }; const target = ENV_FILES.find(existsSync) || ENV_FILES[0]; let lines = existsSync(target) ? readFileSync(target, 'utf-8').split('\n') : []; const set = (k, v) => { const i = lines.findIndex(l => l.trim().startsWith(k + '=')); if (i >= 0) lines[i] = `${k}=${v}`; else lines.push(`${k}=${v}`); }; set('AICOIN_ACCESS_KEY_ID', keyId); set('AICOIN_ACCESS_SECRET', secret); writeFileSync(target, lines.join('\n')); return { ok: true, file: target }; } ``` ### Technical Analysis The `saveKey` function stores the API secret in plaintext in a `.env` file. The call to `writeFileSync` does not specify a restrictive permission mode and does not correct the permissions of an existing file. For a newly created file, effective permissions depend on the process umask. On a permissively configured system, the file may become readable by other local users. If an existing `.env` file is already group-readable or world-readable, overwriting it does not remove those permissions. Plaintext `.env` storage also increases the chance that credentials will be copied into backups, workspace archives, support bundles, or source-control commits. ### Attack Path 1. A user invokes `set-key`. 2. The Skill validates the credential and writes `AICOIN_ACCESS_SECRET` into one of the selected `.env` paths. 3. The file is created under a permissive umask or an e ...[truncated 895 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create new credential files with owner-only permissions: ```js writeFileSync(target, lines.join('\n'), { mode: 0o600 }); ``` 2. For existing files, inspect and explicitly set permissions to `0600` after validating ownership. 3. Refuse to write credentials through symbolic links and verify that the target is a regular file owned by the current user. 4. Use atomic file replacement to avoid partial writes while preserving restrictive permissions. 5. Prefer an operating-system credential store, agent secret store, or encrypted keychain over plaintext `.env` storage. 6. Add `.env` to `.gitignore` and exclude it from archives, diagnostics, and support bundles. 7. Avoid returning unnecessary absolute credential-file paths if those paths may later be logged. 8. Document credential rotation procedures for users who discover that their `.env` permissions were too broad. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
lib/defaults.json:1
Finding
Reusable shared API credential is hardcoded in the distributed package<![CDATA[ ## Vulnerability Details **File Location**: `lib/defaults.json:1-5` **Vulnerability Type**: Hardcoded shared credential **Risk Level**: Medium ### Vulnerable Code ```json { "comment": "Public free-tier AiCoin API key. IP rate-limited. Users can replace with their own key via env vars.", "accessKeyId": "ronJ8uI0Yj2soAfGVs5H1YALUIINbE22", "accessSecret": "CWHZcH2us1CLSE7grroR1TpS0Z1JxTwU" } ``` The credential is used as a fallback in `lib/client.mjs:33-36`: ```js const defaults = JSON.parse(readFileSync(resolve(__dirname, 'defaults.json'), 'utf-8')); export const KEY = process.env.AICOIN_ACCESS_KEY_ID || defaults.accessKeyId; const SECRET = process.env.AICOIN_ACCESS_SECRET || defaults.accessSecret; ``` ### Technical Analysis The package distributes a reusable access key ID and secret to every recipient. Although the file describes the credential as a public, IP-rate-limited free-tier key, it is still used as authentication material for signed requests. Any person who obtains the package can extract the credential and reproduce valid request signatures independently of the Skill. A shared credential cannot provide meaningful client authentication because all users and attackers possess the same secret. It also prevents reliable attribution of abusive requests. This creates operational security risks even if the credential intentionally has limited privileges. Public distribution can lead to automated scraping, rate-limit exhaustion, abuse reports, or revocation that affects every legitimate installation. ### Attack Path 1. An attacker downloads or otherwise obtains the Skill package. 2. The attacker reads `lib/defaults.json` and extracts the shared key ID and secret. 3. The attacker implements the documented HMAC signing process or reuses `lib/client.mjs`. 4. The attacker sends requests directly to the AiCoin API using the shared credential. 5. The attacker consumes the shared quota, triggers service-side abuse controls, or causes the cr ...[truncated 731 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Revoke and rotate the exposed shared credential. 2. Remove reusable secrets from the distributed package and repository history. 3. Require per-user API credentials, or provide a genuinely anonymous free-tier API mechanism that does not rely on a shared secret. 4. If frictionless access is required, use a server-issued short-lived token with strict audience, scope, expiration, and rate limits instead of a permanent package secret. 5. Maintain conservative server-side permissions and quotas for bootstrap credentials during migration. 6. Add automated secret scanning to the release process to prevent future credential commits. 7. Ensure tests use mock values or credentials injected by the test environment rather than live reusable secrets. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (32)

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/aicoin.mjs <接口> '<JSON 参数>'
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));

// ── .env auto-load (OpenClaw exec may not inject env into child processes) ──
const ENV_FILES = [
  resolve(process.cwd(), '.env'),
  resolve(process.env.HOME || '', '.openclaw', 'workspace', '.env'),
Confidence
90% confidence
Finding
The declaration and subsequent use of `ENV_FILES` begins a workflow that searches common workspace and home-directory `.env` files for secrets. In a market-data skill, this credential discovery behavior is broader than necessary and increases the chance that unrelated credentials are exposed to the skill runtime.

Credential Access

High
Category
Privilege Escalation
Content
// ── .env auto-load (OpenClaw exec may not inject env into child processes) ──
const ENV_FILES = [
  resolve(process.cwd(), '.env'),
  resolve(process.env.HOME || '', '.openclaw', 'workspace', '.env'),
  resolve(process.env.HOME || '', '.openclaw', '.env'),
];
Confidence
91% confidence
Finding
Including `~/.openclaw/workspace/.env` in the search path expands access into a shared or persistent workspace-level secret store. That makes the skill capable of pulling in credentials unrelated to this API client, which is unnecessary for basic market-data retrieval and enlarges the blast radius if abused.

Credential Access

High
Category
Privilege Escalation
Content
// ── .env auto-load (OpenClaw exec may not inject env into child processes) ──
const ENV_FILES = [
  resolve(process.cwd(), '.env'),
  resolve(process.env.HOME || '', '.openclaw', 'workspace', '.env'),
  resolve(process.env.HOME || '', '.openclaw', '.env'),
];
for (const file of ENV_FILES) {
Confidence
91% confidence
Finding
Including `~/.openclaw/.env` as another fallback location further broadens the set of secrets the process can ingest. This creates unnecessary credential access capability in a skill whose stated purpose is API-backed market data lookup, not local secret management.

Credential Access

High
Category
Privilege Escalation
Content
const ENV_FILES = [
  resolve(process.cwd(), '.env'),
  resolve(process.env.HOME || '', '.openclaw', 'workspace', '.env'),
  resolve(process.env.HOME || '', '.openclaw', '.env'),
];
for (const file of ENV_FILES) {
  if (!existsSync(file)) continue;
Confidence
88% confidence
Finding
The loop over `ENV_FILES` operationalizes credential harvesting by reading any existing candidate file. Even without exfiltration shown in this file, importing secrets into process memory is credential access capability and increases risk if downstream code, logs, or dependencies are compromised.

Credential Access

High
Category
Privilege Escalation
Content
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
      if (!process.env[k]) process.env[k] = v;
    }
  } catch { /* ignore unreadable .env */ }
}

const defaults = JSON.parse(readFileSync(resolve(__dirname, 'defaults.json'), 'utf-8'));
Confidence
89% confidence
Finding
Assigning discovered `.env` values into `process.env` makes all loaded secrets broadly available to the entire Node.js process and imported modules. That is dangerous because unrelated secrets become accessible to any later code path, expanding impact well beyond the AiCoin credentials the skill actually needs.

Credential Access

High
Category
Privilege Escalation
Content
return hit ? { method: hit.method, spec: hit } : null;
}

// Persist a new key pair to the workspace .env (validates before writing).
export async function saveKey(keyId, secret) {
  const headers = authHeaders(keyId, secret);
  const res = await fetch(`${BASE}/api/v3/coins/tickers?coin_key=bitcoin`, { headers, signal: AbortSignal.timeout(15000) });
Confidence
95% confidence
Finding
The `saveKey` function handles raw API credentials, validates them remotely, and then stores them in plaintext `.env` files. This is a direct credential handling and persistence path that can expose secrets through filesystem access, backups, repo accidents, or other workspace tooling.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest explicitly says Hyperliquid whale/trader analytics should be handled by a different skill, yet this file exposes extensive Hyperliquid trader, whale, account, fills, PnL, portfolio, and surveillance-style endpoints. That mismatch weakens intended skill isolation and can cause a general market-data skill to access sensitive account-level analytics outside its declared scope, increasing the risk of unauthorized profiling or misuse.

Credential Access

High
Category
Privilege Escalation
Content
//   node scripts/aicoin.mjs <endpoint> ['<json params>']   call any v3 endpoint
//   node scripts/aicoin.mjs catalog [group|endpoint]        list endpoints (the live API menu)
//   node scripts/aicoin.mjs key                             show API key status + access probe
//   node scripts/aicoin.mjs set-key <id> <secret>           validate & save a new key to .env
//
// Endpoint = the path after /api/v3/ , e.g.  market/ticker  ,  hyperliquid/whales/open-positions
// Every call prints the v3 envelope {ok, data, error, meta}. Check `ok` first.
Confidence
91% confidence
Finding
The skill includes credential access and storage behavior centered on `.env`, plus a `key` command that reveals key status and partial identifier and probes endpoint permissions. In a skill whose declared function is data retrieval, secret-handling logic materially increases the risk of credential exposure, misuse, and privilege discovery if invoked by an untrusted or over-broadly authorized agent workflow.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill invokes a Node script that uses environment variables and makes external API calls, but it does not declare an explicit tool scope such as allowed tools or permissions. In an agent environment, this weakens least-privilege controls and can allow broader-than-necessary access to env/network capabilities, increasing the blast radius if the skill is modified, misrouted, or abused.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger list is extremely broad, including generic phrases like 'check price', 'trending', 'Twitter', and 'project analysis', which can cause the skill to activate for ordinary requests beyond narrowly scoped crypto-market lookups. Over-broad routing increases the chance that the agent unnecessarily executes code and contacts external services, exposing metadata or consuming privileged capabilities when a simpler or safer handling path would suffice.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The code enumerates and reads multiple `.env` files from the current workspace and the user's home-managed OpenClaw directories, loading any discovered values into `process.env`. This gives the skill access to unrelated secrets beyond what is necessary for fetching market data, increasing the risk of credential exposure or misuse if the skill or its dependencies are compromised.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill includes a `saveKey` function that validates user-supplied API credentials and then persists them into a local `.env` file. For a market-data skill, silently storing secrets on disk broadens the trust boundary, creates long-lived credential exposure, and can leak secrets to other tools, logs, backups, or workspace sharing mechanisms.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
`saveKey` modifies the user's environment configuration by writing `AICOIN_ACCESS_KEY_ID` and `AICOIN_ACCESS_SECRET` to a discovered `.env` file without any visible warning, prompt, or confirmation in this code path. Silent persistence of credentials is dangerous because users may not realize secrets have been stored in plaintext on disk where other processes or future sessions can access them.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The skill includes numerous account-level endpoints for trader behavior, portfolio history, positions, fills, funding, tags, and batch address analysis, which goes beyond typical public market-data retrieval. In the context of a general crypto market-data skill, these capabilities enable user or third-party wallet surveillance and behavioral profiling that are not clearly justified by the advertised purpose.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The endpoint documentation presents Hyperliquid trader and whale features as available even though the manifest directs those requests to another skill. This inconsistency can misroute agent behavior, bypass policy segmentation, and undermine operator expectations about what data this skill can access.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The natural-language description explicitly states "默认 cn" for the locale, which imposes a specific language by default rather than presenting language selection as user-driven. This matches the policy concern for forced language/locale behavior without explicit opt-in.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
This endpoint description says the language defaults to cn, which indicates a forced locale choice in the natural-language contract. Because no user opt-in or preference-based selection is documented here, it is a locale policy violation under the stated rules.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
lib/client.mjs:14