Back to skill

Security audit

@openclaw/interchange

Security checks for vulnerabilities and agentic risk

Overview

The package appears to be a real OpenClaw interchange library, but its public file APIs are too broad and include unsafe write behavior that could affect files outside the intended workspace.

Review before installing. This does not look malicious, but it is a foundational file-writing library with raw path access. Only use it with trusted callers, keep interchange paths constrained to a dedicated workspace, avoid passing user-supplied paths directly, and update the flagged dependencies before production use.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
src/io.js:15
Finding
Predictable Temporary File Allows Symlink-Based Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `src/io.js:15-27` **Vulnerability Type**: Predictable temporary file and unsafe symbolic-link handling **Risk Level**: High ### Vulnerable Code ```javascript export function atomicWrite(filePath, data) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`; const fd = fs.openSync(tmp, 'w'); try { fs.writeSync(fd, data); fs.fsyncSync(fd); fs.closeSync(fd); fs.renameSync(tmp, filePath); } catch (err) { try { fs.closeSync(fd); } catch {} try { fs.unlinkSync(tmp); } catch {} throw err; } } ``` ### Technical Analysis The temporary filename consists only of the destination path, process ID, and current timestamp. These values are predictable or can be approximated by another local process. The file is opened using the string flag `w`. This flag creates or truncates the referenced file, but it does not provide exclusive creation equivalent to `O_EXCL` and does not prevent symbolic-link traversal. If an attacker pre-creates the predicted temporary path as a symbolic link, `fs.openSync(tmp, 'w')` follows that link and truncates or overwrites the link target. The subsequent rename also moves the attacker-created symbolic link onto `filePath`. Consequently, exploitation can both modify an external file and leave the intended interchange path pointing to an attacker-selected destination. The issue affects direct calls to the exported `atomicWrite()` function and higher-level operations that invoke it, including `writeMd()` and index generation. ### Attack Path 1. A local attacker obtains write access to the directory containing the intended interchange file. 2. The attacker determines the victim process ID and estimates the timestamp at which `atomicWrite()` will run. 3. The attacker creates one or more symbolic links matching names such as: `target.md.tmp.&lt;victim-pid&gt;.&lt;timestamp&gt;`. 4. Each s ...[truncated 1059 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate temporary filenames using cryptographically secure randomness rather than timestamps alone, while keeping the temporary file in the destination directory to preserve atomic rename semantics. - Create the temporary file exclusively: ```javascript const tmp = `${filePath}.tmp.${process.pid}.${crypto.randomUUID()}`; const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL; const fd = fs.openSync(tmp, flags, 0o600); ``` - Add `O_NOFOLLOW` on platforms that expose and support it. - Before writing, use `lstatSync()` where appropriate to reject symbolic links. Treat this as defense in depth rather than a substitute for atomic exclusive creation. - Use restrictive temporary-file permissions such as `0o600`. - Verify that the opened object is a regular file using `fstatSync(fd)`. - Preserve cleanup in a `finally` block and track whether the descriptor has already been closed. - Add a regression test that pre-creates the candidate temporary path as a symbolic link and verifies that the external target remains unchanged. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/io.js:15
Finding
Public File APIs Permit Unrestricted Filesystem Access<![CDATA[ ## Vulnerability Details **File Location**: `src/io.js:15-27, 35-36, 53-54`; public exports at `src/index.js:8` **Vulnerability Type**: Missing path-boundary and traversal validation **Risk Level**: Medium ### Vulnerable Code ```javascript export function atomicWrite(filePath, data) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`; const fd = fs.openSync(tmp, 'w'); try { fs.writeSync(fd, data); fs.fsyncSync(fd); fs.closeSync(fd); fs.renameSync(tmp, filePath); } catch (err) { try { fs.closeSync(fd); } catch {} try { fs.unlinkSync(tmp); } catch {} throw err; } } ``` ```javascript export function readMd(filePath) { const raw = fs.readFileSync(filePath, 'utf8'); ``` ```javascript export async function writeMd(filePath, frontmatter, content, opts = {}) { const lock = await acquireLock(filePath); ``` These unrestricted functions are exposed through the package entry point: ```javascript export { readMd, writeMd, atomicWrite } from './io.js'; ``` ### Technical Analysis The package is described as an interchange workspace library, and the indexer uses a configured `INTERCHANGE_ROOT`. However, its public read and write APIs do not enforce that boundary. The `filePath` argument is accepted unchanged. Absolute paths and traversal sequences such as `../` can therefore escape the intended workspace. The write implementation also calls `mkdirSync(..., { recursive: true })`, allowing creation of directory trees outside the interchange root when process permissions permit. A lexical containment check alone would not completely resolve the issue because an apparently valid path beneath the root can traverse through an existing symbolic link. Secure confinement must account for both normalized paths and filesystem links. This vulnerability requires a consuming application or Agent to pass attacker-controlled or insufficiently trusted path value ...[truncated 1596 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require an explicit workspace root for public interchange operations. - Resolve user paths relative to that root and reject paths that escape it: ```javascript function resolveWithinRoot(root, candidate) { const resolvedRoot = path.resolve(root); const resolvedPath = path.resolve(resolvedRoot, candidate); const relative = path.relative(resolvedRoot, resolvedPath); if ( relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) ) { throw new Error('Path escapes the interchange root'); } return resolvedPath; } ``` - Do not use a simple string-prefix test, because paths such as `/workspace-root-evil` can share a textual prefix with `/workspace-root`. - Resolve and validate existing parent directories with `realpathSync()` to detect symbolic-link escapes. - Reject symbolic links in destination path components or open files relative to a trusted directory using platform-supported secure directory APIs. - Keep unrestricted raw filesystem primitives private. If they must remain public, expose them under an explicitly privileged API and document that their arguments must never contain untrusted input. - Validate `skillName` and similar path components using a strict allowlist, such as letters, digits, underscores, and hyphens. - Add tests covering absolute paths, `../` traversal, sibling directories with common prefixes, Windows path forms, and symbolic links that point outside the configured root. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (25)

Known Vulnerable Dependency: vitest==3.2.4 — 2 advisory(ies): CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Critical
Category
Supply Chain
Confidence
90% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: vitest==3.2.4 — 2 advisory(ies): CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Critical
Category
Supply Chain
Confidence
94% confidence
Finding
The declared vitest range resolves to a version with critical advisories involving arbitrary file read and possible execution, but vitest is only a devDependency and is not part of the runtime library surface. This still matters in developer and CI environments, where a vulnerable test tool or UI server could expose sensitive files or enable compromise if used in an unsafe way.

Credential Access

High
Category
Privilege Escalation
Content
### 13. No path traversal protection (io.js, indexer.js)

`writeMd`, `atomicWrite`, `readMd` accept arbitrary paths. A skill could write to `../../etc/passwd` or any location. While skills are trusted code, defense-in-depth would restrict writes to within `INTERCHANGE_ROOT`.

**Fix:** Add an optional `assertWithinRoot(filePath)` guard to `writeMd`.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description presents a Markdown/shared interchange library dealing with file serialization, YAML frontmatter, locking, and schema validation. The supplied code does none of those things. Instead, it provides resilience control for external API calls via a circuit breaker pattern. This is a materially different primary purpose and introduces undeclared capabilities unrelated to the declared library functionality.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description emphasizes foundational Markdown interchange features such as atomic writes, deterministic serialization, YAML frontmatter handling, advisory locking, and schema validation. The supplied code chunk does not implement those capabilities. Instead, it contains miscellaneous helper/formatting functions, only one of which tangentially relates to markdown via a table serializer re-export. The presence of slugification, currency formatting, and relative time formatting indicates a materially different purpose from the declared shared .md interchange library. Therefore, the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The code chunk is an index file that re-exports library modules. It does support the declared purpose around Markdown interchange, atomic I/O, locking, validation, and deterministic serialization. However, it also declares materially broader capabilities not mentioned in the description, especially database reconciliation and index management, which go beyond a narrowly described '.md interchange library' foundation. Because the declared description omits these significant exported behaviors, the description does not fully accurately represent what the code exposes.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description presents a broad shared interchange library centered on Markdown file IO/integrity features such as atomic writes, deterministic serialization, YAML frontmatter, advisory locking, and schema validation. The supplied code instead implements a narrow reconciliation function that detects drift between a database-state map and parsed file records by comparing IDs and hashes. That behavior is related at a high level to interchange data management, but it does not demonstrate the specific declared capabilities and has a materially different immediate purpose: sync/reconciliation analysis rather than foundational .md interchange operations. Because the code’s actual functionality is not accurately represented by the declared description, this is a mismatch.

Known Vulnerable Dependency: js-yaml==4.1.1 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
96% confidence
Finding
js-yaml is a direct production dependency of this package, and the advisories describe CPU exhaustion conditions during parsing of crafted YAML inputs. Because this skill is an interchange library centered on YAML frontmatter, untrusted Markdown/YAML content is core to its function, which makes parser-level denial of service materially relevant.

Known Vulnerable Dependency: nanoid==3.3.11 — 3 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: picomatch==4.0.3 — 2 advisory(ies): CVE-2026-33672 (Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Mat); CVE-2026-33671 (Picomatch has a ReDoS vulnerability via extglob quantifiers)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: postcss==8.5.6 — 4 advisory(ies): CVE-2026-45623 (PostCSS: Arbitrary file read and information disclosure via attacker-controlled ); CVE-2026-69153 (PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappi); CVE-2026-41305 (PostCSS has XSS via Unescaped </style> in its CSS Stringify Output) +1 more

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: rollup==4.57.1 — 1 advisory(ies): CVE-2026-27606 (Rollup 4 has Arbitrary File Write via Path Traversal)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: vite==7.3.1 — 5 advisory(ies): CVE-2026-39365 (Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling); CVE-2026-53571 (vite: `server.fs.deny` bypass on Windows alternate paths); CVE-2026-39363 (Vite Vulnerable to Arbitrary File Read via Vite Dev Server WebSocket) +2 more

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: js-yaml==4.1.1 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
98% confidence
Finding
The package allows installation of js-yaml 4.1.1, which is flagged with multiple denial-of-service advisories involving YAML parsing and merge-key processing. Because this library explicitly advertises YAML frontmatter and schema validation as core functionality, vulnerable YAML parsing is directly relevant and could let crafted input consume excessive CPU or otherwise destabilize consumers of the skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
* @param {string} filePath - Target file path
 * @param {Record<string, any>} frontmatter - Frontmatter fields
 * @param {string} content - Markdown body content
 * @param {{ force?: boolean, skipValidation?: boolean }} [opts] - Options; force=true skips idempotency check, skipValidation=true skips frontmatter validation
 */
export async function writeMd(filePath, frontmatter, content, opts = {}) {
  const lock = await acquireLock(filePath);
Confidence
91% confidence
Finding
The API exposes a `skipValidation` option that allows callers to bypass frontmatter schema checks entirely. In a shared interchange library that other skills build on, this weakens a central trust boundary and can let malformed or policy-violating metadata be persisted, which may later trigger unsafe behavior in downstream consumers that assume validated input.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
};

    // Validate frontmatter unless explicitly skipped (e.g., internal/test use)
    if (!opts.skipValidation) {
      const validation = validateFrontmatter(meta);
      if (!validation.valid) {
        throw new Error(`Invalid frontmatter: ${validation.errors.join('; ')}`);
Confidence
95% confidence
Finding
This conditional explicitly disables validation when `opts.skipValidation` is set, creating a straightforward validation-bypass path. Because this module is the foundational I/O layer for other OpenClaw skills, allowing invalid YAML/frontmatter to be written here can propagate tainted state across the ecosystem and undermine assumptions made by higher-level logic.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
await expect(writeMd(fp, { skill: 'test' }, 'content')).rejects.toThrow(/Invalid frontmatter/);
  });

  it('allows skipValidation for internal use', async () => {
    const fp = path.join(tmpDir, 'skip.md');
    await writeMd(fp, { skill: 'test' }, 'content', { skipValidation: true });
    expect(readMd(fp).meta.skill).toBe('test');
Confidence
80% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
await expect(writeMd(fp, { skill: 'test' }, 'content')).rejects.toThrow(/Invalid frontmatter/);
  });

  it('allows skipValidation for internal use', async () => {
    const fp = path.join(tmpDir, 'skip.md');
    await writeMd(fp, { skill: 'test' }, 'content', { skipValidation: true });
    expect(readMd(fp).meta.skill).toBe('test');
Confidence
80% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown file describes atomic file I/O and presents the library as a foundation for reading and writing interchange files, but it does not include any caution that using the write APIs will modify files on disk. Because markdown files should warn about behaviors that could affect user data or system integrity, the omission is a minor safety disclosure gap.

Known Vulnerable Dependency: @vitest/mocker==3.2.4 — 1 advisory(ies): CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Low
Category
Supply Chain
Confidence
60% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: esbuild==0.27.3 — 1 advisory(ies): GHSA-g7r4-m6w7-qqqr (esbuild allows arbitrary file read when running the development server on Window)

Low
Category
Supply Chain
Confidence
60% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"test:watch": "vitest"
  },
  "dependencies": {
    "js-yaml": "^4.1.0"
  },
  "devDependencies": {
    "vitest": "^3.0.0"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"js-yaml": "^4.1.0"
  },
  "devDependencies": {
    "vitest": "^3.0.0"
  },
  "engines": {
    "node": ">=18"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The code forces the locale to 'en-US' and the currency to 'USD' in a user-facing formatter. This is a natural-language/locale policy concern because it imposes a specific locale and regional format without any opt-in or indication that the skill is intentionally US-only.

Static analysis

No suspicious patterns detected.