Back to skill

Security audit

Workspace Anchor

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent workspace-boundary purpose, but its broad filesystem discovery and unsafe shell command construction create review-level risk before installation.

Review this skill carefully before installing. Its goal is reasonable, but it should be constrained to explicit workspace roots and changed to avoid shell interpolation before being trusted in an agent workflow, especially where project paths or lock files may come from untrusted repositories.

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

Error
Location
lib/validate.js:146
Finding

OS Command Injection Through User-Controlled Validation Path

Content
View full analysis

Vulnerability Details

File Location: lib/validate.js:146-149
Vulnerability Type: OS command injection
Risk Level: High

Vulnerable Code:

js
const output = execSync(
  `bash "${enforcerScript}" "${targetPath}" current`,
  { encoding: 'utf8' }
).trim();

Technical Analysis

targetPath is obtained from the command-line arguments and interpolated directly into a command executed by a shell. Although the value is surrounded by double quotes, shell command substitution constructs such as $(command) and backticks are still evaluated inside double-quoted strings. Embedded quotation marks may also terminate the intended argument and introduce additional shell syntax.

The application does not validate or safely encode targetPath before passing it to execSync(). Consequently, path validation itself becomes a command-execution primitive.

Attack Path

  1. An attacker supplies a crafted target path to lib/validate.js or causes another component to call validateWithEnforcer() with attacker-controlled input.
  2. The crafted value contains shell syntax, such as command substitution.
  3. validateWithEnforcer() concatenates the value into the shell command.
  4. execSync() invokes the shell, which evaluates the injected syntax before running the enforcer script.
  5. The injected command executes under the identity and privileges of the Node.js process.

Impact Assessment

Successful exploitation permits arbitrary local command execution with all privileges available to the Agent or user running the skill. An attacker could read or modify accessible files, obtain locally available secrets, alter project contents, invoke other installed programs, or perform network operations where permitted.

The vulnerability does not independently escalate operating-system privileges, but its scope includes all resources accessible to the compromised process.

Remediation
View remediation

Remediation Suggestions

Do not construct a shell command from targetPath. Invoke the enforcer as an executable with an argument array and disable shell processing:

js
const { spawnSync } = require('child_process');

const result = spawnSync(
  enforcerScript,
  [targetPath, 'current'],
  {
    encoding: 'utf8',
    shell: false
  }
);

if (result.error) {
  throw result.error;
}

if (result.status !== 0) {
  throw new Error(result.stderr || `Enforcer exited with status ${result.status}`);
}

const output = result.stdout.trim();

Alternatively, use execFileSync() with an argument array. Validate that targetPath is a string, reject NUL and control characters, and resolve and canonicalize it before boundary enforcement. Do not attempt to fix the issue solely through shell escaping.

T09 · Insecure Skill Coding Practices

Error
Location
lib/discover.js:94
Finding

OS Command Injection Through Configured Discovery Roots

Content
View full analysis

Vulnerability Details

File Location: lib/discover.js:94-105
Vulnerability Type: OS command injection
Risk Level: High

Vulnerable Code:

