Back to skill

Security audit

Skill Dashboard

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its dashboard purpose, but it manages installed skills through unsafe shell-command paths and weak confirmation boundaries that need review before installation.

Install only if you trust this publisher and are comfortable giving the skill dashboard authority to inspect, update, and uninstall local skills. Before broad use, the implementation should replace shell-string exec calls with argument-based execution, validate slugs at every exported entry point, bind destructive actions to explicit confirmations, constrain state files to a fixed local path, and treat remote changelog text as untrusted display data.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
dashboard.js:129
Finding
Shell Command Injection Through Unvalidated Skill Slugs## Vulnerability Details **File Location**: `dashboard.js:129-132`, `dashboard.js:269-271`, `dashboard.js:294-296`, `dev-dashboard.js:100-102`, `commands/update.js:15-17`, `commands/update.js:33-35`, and `commands/uninstall.js:18-20` **Vulnerability Type**: OS command injection through `child_process.exec` **Risk Level**: High ### Vulnerable Code `dashboard.js:129-132`: ```js async function checkUpdate(skill) { try { const output = await execCommand(`clawhub inspect ${skill.slug} --json`); const remote = JSON.parse(output); ``` `dashboard.js:269-271`: ```js async function executeUpdate(skillSlug) { try { const output = await execCommand(`clawhub update ${skillSlug}`); ``` `dashboard.js:294-296`: ```js async function executeUninstall(skillSlug) { try { await execCommand(`clawhub uninstall ${skillSlug}`); ``` `dev-dashboard.js:100-102`: ```js async function fetchClawhubData(slug) { try { const output = await execCommand(`clawhub inspect ${slug} --json`); ``` `commands/update.js:15-17`: ```js async function checkUpdate(skillSlug) { return new Promise((resolve, reject) => { exec(`clawhub inspect ${skillSlug} --json`, { encoding: 'utf8', timeout: 30000 }, (error, stdout, stderr) => { ``` `commands/update.js:33-35`: ```js async function executeUpdate(skillSlug) { return new Promise((resolve, reject) => { exec(`clawhub update ${skillSlug}`, { encoding: 'utf8', timeout: 60000 }, (error, stdout, stderr) => { ``` `commands/uninstall.js:18-20`: ```js async function executeUninstall(skillSlug, stateFile) { return new Promise((resolve, reject) => { exec(`clawhub uninstall ${skillSlug}`, { encoding: 'utf8', timeout: 60000 }, (error, stdout, stderr) => { ``` ### Technical Analysis The affected functions concatenate a skill slug into a command string passed to Node.js `child_process.exec`. This API invokes a command she ...[truncated 2004 chars]
Remediation
## Remediation Suggestions - Replace `child_process.exec` with `execFile` or `spawn` and pass each argument separately with shell processing disabled: ```js const { execFile } = require('child_process'); execFile( 'clawhub', ['inspect', skillSlug, '--json'], { encoding: 'utf8', timeout: 30000 }, callback ); ``` - Apply validation inside every exported function immediately before execution. For example: ```js function validateSkillSlug(value) { if (typeof value !== 'string' || !/^[A-Za-z0-9_-]+$/.test(value)) { throw new Error('Invalid skill slug'); } return value; } ``` - Do not rely exclusively on validation performed while parsing `clawhub list`, because direct callers can bypass that path. - Reject slugs containing whitespace, shell metacharacters, path separators, control characters, or Unicode look-alike characters. - Use a centralized command wrapper that accepts an executable and argument array rather than a complete command string. - Add tests covering separators, substitutions, redirects, newlines, and other shell metacharacters.

T09 · Insecure Skill Coding Practices

