Back to skill

Security audit

Aicoin Hyperliquid

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a legitimate AiCoin analytics skill, but it needs review because it broadly handles local API secrets and can call much more of the AiCoin API than its Hyperliquid framing suggests.

Install only if you are comfortable with a networked AiCoin API tool that may read existing .env files, save an AiCoin API secret in plaintext, and call non-Hyperliquid AiCoin endpoints. Use a dedicated low-privilege AiCoin key, keep unrelated secrets out of the working and OpenClaw .env files, and verify AICOIN_BASE_URL points only to the official AiCoin service. I found no evidence of wallet private-key access, trading authority, destructive actions, or hidden persistence.

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
lib/client.mjs:39
Finding
Authentication Requests Can Be Redirected to an Arbitrary Network Destination<![CDATA[ ## Vulnerability Details **File Location**: `lib/client.mjs:39-41, 47-57, 86-103, 131-132` **Vulnerability Type**: Unrestricted authentication endpoint configuration **Risk Level**: Medium ### Complete Code Snippet ```js const defaults = JSON.parse(readFileSync(resolve(__dirname, 'defaults.json'), 'utf-8')); export const BASE = process.env.AICOIN_BASE_URL || 'https://open.aicoin.com'; export const KEY = process.env.AICOIN_ACCESS_KEY_ID || defaults.accessKeyId; const SECRET = process.env.AICOIN_ACCESS_SECRET || defaults.accessSecret; export const USING_OWN_KEY = !!(process.env.AICOIN_ACCESS_KEY_ID && process.env.AICOIN_ACCESS_SECRET); // HMAC-SHA1(signStr, secret) → hex → base64. The 4 values ride in X-Aic-* headers. function authHeaders(keyId = KEY, secret = SECRET) { const nonce = randomBytes(8).toString('hex'); const ts = Math.floor(Date.now() / 1000).toString(); const signStr = `AccessKeyId=${keyId}&SignatureNonce=${nonce}&Timestamp=${ts}`; const hex = createHmac('sha1', secret).update(signStr).digest('hex'); return { 'X-Aic-AccessKey-Id': keyId, 'X-Aic-Signature-Nonce': nonce, 'X-Aic-Timestamp': ts, 'X-Aic-Signature': Buffer.from(hex).toString('base64'), }; } // Core request. Returns { httpStatus, body }; body is the parsed envelope. export async function request(method, path, params = {}) { const full = normalizePath(path); const m = (method || 'GET').toUpperCase(); const headers = authHeaders(); let url = `${BASE}${full}`; const init = { method: m, headers, signal: AbortSignal.timeout(30000) }; if (m === 'GET' || m === 'DELETE') { const qs = new URLSearchParams(); for (const [k, v] of Object.entries(params || {})) { if (v === undefined || v === null || v === '') continue; qs.set(k, Array.isArray(v) ? v.join(',') : String(v)); } const s = qs.toString(); if (s) url += `?${s}`; } else { headers['Content-Type'] = 'application/json'; init.body = JSON.stringify(para ...[truncated 2471 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the API origin to `https://open.aicoin.com` unless alternate origins are an explicit functional requirement. 2. If configurability is required, parse the value with `new URL()` and enforce: - The `https:` protocol. - An explicit allowlist of trusted hostnames. - No embedded username or password. - An expected port. 3. Reject malformed URLs and fail closed rather than silently using an untrusted destination. 4. Review redirect handling and ensure authentication headers are never forwarded to a different origin. 5. Separate test configuration from production configuration so test endpoints cannot be enabled through an ordinary workspace `.env`. 6. Document the exact network destination and data sent by the Skill. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
lib/client.mjs:130
Finding
API Secrets Are Accepted in Process Arguments and Written to Plaintext Files Without Explicit Permission Hardening<![CDATA[ ## Vulnerability Details **File Location**: `scripts/aicoin.mjs:131-147`; `lib/client.mjs:130-149`; `SKILL.md:86-88` **Vulnerability Type**: Insecure local credential handling **Risk Level**: Medium ### Complete Code Snippet The command wrapper accepts a secret directly from process arguments: ```js if (cmd === 'set-key') { let id, secret; const raw = rest.join(' ').trim(); if (raw.startsWith('{')) { // JSON 模式:兼容 AiCoin 后台直接拷下来的 {"api_key","access_key"} // 注意 AiCoin 后台命名反直觉 —— `api_key` 是公开 ID,`access_key` 才是 SECRET。 // 也兼容 {"access_key_id","access_secret"} 等更直白的命名。 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: '参数不是合法 JSON' } }); } } else if (rest.length >= 2) { id = rest[0]; secret = rest[1]; } if (!id || !secret) { return out({ ok: false, error: { code: 'bad_args', message: "用法: set-key <key_id> <secret> 或 set-key '<json>'(JSON 字段名兼容 api_key/access_key、access_key_id/access_secret 等;AiCoin 后台 api_key 是 ID、access_key 是 SECRET)" } }); } const r = await saveKey(id, secret); return out(r.ok ? { ok: true, message: `key 已保存到 ${r.file}` } : { ok: false, error: { code: 'invalid_key', message: r.error } }); } ``` The credential is then stored in a plaintext `.env` file: ```js // 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) }); if (res.status === 401 || res.status === 403) return { ok: false, error: `key 验证失败 (HTTP ${res.status})` }; if (!res.ok) return { ok: false, error ...[truncated 2571 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for passing secrets directly through command-line arguments. 2. Read the secret from hidden interactive input, standard input, a protected file descriptor, or the platform credential store. 3. Avoid including secrets in shell examples that are likely to be retained in command history. 4. Store credentials in a Skill-specific file rather than selecting an arbitrary existing OpenClaw `.env`. 5. Create credential files atomically with mode `0o600`, and verify ownership and permissions before updating existing files. 6. Reject symbolic-link targets and validate the resolved destination before writing. 7. Consider using the operating system keychain or an OpenClaw-managed secret store instead of plaintext `.env` storage. 8. If `.env` support must remain, warn users that the file contains plaintext credentials and provide explicit permission-hardening instructions. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
lib/defaults.json:1
Finding
Reusable Shared API Credential Is Embedded in the Distributed Source<![CDATA[ ## Vulnerability Details **File Location**: `lib/defaults.json:1-5` **Vulnerability Type**: Hardcoded shared credential **Risk Level**: Low ### Complete Code Snippet ```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" } ``` ### Technical Analysis The package includes both an access-key identifier and its corresponding secret. Although the file labels the credential as a public, IP-rate-limited, free-tier key, it is still a reusable authentication credential distributed to every recipient of the Skill. A secret embedded in distributed source cannot provide meaningful confidentiality. Anyone who can download or inspect the package can extract and reuse it independently of the Skill. The credential appears intended to provide default market-data access and therefore supports the declared functionality. However, embedding a shared secret is an insecure design because abuse by one party can affect availability for every user relying on the same credential. ### Attack Path 1. An attacker downloads or otherwise obtains the Skill package. 2. The attacker opens `lib/defaults.json`. 3. The attacker extracts the access-key identifier and access secret. 4. The attacker sends authenticated requests to the AiCoin API using the shared credential. 5. The attacker consumes available quota or triggers service-side rate limits, affecting other Skill users. ### Impact Assessment The primary impact is abuse of the shared free-tier account, quota exhaustion, rate limiting, and possible revocation of the bundled credential. This can cause partial or complete loss of default API functionality for all users of the Skill. Because the credential is described as public, free-tier, and IP-rate-limited, the direct confidentiality impact is low. No user-specific AiCoin secret, wallet private ...[truncated 66 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the shared access secret from the distributed package. 2. Require users to configure their own AiCoin credentials through a protected secret-management mechanism. 3. If anonymous or free access is required, expose it through a service-managed public endpoint rather than distributing a reusable secret. 4. Scope any replacement token to read-only endpoints and the minimum required dataset. 5. Apply per-user or per-installation rate limits so abuse does not affect all Skill users. 6. Ensure the credential can be rotated and revoked without publishing a new package version. ]]>
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (40)

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
88% confidence
Finding
This code explicitly implements .env discovery and secret ingestion from the working directory and user-home OpenClaw paths, giving the skill access to locally stored credentials beyond its immediate runtime inputs. In an agent ecosystem, that materially increases the blast radius because the skill can read secrets that belong to other tools or sessions.

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
86% confidence
Finding
Including the workspace .env path in the search list causes the skill to ingest credentials from a shared execution area, not just from its own dedicated configuration. That increases the chance of cross-skill secret exposure and unintended credential reuse in a multi-tool environment.

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
86% confidence
Finding
Searching the user's home OpenClaw .env further broadens credential access beyond the local project boundary. This makes the behavior more dangerous in context because an analytics skill can pull in account-level secrets from a global agent environment that the user did not intend this skill to read.

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
83% confidence
Finding
The loop over multiple candidate .env files operationalizes broad secret harvesting rather than a single explicit configuration source. In the context of a read-only analytics skill, this is an unnecessary elevation in credential access capability and increases exposure if the process, logs, or dependent modules 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
80% confidence
Finding
Assigning discovered .env values into process.env makes all subsequently loaded code in the same process able to access those secrets, not just this client module. That propagation can unintentionally widen internal exposure and facilitate leakage through unrelated libraries or debugging output.

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 not only accesses credentials but persists them to a discovered .env file after a network validation step. This creates long-lived secrets on disk in potentially shared or poorly protected locations, making theft, accidental commit, and cross-tool exposure substantially more likely.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The endpoint catalog grants access to a very broad authenticated API surface far beyond the declared Hyperliquid whale/perps analytics scope, including content, airdrops, equities, macro, signals, and treasury data. In an agent-skill setting, this violates least privilege and creates scope-confusion risk: prompts intended for Hyperliquid analytics could be steered into unrelated data access paths, increasing chances of unauthorized or privacy-invasive retrieval.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The CLI explicitly allows calling any AiCoin v3 endpoint, which materially exceeds the declared Hyperliquid analytics scope. In an agent-skill context, this breaks least-privilege and can enable unintended access to unrelated data domains or premium API surfaces the user did not intend to expose.

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
96% confidence
Finding
The script includes direct credential handling and persistence to `.env`, which is a genuine credential-access surface. In an agent skill, accepting and storing secrets is especially sensitive because it can normalize secret submission and create durable local exposure if the workspace is later leaked or misconfigured.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
A read-oriented analytics skill includes a credential capture and persistence path via `set-key`, storing secrets to `.env`. Secret collection and local persistence are high-risk capabilities, especially when unnecessary for the core advertised use, and increase the blast radius if the environment is exposed.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares executable behavior that uses environment variables and network access, but it does not constrain or disclose tool scope via an explicit permissions or allowed-tools policy. In an agent setting, that increases the risk of overbroad execution and accidental exposure of secrets or unexpected outbound requests, especially because the skill also documents credential handling and live API interaction.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The trigger description is overly broad and includes generic 'on-chain whale' and 'smart money' phrases that extend beyond Hyperliquid-specific analytics. In a multi-skill agent, this can cause misrouting, leading the agent to select a skill with broader networked capabilities and credential handling for queries that should go to a narrower or different tool, increasing the chance of unnecessary data access or unsafe execution.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest markets this as a Hyperliquid analytics skill, but the documented command accepts an arbitrary API path after `/api/v3/`, which expands capability beyond the stated scope. That scope mismatch is dangerous because an agent may invoke unrelated endpoints, including ones with different data sensitivity or side effects, under the assumption that the skill is narrowly read-only and domain-limited.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
A read-only analytics skill includes a `set-key` feature that writes credentials into a local `.env`, which exceeds the minimum capability needed for data retrieval. This is risky because it encourages secret ingestion and persistence inside the agent environment, creating opportunities for credential mishandling, unintended reuse by other tools, or leakage through logs, workspace access, or later commands.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The skill proactively scans multiple .env locations and imports secrets into process.env, even though the declared use case is passive analytics. This unjustified credential-management behavior broadens access to unrelated secrets in the execution environment and can enable accidental reuse or downstream leakage if other code paths expose environment variables or write them back to disk.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The client includes a function that validates and then persists API credentials into a local .env file, creating a durable secret at rest in the workspace. For a skill described as read-only analytics that reportedly does not require wallet keys, this expands the secret-handling surface unnecessarily and increases the chance of later disclosure through logs, backups, repo commits, or other tools reading the same workspace files.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
saveKey updates or creates .env entries without any confirmation, preview, or warning to the user, so invoking this path silently changes persistent authentication state. This can overwrite valid credentials, plant attacker-supplied keys, or leave secrets stored in a location the user did not intend.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The returned error strings are hard-coded in Chinese ("key 验证失败" and "验证请求失败") with no indication that language is configurable or region-specific. This imposes a specific language on users without opt-in, which violates the stated language/locale policy.

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