← Back to Plugins
Tools

Skill Scanner

kobepaw By kobepaw 👁 67 views ▲ 0 votes

AST-based evasion detection for OpenClaw skills. Catches obfuscation, dynamic eval, and encoding tricks that regex misses.

GitHub

Install

openclaw plugins install @kobepaw/openclaw-skill-scanner

Configuration Example

// Concatenated require — regex sees no module name
const cp = require("child_" + "process");
cp.exec("whoami");

// Variable import — regex can't match the specifier
const target = getPayload();
await import(target);

// Indirect eval via comma operator — regex won't match "eval" as a call
(0, eval)("malicious code here");

// globalThis bracket notation — bypasses direct "eval" keyword check
globalThis["eval"]("malicious code here");

// Prototype pollution
obj["__proto__"]["isAdmin"] = true;

README

# @kobepaw/openclaw-skill-scanner

> AST-based evasion detection for [OpenClaw](https://openclaw.ai) skills.  
> Catches obfuscation, dynamic eval, and encoding tricks that regex patterns miss.

[![npm](https://img.shields.io/npm/v/@kobepaw/openclaw-skill-scanner)](https://www.npmjs.com/package/@kobepaw/openclaw-skill-scanner)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)

---

## What it catches

OpenClaw's built-in skill scanner uses regex patterns to detect dangerous code. But regex has a fundamental weakness: **it matches strings, not structure**. A malicious skill author can bypass regex by obfuscating their code.

This plugin adds a TypeScript AST analysis layer that catches **structural** evasion patterns:

| Rule ID | Severity | What it catches |
|---------|----------|----------------|
| `dynamic-import` | 🔴 critical | `import(variable)`, `import(\`${template}\`)` — dynamic module loading |
| `dynamic-require` | 🔴 critical | `require(expr)` with non-literal args — concatenated module names |
| `indirect-eval` | 🔴 critical | `globalThis["eval"](...)`, `(0, eval)(...)`, `window.Function(...)` |
| `prototype-pollution` | 🔴 critical | `__proto__` assignment, `constructor.prototype`, `Object.setPrototypeOf()` |

### Real examples

These all **bypass regex** but are **caught by this plugin**:

```js
// Concatenated require — regex sees no module name
const cp = require("child_" + "process");
cp.exec("whoami");

// Variable import — regex can't match the specifier
const target = getPayload();
await import(target);

// Indirect eval via comma operator — regex won't match "eval" as a call
(0, eval)("malicious code here");

// globalThis bracket notation — bypasses direct "eval" keyword check
globalThis["eval"]("malicious code here");

// Prototype pollution
obj["__proto__"]["isAdmin"] = true;
```

---

## Install

```sh
openclaw plugins install @kobepaw/openclaw-skill-scanner
```

---

## Configuration

Add to your OpenClaw config:

```json
{
  "plugins": {
    "entries": {
      "skill-scanner": {
        "enabled": true,
        "blockOnDetection": false
      }
    }
  }
}
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | boolean | `true` | Enable/disable AST scanning |
| `blockOnDetection` | boolean | `false` | Block skill install on findings (warn-only by default) |

---

## How it works

The plugin uses TypeScript's built-in parser (`typescript` package) — **zero additional dependencies** beyond what's already in your OpenClaw install.

When a skill is scanned:
1. TypeScript's `createSourceFile` parses the source into an AST
2. Four structural detectors walk the AST looking for dangerous patterns
3. Findings are reported with file path, line number, and evidence snippet
4. Deduplication: one finding per rule per file keeps output actionable, not noisy
5. Graceful degradation: if parsing fails, a `warn`-level `ast-parse-error` finding is emitted

---

## Design decisions

- **Complements, doesn't replace** — works alongside existing regex scanners as a second layer
- **Zero new dependencies** — TypeScript is already a project dependency in most OpenClaw setups  
- **Correct ScriptKind mapping** — `.tsx→TSX`, `.jsx→JSX`, `.js/.mjs/.cjs→JS`, everything else→`TS`
- **One finding per ruleId per file** — signal over noise

### Known acceptable gaps

- `process.binding()` native access not detected
- `setTimeout("code", 0)` string eval not detected  
- `Function.prototype.constructor("code")()` not detected
- Legitimate `require(path.join(__dirname, "config"))` will flag as `dynamic-require` — acceptable trade-off for security

---

## Contributing

Issues and PRs welcome: [github.com/kobepaw/openclaw-skill-scanner](https://github.com/kobepaw/openclaw-skill-scanner)

---

## License

[Apache-2.0](LICENSE) © kobepaw
tools

Comments

Sign in to leave a comment

Loading comments...