Warning
Location
dashboard.js:269
Finding
Destructive Update and Uninstall Operations Can Bypass User Confirmation## Vulnerability Details **File Location**: `dashboard.js:269-296`, `commands/update.js:32-47`, and `commands/uninstall.js:17-43` **Vulnerability Type**: Missing authorization and confirmation enforcement for destructive operations **Risk Level**: Medium ### Vulnerable Code `dashboard.js:269-296`: ```js async function executeUpdate(skillSlug) { try { const output = await execCommand(`clawhub update ${skillSlug}`); return `✅ ${skillSlug} 已更新成功!\n\n新版本已安装,可以立即使用。`; } catch (error) { return `❌ 更新失败:${error.error || error.message}`; } } async function uninstallSkill(skillSlug) { return { needsConfirm: true, message: `⚠️ 确定要卸载 ${skillSlug} 吗?\n\n卸载后:\n- 技能文件将被删除\n- 配置将丢失\n- 需要重新安装才能使用\n\n这个操作不可逆,确定要继续吗?(回复"确定"或"取消")` }; } async function executeUninstall(skillSlug) { try { await execCommand(`clawhub uninstall ${skillSlug}`); ``` `commands/update.js:32-47`: ```js async function executeUpdate(skillSlug) { return new Promise((resolve, reject) => { exec(`clawhub update ${skillSlug}`, { encoding: 'utf8', timeout: 60000 }, (error, stdout, stderr) => { if (error) { reject({ error: error.message, stderr }); } else { resolve({ success: true, message: `✅ ${skillSlug} 已更新成功!\n\n新版本已安装,可以立即使用。` }); } }); }); } ``` `commands/uninstall.js:17-43`: ```js async function executeUninstall(skillSlug, stateFile) { return new Promise((resolve, reject) => { exec(`clawhub uninstall ${skillSlug}`, { encoding: 'utf8', timeout: 60000 }, (error, stdout, stderr) => { if (error) { reject({ error: error.message, stderr }); } else { try { if (fs.existsSync(stateFile)) { const state = JSON.parse(fs.readFileSync(stateFile, 'utf8')); if (state[skillSlug]) { delete state[skillSlug]; ...[truncated 2301 chars]
Remediation
## Remediation Suggestions - Keep destructive executor functions private and expose a single operation handler that enforces confirmation. - When an operation is proposed, create a cryptographically random, short-lived, single-use confirmation token. - Bind the token to the operation type, canonical skill slug, target version where applicable, requesting session, and expiration time. - Require the executor to receive and validate that token before running any command. - Invalidate the token after one use and whenever the requested slug or target version changes. - Revalidate that the selected skill is installed and that the target operation still matches the displayed confirmation. - Record an audit event containing the requesting session, operation, target, confirmation time, and result. - Add integration tests proving that direct execution without a valid confirmation state is rejected.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
commands/toggle.js:17
Finding
Caller-Controlled State File Path Allows Unauthorized JSON File Modification## Vulnerability Details **File Location**: `commands/toggle.js:17-36` and `commands/uninstall.js:17-36` **Vulnerability Type**: Arbitrary file read and rewrite through an unrestricted path argument **Risk Level**: Medium ### Vulnerable Code `commands/toggle.js:17-36`: ```js async function toggleSkill(skillSlug, enable, stateFile) { try { let state = {}; if (fs.existsSync(stateFile)) { state = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } if (!state[skillSlug]) { state[skillSlug] = {}; } state[skillSlug].enabled = enable; state[skillSlug].lastUsed = new Date().toISOString(); state[skillSlug].lastModified = new Date().toISOString(); fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8'); ``` `commands/uninstall.js:17-36`: ```js async function executeUninstall(skillSlug, stateFile) { return new Promise((resolve, reject) => { exec(`clawhub uninstall ${skillSlug}`, { encoding: 'utf8', timeout: 60000 }, (error, stdout, stderr) => { if (error) { reject({ error: error.message, stderr }); } else { try { if (fs.existsSync(stateFile)) { const state = JSON.parse(fs.readFileSync(stateFile, 'utf8')); if (state[skillSlug]) { delete state[skillSlug]; fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8'); } } } catch (e) { console.error('Failed to clean the state cache:', e.message); } ``` ### Technical Analysis Both exported functions accept `stateFile` from the caller and use it directly in filesystem operations. The path is not constructed internally, canonicalized, restricted to the project directory, or checked for symbolic links. If the selected file contains valid JSON and is writable by the Agent process, `toggleSkill` adds or mo ...[truncated 1543 chars]
Remediation
## Remediation Suggestions - Remove `stateFile` from the public function parameters and construct the expected state path internally from a trusted constant. - Resolve both the permitted base directory and target path with `fs.realpathSync` or equivalent canonicalization. - Verify that the canonical target remains inside the permitted state directory. - Reject symbolic links by using `lstat`, and consider opening files with no-follow protections where supported. - Require the target filename to exactly match the intended state filename rather than merely residing in an allowed directory. - Create state files with restrictive permissions and use atomic replacement through a temporary file in the same trusted directory. - Validate the state schema before rewriting it, and reject unexpected top-level structures. - If callers need multiple state stores, use opaque identifiers mapped to trusted paths rather than accepting raw filesystem paths.