js
function findLockFiles(searchLocations) {
  const projects = new Map();

  searchLocations.forEach(location => {
    const expanded = expandPath(location);
    if (!fs.existsSync(expanded)) return;

    try {
      // Find all .project-lock files
      const lockFiles = execSync(
        `find "${expanded}" -name ".project-lock" -type f 2>/dev/null`,
        { encoding: 'utf8' }
      ).trim().split('\n').filter(f => f.trim());

Technical Analysis

expanded is derived from configured search locations, including values from config/defaults.json, additional roots, and supported environment-variable expansion. It is inserted directly into a shell command.

Double quotes do not neutralize command substitution. Therefore, a configured path containing shell-evaluated syntax can execute commands when project discovery runs. The preliminary fs.existsSync(expanded) check does not sanitize the value or prevent shell interpretation if a matching path exists.

Attack Path

  1. An attacker who can influence deployment configuration, configured additional roots, or an expanded environment-backed search root supplies a path containing shell syntax.
  2. A filesystem entry matching that literal path is made available so that fs.existsSync() succeeds.
  3. The user or Agent runs project discovery, listing, or switching functionality.
  4. findLockFiles() embeds the malicious root into a shell command.
  5. The shell evaluates the injected syntax and executes the attacker-selected command with the Node.js process privileges.

Impact Assessment

Exploitation permits arbitrary command execution in the context of the user or Agent invoking discovery. The attacker can access or modify any data available to that account and may compromise every projec ...[truncated 268 chars]

Remediation
View remediation

Remediation Suggestions

Execute find without a shell and pass the root as a discrete argument:

js
const { spawnSync } = require('child_process');

const result = spawnSync(
  'find',
  [expanded, '-name', '.project-lock', '-type', 'f'],
  {
    encoding: 'utf8',
    shell: false
  }
);

if (result.error || result.status !== 0) {
  return;
}

const lockFiles = result.stdout
  .trim()
  .split('\n')
  .filter(file => file.trim());

A safer and more portable design is to use Node.js filesystem traversal instead of an external find command. Canonicalize configured roots with fs.realpathSync(), require absolute paths, reject NUL and control characters, and define which users or components are authorized to modify discovery configuration.

T09 · Insecure Skill Coding Practices

Error
Location
lib/validate.js:43
Finding

OS Command Injection Through Project ROOT Lock-File Content

Content
View full analysis

Vulnerability Details

File Location: lib/validate.js:43-61
Vulnerability Type: OS command injection through untrusted lock-file data
Risk Level: High

Vulnerable Code:

js
try {
  // Find .project-lock file for current session project
  const output = execSync('bash -c "cat ~/clawd/.project-lock | grep ROOT:" | sed \'s/ROOT: //\'"', { encoding: 'utf8' }).trim();

  if (!output) {
    return {
      error: 'No current project set',
      project: null
    };
  }

  const currentRoot = expandPath(output);

  // Try to find the actual lock file location
  const lockFiles = execSync(
    `find "${currentRoot}" -name ".project-lock" 2>/dev/null`,
    { encoding: 'utf8' }
  ).trim().split('\n').filter(f => f.trim());

Technical Analysis

The code reads the ROOT: value from ~/clawd/.project-lock, expands it, and interpolates it into a shell command. Lock-file content is data and is not safe to interpret as shell syntax. A malicious ROOT: value containing command substitution can be evaluated when getCurrentProject() invokes find.

The use of a fixed shell pipeline to parse the lock file also introduces unnecessary shell dependence. Neither the parsed output nor currentRoot is constrained to a valid canonical directory before command execution.

Attack Path

  1. An attacker plants or modifies ~/clawd/.project-lock and inserts a crafted ROOT: value containing shell syntax.
  2. The user or Agent invokes status or validation functionality, causing getCurrentProject() to execute.
  3. The code extracts the attacker-controlled ROOT: value.
  4. The value is embedded into the find command.
  5. The shell evaluates the injected syntax and executes arbitrary commands under the invoking process identity.

Impact Assessment

An attacker with the ability to control the active lock file can execute arbitrary commands when status or validation is requested. The resu ...[truncated 358 chars]

Remediation
View remediation

Remediation Suggestions

Parse the lock file directly with Node.js rather than using cat, grep, and sed. After extracting ROOT, resolve and canonicalize it, then perform traversal without a shell:

js
const lockContent = fs.readFileSync(
  path.join(process.env.HOME, 'clawd', '.project-lock'),
  'utf8'
);

const rootLine = lockContent
  .split(/\r?\n/)
  .find(line => line.trim().startsWith('ROOT:'));

if (!rootLine) {
  throw new Error('No current project set');
}

const configuredRoot = rootLine.slice(rootLine.indexOf(':') + 1).trim();
const currentRoot = fs.realpathSync(path.resolve(expandPath(configuredRoot)));

const result = spawnSync(
  'find',
  [currentRoot, '-name', '.project-lock', '-type', 'f'],
  {
    encoding: 'utf8',
    shell: false
  }
);

Prefer Node.js filesystem APIs over invoking find. Require ROOT to be an absolute directory, reject control characters and NUL bytes, enforce ownership and restrictive permissions on the active lock file, and verify that its declared root agrees with the lock file's trusted location.

Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (9)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 65)May include surrounding context.

Project Protocol Lock File (Workspace Anchor)

NAME: my-cool-app ROOT: ~/projects/my-cool-app IGNORE: node_modules, .git, dist, .env

text

## 🤖 Agent Instructions (Internal)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · config/defaults.json (reported line 17)May include surrounding context.

json
"__pycache__",
    "target",
    "dist",
    ".env.local",
    ".DS_Store"
  ]
}

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README explicitly instructs agents to use broad filesystem discovery commands like find or ls to locate .project-lock files if paths are not already known. For a skill whose purpose is boundary enforcement, encouraging unrestricted discovery across the environment expands the agent's visibility beyond the current project and can expose unrelated repositories, sensitive directory names, or boundary files from other workspaces.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The helper is intended to dispatch a sub-command, but it always re-invokes the same cli.js entrypoint without ever using the supplied cmd value. Because main() then calls runCommand(command, args) again, any valid command triggers unbounded self-recursion via repeated process spawning, causing denial of service through process exhaustion, hangs, or rapid resource consumption.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script recursively searches user home and other environment/config-derived roots, which can enumerate far more of the filesystem than is necessary for a narrowly scoped project discovery action. In an agent skill context, this broad discovery can expose sensitive directory structure and project metadata and increases the blast radius if the tool is invoked automatically or by an untrusted prompt.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The path check is logically incorrect: after computing path.relative(currentRoot, targetAbs), it blocks when the relative path starts with '..' or when that relative path does not start with currentRoot. Because a relative path normally never starts with the absolute project root, the second condition makes legitimate in-project paths fail validation. In a security boundary component, this can cause denial of intended operations and encourage callers to ignore or bypass the validator entirely, weakening path enforcement.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The docstring states 'Expand environment variables (~, $HOME, etc.)', suggesting general path expansion behavior, but the code only handles a few hard-coded prefixes and for '$VARNAME/...' patterns returns just the environment variable value instead of preserving the suffix. This is an intent/documentation divergence because the comment describes broader and more correct expansion semantics than the function actually provides.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The script reads HOME and USERPROFILE environment variables to expand search roots, which is access to environment-derived user location data. Although this is functionally reasonable, there is no confirmation prompt, user-facing notice, or explanatory warning in the file describing that user home-based locations will be inspected.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code launches a shell-based recursive find over broad, user-derived locations without explicit scope controls or disclosure. Although the path is quoted, using execSync with shell invocation and scanning large areas can reveal sensitive filesystem metadata, cause performance degradation, and is riskier in an agent setting where execution may happen implicitly.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
cli.js:22

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
lib/discover.js:103

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
lib/validate.js:51