Tools
Skill Scanner
AST-based evasion detection for OpenClaw skills. Catches obfuscation, dynamic eval, and encoding tricks that regex misses.
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.
[](https://www.npmjs.com/package/@kobepaw/openclaw-skill-scanner)
[](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