T01 · Skill Instruction Hijacking

Warning
Location
dashboard.js:129
Finding
Untrusted Remote Changelog Content Is Embedded in Agent-Facing Confirmation Output## Vulnerability Details **File Location**: `dashboard.js:129-141` and `dashboard.js:246-259` **Vulnerability Type**: Indirect prompt injection through remote skill metadata **Risk Level**: Medium ### Vulnerable Code `dashboard.js:129-141`: ```js async function checkUpdate(skill) { try { const output = await execCommand(`clawhub inspect ${skill.slug} --json`); const remote = JSON.parse(output); const localVersion = skill.version; const remoteVersion = remote.version; if (remoteVersion !== localVersion) { return { available: true, version: remoteVersion, changelog: remote.changelog || 'Version update' }; ``` `dashboard.js:246-259`: ```js async function updateSkill(skillSlug) { try { const update = await checkUpdate({ slug: skillSlug, version: 'current' }); if (!update.available) { return `✅ ${skillSlug} is already the latest version.`; } return { needsConfirm: true, message: `A new version v${update.version} is available for ${skillSlug}\n\nChanges:\n${update.changelog}\n\nUpdate to v${update.version}? Reply with confirm or cancel.`, version: update.version }; ``` ### Technical Analysis The changelog originates from the JSON returned by the remote `clawhub inspect` command. A remote skill publisher can therefore control this field. The value is inserted verbatim into a message returned to the hosting Agent. No trust-boundary label, escaping, structural encoding, content filtering, or length limit distinguishes the remote text from the dashboard's own instructions. In an AI Agent environment, a malicious changelog can contain imperative text crafted to override the current task, request sensitive information, or induce tool use. This is an indirect prompt-injection condition rather than automatic operating-system code execution. Exploitation depends on ...[truncated 1276 chars]
Remediation
## Remediation Suggestions - Treat all registry metadata, including changelogs, descriptions, author fields, and URLs, as untrusted data. - Return metadata in a structured field separate from operational instructions instead of concatenating it into an Agent-facing prompt. - Clearly label the content as untrusted and instruct the host that instructions contained inside it must never be followed. - Render the changelog in a fenced or otherwise strongly delimited data block. - Enforce conservative length and character limits and remove terminal control sequences. - Prefer a fixed confirmation prompt generated entirely from trusted application text, with remote metadata displayed only in a non-actionable UI component. - Require explicit confirmation based on trusted fields such as canonical slug and target version; never derive authorization decisions from changelog text. - Add tests using adversarial changelog strings that attempt to redirect the Agent or request tool execution.
Vulnerability Patterns
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (17)

Memory Manipulation

