Back to skill

Security audit

Claw Draw

Security checks for vulnerabilities and agentic risk

Overview

This is a real drawing integration, but it needs Review because it publishes to a public canvas, persists credentials, fetches arbitrary image URLs with imperfect safeguards, and includes under-disclosed autonomous and temp-file behavior.

Install only if you are comfortable with an agent creating a ClawDraw identity, storing an API key under `~/.clawdraw`, and publishing drawings or uploaded images to a shared public canvas. Avoid using `paint` with private, internal, or attacker-controlled URLs, review any `roam` usage carefully, and clean up temp screenshots or prompt files after generation workflows.

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
scripts/clawdraw.mjs:1964
Finding
DNS Rebinding Bypasses Image Fetch SSRF Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawdraw.mjs:1964-2052` **Vulnerability Type**: Time-of-check/time-of-use SSRF protection bypass through DNS rebinding **Risk Level**: High ### Vulnerable Code ```js async function validateImageUrl(urlStr) { const parsed = new URL(urlStr); // Block non-HTTP(S) (already checked by caller, but defense-in-depth) if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { throw new Error('Only HTTP and HTTPS URLs are supported.'); } // Block obvious private hostnames const host = parsed.hostname.toLowerCase(); if (host === 'localhost' || host.endsWith('.local') || host.endsWith('.internal')) { throw new Error('Private/internal URLs are not allowed.'); } // DNS resolve and block private IP ranges const { address } = await lookup(host); const parts = address.split('.').map(Number); const isPrivate = parts[0] === 127 || parts[0] === 10 || (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || (parts[0] === 192 && parts[1] === 168) || (parts[0] === 169 && parts[1] === 254) || parts[0] === 0 || address === '::1' || address.startsWith('fe80:') || address.startsWith('fc00:') || address.startsWith('fd'); if (isPrivate) { throw new Error('Private/internal URLs are not allowed.'); } } ``` The validated address is not bound to the subsequent request: ```js const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30_000); let res; try { res = await fetch(url, { redirect: 'manual', signal: controller.signal, }); } finally { clearTimeout(timeout); } ``` ### Technical Analysis The code resolves the supplied hostname with `lookup()` and verifies that the returned address is not within selected private, loopback, or link-local ranges. The later `fetch()` call independently resolves the hostname again. This creates a time-of-check/time-of-use discrepancy. An attacker wh ...[truncated 2439 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve and validate every A and AAAA record associated with the hostname rather than checking only one result. 2. Normalize addresses with a well-tested IP-address library before classification, including: - IPv4-mapped IPv6 addresses - IPv6 loopback and unspecified addresses - IPv6 link-local and unique-local ranges - IPv4 private, loopback, link-local, carrier-grade NAT, multicast, reserved, and documentation ranges 3. Bind the outgoing connection to an address that was already validated. Preserve the original hostname for the HTTP `Host` header and TLS SNI/certificate validation. 4. Do not allow the HTTP client to perform an uncontrolled second DNS lookup after validation. 5. Repeat the resolve, validate, and address-binding process separately for every redirect target. 6. Consider permitting image downloads only through a controlled proxy or an explicit allowlist when operationally feasible. 7. Add automated tests covering: - DNS rebinding between validation and connection - Multiple A and AAAA records - IPv4-mapped IPv6 addresses - Redirect-based rebinding - Cloud metadata, loopback, and private address ranges ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/clawdraw.mjs:2785
Finding
Predictable Temporary Files Permit Local Disclosure and Symlink Attacks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawdraw.mjs:2785-2789` **Vulnerability Type**: Insecure temporary-file creation **Risk Level**: Medium ### Vulnerable Code ```js const screenshotPath = path.join(os.tmpdir(), `clawdraw-pgs-screenshot-${Date.now()}.png`); const promptPath = path.join(os.tmpdir(), `clawdraw-pgs-prompt-${Date.now()}.txt`); fs.writeFileSync(screenshotPath, pngBuf); fs.writeFileSync(promptPath, injectedPrompt, 'utf-8'); ``` ### Technical Analysis The generated filenames contain only the current timestamp and a fixed prefix. They are therefore predictable to another local process that can observe or approximate when the command runs. `writeFileSync()` is used without exclusive creation, so an existing file is silently truncated and replaced. The code also does not verify that the destination is a regular file rather than a symbolic link. No explicit restrictive mode is supplied. Effective access therefore depends on the process umask and operating-system defaults. On a permissively configured host, other local users may be able to read the screenshot or prompt. The files are not removed after the generation preparation finishes. Prompts and screenshots can consequently remain in the shared temporary directory indefinitely, extending the disclosure window. ### Attack Path 1. A local attacker observes that the victim is running `clawdraw generate` or repeatedly predicts nearby millisecond timestamps. 2. The attacker creates candidate paths in the shared temporary directory using the expected filename pattern. 3. The attacker either: - Creates ordinary files and later reads data written to them, or - Creates symbolic links pointing to another file writable by the victim. 4. `writeFileSync()` follows the pre-existing path and writes the screenshot or prompt without exclusive-creation checks. 5. The attacker obtains generated data or causes another victim-owned file to be overwritten. 6. Even without pre-creat ...[truncated 806 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private randomized temporary directory with `fs.mkdtempSync()` or its asynchronous equivalent. 2. Set the temporary directory mode to `0o700`. 3. Create each file atomically with: - `flag: 'wx'` to reject pre-existing paths - `mode: 0o600` to restrict file access 4. Use `lstat()` or safe exclusive-open semantics to ensure the destination is not a symbolic link. 5. Open the file descriptor first and write through that descriptor instead of checking and writing by path in separate operations. 6. Delete screenshots and prompts in a `finally` block when they are no longer needed. 7. If files must survive for an external image-generation step, provide an explicit cleanup command and expire old files automatically. 8. Avoid printing sensitive prompt content directly to logs unless the user explicitly requests it. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/auth.mjs:50
Finding
Credential Storage Does Not Repair Pre-Existing Insecure Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.mjs:50-84` **Vulnerability Type**: Insufficient credential-file permission enforcement **Risk Level**: Low ### Vulnerable Code ```js function writeCache(token) { try { fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 }); fs.writeFileSync(CACHE_FILE, JSON.stringify({ token, expiresAt: Date.now() + TOKEN_TTL_MS, createdAt: new Date().toISOString(), }), { encoding: 'utf-8', mode: 0o600 }); } catch (err) { console.warn('[auth] Could not write token cache:', err.message); } } ``` The long-lived API key is stored in the same manner: ```js export function writeApiKey(apiKey, agentId, agentName) { fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 }); fs.writeFileSync(APIKEY_FILE, JSON.stringify({ apiKey, agentId, agentName, createdAt: new Date().toISOString(), }), { encoding: 'utf-8', mode: 0o600 }); } ``` ### Technical Analysis The `mode` option supplied to `mkdirSync()` and `writeFileSync()` controls permissions when a new object is created. It does not reliably tighten permissions on a directory or file that already exists. If `~/.clawdraw`, `token.json`, or `apikey.json` was previously created with permissive access, rewriting the files can retain those existing permissions. The implementation does not call `chmod()`, verify ownership, or reject symbolic links before storing credentials. The API key is persistent and can be exchanged for JWTs. The token cache contains a short-lived bearer token. Both therefore require deterministic permission enforcement rather than reliance on creation-time modes and the user's umask. ### Attack Path 1. `~/.clawdraw`, `apikey.json`, or `token.json` already exists with group-readable or world-readable permissions. 2. This may result from a previous version, manual creation, backup restoration, unusual umask, or local manipulation. 3. The user runs setup or authentication. 4. The Ski ...[truncated 1109 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Explicitly enforce permissions after creating or opening the objects: - `0o700` for `~/.clawdraw` - `0o600` for `apikey.json` and `token.json` 2. Use `lstat()` to reject symbolic links and non-regular credential files. 3. Verify that the credential directory and files are owned by the current effective user. 4. Open credential files using safe flags such as `O_NOFOLLOW` where supported. 5. Write credentials to a newly created private file using `flag: 'wx'`, synchronize it if necessary, and atomically rename it into place. 6. Recheck and repair permissions every time credentials are read or written, not only during first creation. 7. Warn or fail securely if ownership or file type is unexpected. 8. Provide a credential-revocation procedure for users who discover that their files were exposed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (44)

Missing User Warnings

High
Confidence
97% confidence
Finding
The README states that setup automatically creates an agent account and saves an API key with no browser, website, or manual key entry, but does not clearly warn users that credentials will be created and persisted locally. Silent credential creation and storage can surprise users, expand the trusted footprint of the system, and leave long-lived secrets on disk without informed consent or guidance on storage location and revocation.

Ae1

High
Category
analysis-evasion
Content
files: ["scripts/clawdraw.mjs","scripts/auth.mjs","scripts/connection.mjs","scripts/snapshot.mjs","scripts/symmetry.mjs","scripts/roam.mjs","primitives/","lib/"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
files: ["scripts/clawdraw.mjs","scripts/auth.mjs","scripts/connection.mjs","scripts/snapshot.mjs","scripts/symmetry.mjs","scripts/roam.mjs","primitives/","lib/"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
files: ["scripts/clawdraw.mjs","scripts/auth.mjs","scripts/connection.mjs","scripts/snapshot.mjs","scripts/symmetry.mjs","scripts/roam.mjs","primitives/","lib/"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
awdraw.mjs","scripts/auth.mjs","scripts/connection.mjs","scripts/snapshot.mjs","scripts/symmetry.mjs","scripts/roam.mjs","primitives/","lib/","templates/","comm
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Content
The paint command fetches an image from a user-provided URL, processes it with `sharp` (libvips), and converts it to strokes:

- **URL validation** — Only HTTP/HTTPS protocols are allowed. Private and internal IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, IPv6 loopback, link-local `fe80:`, unique local `fc00:`/`fd`) are blocked via DNS resolution to prevent SSRF.
- **Redirect SSRF protection** — Fetch uses `redirect: 'manual'` to prevent attackers from bypassing DNS validation with a public URL that 301-redirects to a private IP (e.g. `169.254.169.254`). Redirect targets are re-validated through `validateImageUrl()` before following. Maximum 1 redirect hop.
- **30s fetch timeout** — `AbortController` enforces a 30-second timeout to prevent slow-server DoS.
- **Content-Type validation** — Only `image/*` MIME types are accepted. Non-image responses are rejected before being passed to `sharp`.
- **Format whitelist** — Only `image/jpeg`, `image/png`, `image/webp`, `image/gif`, `image/tiff`, and `image/avif` are allowed. Other image formats (and all non-image decoders in libvips) are never reached.
Confidence
90% confidence
Finding
Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Session Persistence

Medium
Category
Rogue Agent
Content
## What it does

Gives AI agents the ability to draw on a shared infinite canvas alongside humans and other agents. Agents create stroke data (parametric curves, fractals, flow fields, etc.) and send the resulting strokes to the canvas in real time.

## Features
Confidence
81% confidence
Finding
The documented behavior implies persistent external state: generated strokes are sent to a shared canvas in real time and the feature list also references local history for undo. In skill context this is more dangerous because actions are not purely local or ephemeral; agent output can persist remotely and locally, creating audit, privacy, and unintended-action risks if the skill is invoked accidentally.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README explains that agents send generated strokes in real time to a shared infinite multiplayer canvas, but it does not prominently warn that outputs are transmitted to and visible within a shared external service. Without a user-facing disclosure, users may unknowingly send sensitive prompts, derived content, or identifying artistic output to a public or semi-public environment.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The README says users can simply ask the agent to draw and 'it knows what to do,' which is broad invocation guidance without clear boundaries, confirmation requirements, or exclusions. In an agent environment, this can cause the skill to trigger on ordinary drawing-related requests and perform external actions on a shared canvas without sufficiently explicit user intent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill declares network and environment-backed behavior (`CLAWDRAW_API_KEY`, HTTPS/WSS endpoints, fetching image URLs) but does not declare an explicit tool scope such as `permissions` or `allowed-tools`. That makes the effective authority broader and less auditable, increasing the risk that an agent can use network/env capabilities without clear policy boundaries.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: clawdraw
version: 0.9.20
description: "Create algorithmic art on ClawDraw's infinite multiplayer canvas. Use when asked to draw, paint, create visual art, generate patterns, or make algorithmic artwork. Supports custom stroke generators, 75 primitives (fractals, flow fields, L-systems, spirographs, noise, simulation, 3D), 25 collaborator behaviors (extend, branch, contour, morph, etc.), SVG templates, stigmergic markers, symmetry transforms, composition, image upload and placement (PNG/JPEG/WebP/GIF, 5MB max), image painting (5 artistic modes: pointillist, sketch, vangogh, slimemold, freestyle), and canvas vision snapshots."
user-invocable: true
homepage: https://clawdraw.ai
emoji: 🎨
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- **Report what you spent.** After drawing, tell the user approximately how many strokes you sent and how much INQ it cost.
- **Share the waypoint link, not a follow link.** Every draw/paint command automatically creates a waypoint and prints a `Waypoint: https://clawdraw.ai/?wp=...` URL. Present this URL to the user so they can watch the drawing in real time. **Never** generate or share `?follow=` URLs — follow mode is a web-only feature and agents must not use it.
- **Run setup before drawing.** Before any draw command, if you have not already confirmed authentication, run `clawdraw setup` first. There is no API key available on the ClawDraw website — `clawdraw setup` is the only way to create agent credentials. It takes 5 seconds and requires no user input.
- **Handle auth errors with setup.** If any command fails with "Agent auth failed (401)" or "Invalid or revoked API key", run `clawdraw setup` immediately. Do not ask the user to find an API key on a website — none exists there.
- **One tab per request.** The first draw/paint/compose command in a request opens the waypoint and browser tab automatically. Every subsequent command in the same request MUST use `--no-waypoint` — otherwise a new tab opens for each command.

## Installation
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Skill Enumeration

Medium
Category
Agent Snooping
Content
### Claude Code

`npm install -g @clawdraw/skill` auto-registers the skill at `~/.claude/skills/clawdraw/SKILL.md`.
Start a new Claude Code session — `/clawdraw` is immediately available.

**First-time setup (required before drawing):**
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill encourages fetching arbitrary user-supplied image URLs for `paint`, but the warning about external network access and privacy implications is only buried later in the document rather than presented at the point of use. This can lead agents to retrieve attacker-controlled or sensitive internal URLs, enabling SSRF-like behavior or unintended disclosure of browsing targets/metadata.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Model Invocation Notice

This skill is invoked only when the user explicitly asks to draw, paint, or create art. It does not auto-execute on startup, run on a schedule, or monitor background events. The `always: false` metadata flag confirms this is an opt-in skill.

## Trust Statement
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Session Persistence

Medium
Category
Rogue Agent
Content
## Model Invocation Notice

This skill is invoked only when the user explicitly asks to draw, paint, or create art. It does not auto-execute on startup, run on a schedule, or monitor background events. The `always: false` metadata flag confirms this is an opt-in skill.

## Trust Statement
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill instructs the agent to blindly set environment variables from an untrusted task object before running shell-accessed commands. Because environment variables can alter execution behavior, redirect config/auth paths, inject options into subprocesses, or influence shell-invoked tooling, this creates a command/context injection surface and can also expose or misuse sensitive credentials supplied by the task.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This markdown file documents an HTTP image upload endpoint using a bearer token and base64-encoded image payload, which affects user data and privacy. The section provides usage details and limits but does not include any warning or disclosure that image contents and authentication credentials are being sent to a remote service.

External Transmission

Medium
Category
Data Exfiltration
Content
### Upload image (HTTP)

`POST https://api.clawdraw.ai/api/agents/images` with `Authorization: Bearer <jwt>`.

```json
{ "base64": "<base64-encoded-image>", "x": 5000, "y": 5000, "width": 300, "height": 300 }
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
80% confidence
Finding
The manifest describes art creation, collaboration behaviors, image handling, and vision snapshots, but not autonomous roaming. This file imports and routes a `roam` command as 'Autonomous free-roam mode', adding agent autonomy outside the declared scope.

Session Persistence

Medium
Category
Rogue Agent
Content
const state = readState();
  if (!state.hasCustomAlgorithm) {
    console.log('');
    console.log('Create your own algorithm first!');
    console.log('');
    console.log('Use `clawdraw stroke --stdin` or `clawdraw stroke --file` to send custom strokes,');
    console.log('then you can mix in built-in primitives with `clawdraw draw`.');
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
async function cmdCreate(name) {
  if (!name) {
    console.error('Usage: clawdraw create <agent-name>');
    process.exit(1);
  }
  try {
Confidence
89% confidence
Finding
The `create` and `setup` flows display or persist newly issued API keys locally, including writing them to `~/.clawdraw/apikey.json`, but no file-permission hardening is shown in this file. On multi-user systems or lax umask settings, this can expose long-lived credentials to other local users or processes, enabling unauthorized account access and spending actions.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest frames this skill as creating algorithmic art on ClawDraw's canvas, but the code also redeems web-account link codes and creates Stripe checkout sessions for purchasing INQ. Those are account and billing workflows rather than drawing or canvas-manipulation behavior.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest mentions image upload/placement and image painting modes, but not proposing generation areas, acquiring generation locks, capturing screenshots for generation, or preparing prompts for external image generation workflows. The `propose-pgs` and `generate` commands add a separate image-generation orchestration capability.

Scope Creep

Medium
Confidence
98% confidence
Finding
The security manifest explicitly claims `files: none`, but the code reads and writes a marker file in the system temp directory to implement browser-tab cooldown behavior. This is a real capability mismatch: agents or users relying on the manifest for sandboxing/trust decisions would be misled about local filesystem side effects, and hidden file writes are especially sensitive in agent skill ecosystems.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/clawdraw.mjs:75