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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Confidence
80% confidence
Finding

Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Content

Scanner excerpt · commands/uninstall.js (reported line 30)May include surrounding context.

js
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');
            }
          }

Intent-Code Divergence

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.