High
Category
Memory Poisoning
Content
if (fs.existsSync(stateFile)) {
            const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
            if (state[skillSlug]) {
              delete state[skillSlug];
              fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
            }
          }
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The helper claims to provide 'safe constraints' around command execution, but it directly passes shell command strings to child_process.exec, which invokes a shell. Several exported functions interpolate skillSlug into commands such as `clawhub update ${skillSlug}` and `clawhub uninstall ${skillSlug}`, so a crafted slug or unexpected upstream input could trigger shell metacharacter injection and arbitrary command execution.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger list includes broad natural-language phrases such as “开发者模式” and “一键巡查” that can plausibly appear in ordinary conversation, increasing the chance the skill activates unintentionally. In a dashboard skill with management capabilities, accidental activation can expose installed-skill information or lead users further into sensitive actions, even if destructive steps still require confirmation.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This JavaScript file contains natural-language comments and a returned user-facing message entirely in Chinese, with no indication that the skill is region-specific or that users can opt into another language. Per the policy, forcing a specific language without user choice is a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This code's user-facing confirmation message and descriptive comments are written only in Chinese, including the required response values "确定" or "取消". That creates a language/locale restriction without offering the user a choice or documenting a justified region-specific scope.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The file claims to handle updates with secondary confirmation, but executeUpdate() directly runs `clawhub update ${skillSlug}` and does not itself verify any confirmation state. If a caller invokes this function without a separate trusted confirmation gate, updates can occur immediately, creating a mismatch between documented safety behavior and actual enforcement.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The confirmation prompt requires the user to reply with the Chinese words "确定" or "取消", which imposes a specific language on all users. This is a natural-language policy concern because the file does not offer any language choice or document a justified locale restriction.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s user-facing description and interaction model are written exclusively in Chinese, including the stated behavior and UI metaphors. Because the skill does not offer any language or locale opt-in/selection, it imposes a specific language on users, which matches the language-policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The prompt instructs the user to reply with the Chinese words "是" or "不用", which constrains interaction to a single language without opt-in. This is a natural-language policy issue because users are not given an alternative language or a documented locale-specific justification.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The update confirmation message requires the user to answer with "确定" or "取消", again forcing a specific language for a control-flow decision. The file does not provide language choice or indicate that this is a justified region-specific constraint.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The uninstall confirmation requires Chinese input ("确定" or "取消") for an irreversible operation. Since no language choice or locale justification is provided, this constitutes a language-policy violation in user-facing natural language.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The function and UI text describe toggling a skill's status, implying actual management of installed skills. In reality, the code only writes an 'enabled' flag to skill-state.json and returns success, without invoking any underlying tool or mechanism to change the skill's real activation state.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s natural-language description and runtime output indicate the skill operates in Chinese, and later code formats timestamps with the fixed locale `zh-CN`. This imposes a specific language/locale on users without offering a choice or documenting a region-specific need, which matches the language/locale policy violation category.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module header describes only pagination logic, page navigation, page-number calculation, and user input parsing for paging. However, parseUserInput also recognizes operational commands like update, uninstall, disable, enable, detail, and opening ClawHub, which are materially different from pagination and make the documentation misleading about the module's intent.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
This file contains natural-language comments and user-facing text exclusively in Chinese, including operational prompts shown to users. Under the policy, forcing a specific language without offering a user choice is a locale/language policy violation unless clearly justified.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The parser hard-codes Chinese and some English trigger words for navigation and actions such as update, uninstall, disable, and enable. This constrains use to specific languages without any explicit user preference mechanism or documented justification.

Intent-Code Divergence

Low
Confidence
78% confidence
Finding
The confirmation message says uninstalling will delete skill files and cause configuration loss. In this file, the implemented behavior is to invoke `clawhub uninstall` and remove an entry from a state cache file; there is no direct code here that deletes skill files or clears all configuration. That makes the inline user-facing documentation stronger than what this code demonstrates.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
commands/uninstall.js:21

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
commands/update.js:19

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
dashboard.js:57

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
dev-dashboard.js:28