Back to skill

Security audit

Excalidraw

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its drawing-generation purpose, but its helper script can load executable code from unsafe local paths if a dependency is missing.

Review before installing or running the helper script. Use only in a trusted environment, avoid running it with sensitive environment variables, and prefer fixing the dependency loading so lz-string is installed and resolved from the skill's trusted package directory only. Also verify or change the configured Obsidian vault path before allowing file writes.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (1)

T08 · Insecure Dependencies

Error
Location
scripts/compress.cjs:13
Finding

Unsafe Dependency Loading from a Shared Temporary Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/compress.cjs, lines 13–18
Vulnerability Type: Unsafe third-party module resolution from a shared, writable directory
Risk Level: High

Vulnerable Code

js
let lzstring;
try {
    lzstring = require("lz-string");
} catch {
    // Try local or relative
    try { lzstring = require("/tmp/node_modules/lz-string"); }
    catch { lzstring = require(require("path").join(process.cwd(), "node_modules", "lz-string")); }
}

Technical Analysis

When standard Node.js resolution for lz-string fails, the script explicitly loads the dependency from /tmp/node_modules/lz-string. On typical multi-user Unix-like systems, /tmp is shared and writable. An attacker with local write access can create that module path and place attacker-controlled JavaScript in it.

Node.js executes a module's top-level code during require(). Consequently, the malicious module executes before the script reads or converts the drawing. No package manifest, lockfile, integrity verification, ownership check, or trusted-path validation is present to authenticate the fallback dependency.

The final fallback based on process.cwd() also makes dependency selection depend on the caller-controlled working directory. If the normal and /tmp resolutions fail, invoking the script from a directory containing an attacker-controlled node_modules/lz-string can produce the same code-execution result.

Attack Path

  1. An attacker obtains write access to the shared /tmp directory, or controls the directory from which the script will be launched.
  2. The attacker creates a malicious module at /tmp/node_modules/lz-string/index.js, or at node_modules/lz-string beneath the controlled working directory.
  3. The legitimate lz-string dependency is absent or cannot be resolved through normal Node.js module lookup.
  4. A victim invokes node scripts/compress.cjs input.excalidraw.
  5. The fallback require() resolves and executes the a ...[truncated 773 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove both the hard-coded /tmp/node_modules/lz-string fallback and the process.cwd()-based fallback.
  2. Declare lz-string as a direct dependency in a project-local package.json.
  3. Commit a lockfile and use deterministic installation such as npm ci.
  4. Load the dependency only through standard resolution from the Skill's trusted installation tree:
js
let lzstring;
try {
    lzstring = require("lz-string");
} catch (error) {
    console.error("Required dependency 'lz-string' is not installed.");
    process.exit(1);
}
  1. Fail closed when the trusted dependency is unavailable rather than searching writable or caller-controlled locations.
  2. Pin and routinely audit the dependency version. Where deployment controls permit it, verify installed package integrity and ensure the Skill directory is not writable by untrusted users.
  3. Run the conversion script with least privilege and without unnecessary credentials in its environment.
Vulnerability Patterns
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill instructs writes to a fixed absolute path in the user's Obsidian vault without requiring explicit user confirmation or clarifying that files will be created or modified there. This can cause unintended file creation or overwriting in a real user environment, especially because the path is concrete and action-oriented rather than illustrative.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

This markdown file contains a user-visible text example in Chinese ("🎓 小导 · 科研助手 🔒私用") while the document otherwise provides no indication that the skill is intentionally locale-specific or that users can choose language. Under the policy, forcing or implying a specific language without opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.