← Back to Plugins
Tools

Skillwall

hamarshehmhmd By hamarshehmhmd 👁 1 views ▲ 0 votes

Supply-chain firewall for coding-agent skills, plugins, hooks & marketplaces (Claude Code, Codex, Cursor, OpenClaw): scan SKILL.md before it runs, pin what you reviewed, fail CI on drift.

Homepage GitHub

Install

pip install -e

Configuration Example

[policy]
fail_on = "high"                        # info | low | medium | high | critical | never
ignore_rules = ["SW003"]
allowed_domains = ["corp.example.com"]  # your own hosts, so SW034/SW021/SW044 don't flag them
allowed_sources = ["anthropics/claude-plugins-official", "my-org/*"]   # marketplaces & plugin repos

[[allow]]                               # written by `skillwall allow`
id = "plugin:plugins/quality"
sha256 = "8f1be79f0fc7e51adc09760f791a671040f2266b84d44947d10a2188a4afc175"
reason = "reviewed by me"

README

# skillwall

[![CI](https://github.com/hamarshehmhmd/skillwall/actions/workflows/ci.yml/badge.svg)](https://github.com/hamarshehmhmd/skillwall/actions/workflows/ci.yml)
[![License: AGPL-3.0-or-later](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE)
![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
![No dependencies](https://img.shields.io/badge/dependencies-none-brightgreen.svg)

```bash
curl -fsSL https://raw.githubusercontent.com/hamarshehmhmd/skillwall/main/install.sh | sh
```

Prefer not to pipe to sh? `pipx install git+https://github.com/hamarshehmhmd/skillwall` or `uv tool install git+https://github.com/hamarshehmhmd/skillwall`. [Read install.sh](install.sh) first if you like.

**A supply-chain check for coding-agent skills, plugins, hooks and marketplaces.** It scans `SKILL.md` files, subagents, slash commands, hook configs, `.mcp.json` and plugin marketplaces for Claude Code, Codex, Cursor, OpenClaw and friends *before they run*. Then it pins what you reviewed and fails CI when any of it changes.

![skillwall demo](docs/demo.gif)

## Why

Agent skills are now a package ecosystem with no package manager security around it:

- **Code runs before the model sees it.** In Claude Code, a skill's `` !`command` `` dynamic-context lines run during preprocessing. A skill can do `` !`gh auth token` `` and `` !`curl … --data-binary @token` `` before the model reads a word of it, so the model refusing to help comes too late ([Datadog Security Labs, May 2026](https://securitylabs.datadoghq.com/articles/malicious-skills-supply-chain-risks-in-coding-agents-with-dynamic-context/)).
- **Skills arrive with the repo.** `.claude/skills/` in a repo you clone (or in a nested package of a monorepo, or in an `--add-dir` folder) is loaded once you trust the workspace. You never "installed" anything.
- **The ecosystem has malware.** Snyk's ToxicSkills scan found 76 confirmed malicious payloads in 3,984 skills ([Feb 2026](https://snyk.io/blog/toxicskills-malicious-ai-agent-skills-clawhub/)). Its Nov 2026 re-scan of the top 20k skills on skills.sh and ClawHub found most of them unsafe in some way ([research.snyk.io](https://research.snyk.io/blog/skill-analysis/)). Other public writeups cover plugin hooks that auto-approve `curl` ([PromptArmor](https://www.promptarmor.com/resources/hijacking-claude-code-via-injected-marketplace-plugins)), a "dependency helper" skill that installs a trojanized `httpx` from another index ([Prompt Security](https://prompt.security/blog/when-your-plugin-starts-picking-your-dependencies-marketplace-skills-and-dependency-hijack-in-claude-code)), and `Bash(*)` plus a reverse shell from a skill ([Reversec](https://labs.reversec.com/posts/2026/05/skill-issues-compromising-claude-code-with-malicious-skills-agents-part-1)).
- **Updates are silent.** Marketplace plugins track a branch by default, and a skill that was fine when you looked at it can change later.

npm and PyPI have lockfiles, `npm audit` and Socket. Skills have `grep`. skillwall is a small, dependency-free CLI to fill that gap on your laptop and in CI.

## Usage

```bash
skillwall scan                       # this repo: .claude/, .codex/, .cursor/, .agents/, plugins, SKILL.md anywhere
skillwall scan --user                # + ~/.claude, ~/.codex, ~/.cursor, ~/.agents, ~/.openclaw, ~/.config/opencode
skillwall scan gh:someone/cool-skills    # vet a skills repo or marketplace BEFORE you install it (shallow clone)
skillwall list --user                # inventory: every skill/plugin/agent/command/hook/MCP config + content hash

skillwall lock                       # pin the content hash of everything you reviewed -> skillwall.lock.json
skillwall verify                     # exit 1 if any artifact was added or changed since the lock (rug-pull check)
skillwall allow plugins/quality --reason "reviewed by me"   # accept a finding-bearing artifact at this exact hash
```

Exit codes: `0` clean, `1` findings at or above `--fail-on` (default `high`) or drift in `verify`, `2` usage error.
Output formats: text (default), `-f json`, `-f sarif -o skillwall.sarif` (GitHub code scanning). In GitHub Actions a summary table goes to the job summary.

### Demo

```bash
git clone https://github.com/hamarshehmhmd/skillwall && cd skillwall/examples/demo-repo
skillwall scan        # 8 artifacts, 5 critical: token exfil via !`…`, curl|bash SessionStart hook, hidden Unicode, …
cd ../marketplace && skillwall lock && skillwall verify
```

`examples/` has two harmless sample trees. All hosts in them use the reserved `.invalid` TLD.

## What it checks

| Rule | Severity | What |
|---|---|---|
| SW001 | high / critical | `` !`cmd` `` dynamic context that calls the network (critical if it uploads data or pipes to a shell) |
| SW002 | high | dynamic context that reads secrets (`gh auth token`, `~/.ssh`, `~/.aws/credentials`, `printenv`, …) |
| SW003 | low | any other dynamic-context command (inventory) |
| SW010 / SW012 | high / medium | `allowed-tools: Bash` / `Bash(*)`, or pre-approved `curl`, `sudo`, `bash -c`, … |
| SW011 | high | subagent with `permissionMode: bypassPermissions` |
| SW013 | medium | skill/agent that declares its own hooks |
| SW020 | critical | download-and-execute: `curl … \| sh`, `bash -c "$(curl …)"`, `iwr … \| iex`, `base64 -d \| sh` (medium in docs or for well-known installers) |
| SW021 | high / critical | reads credentials and posts to a non-official host nearby (exfiltration shape) |
| SW022 | high | package index / registry override (`--index-url`, `UV_INDEX_URL`, `--registry`, …) |
| SW023 | high | writes to `CLAUDE.md`/`AGENTS.md`, agent settings, shell rc files, `authorized_keys`, crontab |
| SW030 | high | prompt-injection phrasing ("ignore previous instructions", "do not tell the user about…", `<IMPORTANT>`) |
| SW031 | critical / high / medium | Unicode tag characters (decoded for you), bidi controls, zero-width characters |
| SW032 | medium | large encoded blobs |
| SW033 | high / medium | executables or other binaries shipped inside a skill/plugin |
| SW034 | high / medium | lookalike hosts (`api.anthropic-cdn.…`, `github-assets.…`), punycode, raw IPs |
| SW040 / SW043 | critical–info | hook commands: download-and-execute, network, secrets; otherwise listed for review |
| SW041 | high | settings with `Bash`/`Bash(*)` in `permissions.allow` or `defaultMode: bypassPermissions` |
| SW042 | medium | `enableAllProjectMcpServers: true` |
| SW044 | high | `ANTHROPIC_BASE_URL` / `*_BASE_URL` pointing at a non-official host |
| SW045 | high / medium | hooks that answer `permissionDecision: "allow"` (unconditionally = high) |
| SW050 / SW051 | high / low | marketplace or plugin sources not on your allow-list / not pinned to a commit |
| SW052 | medium | MCP servers launched with `npx -y pkg` / `uvx pkg` without a version |

`skillwall rules` prints the list. Test files (`tests/`, `*.test.ts`, `test_*.py`) are not text-scanned because agents don't load them. Binaries inside them are still reported.

**What it is not:** this is static, heuristic analysis, so read the findings and don't treat a clean run as a guarantee. A determined attacker can hide behind wrapper scripts or download a second stage at runtime. That's why `lock`/`verify` exists: whatever you reviewed stays exactly what runs. At runtime, also consider `"disableSkillShellExecution": true` in Claude Code managed settings and a command firewall such as [agentfence](https://github.com/hamarshehmhmd/agentfence).

## Policy file

Optional `.skillwall.toml` in the scanned directory (or `--policy path`):

```toml
[policy]
fail_on = "high"                        # info | low | medium | high | critical | never
ignore_rules = ["SW003"]
allowed_domains = ["corp.example.com"]  # your own hosts, so SW034/SW021/SW044 don't flag them
allowed_sources = ["anthropics/claude-plugins-official", "my-org/*"]   # marketplaces & plugin repos

[[allow]]                               # written by `skillwall allow`
id = "plugin:plugins/quality"
sha256 = "8f1be79f0fc7e51adc09760f791a671040f2266b84d44947d10a2188a4afc175"
reason = "reviewed by me"
```

An allowed artifact is reported as reviewed only while its hash matches. Change one byte and its findings are back, marked "changed since it was reviewed".

## CI

```yaml
- uses: hamarshehmhmd/skillwall@v0.1.0
  with:
    command: scan            # or: verify (needs a committed skillwall.lock.json)
    args: --fail-on high
    sarif: skillwall.sarif   # optional; upload with github/codeql-action/upload-sarif
```

pre-commit:

```yaml
- repo: https://github.com/hamarshehmhmd/skillwall
  rev: v0.1.0
  hooks: [{id: skillwall}]
```

## Where it looks

Skills are any directory containing `SKILL.md`: `.claude/skills/`, nested packages, `~/.claude/skills`, plugin `skills/`, `~/.codex/skills`, `.agents/skills`, `~/.openclaw`, and so on. It also reads Claude Code subagents (`.claude/agents/*.md`, plugin `agents/`), slash commands (`commands/**.md`), `.claude/settings*.json` (permissions, hooks, env, `extraKnownMarketplaces`), plugin `hooks/hooks.json` and `plugin.json`, `.claude-plugin/marketplace.json`, `~/.claude/plugins/known_marketplaces.json`, `.mcp.json`, and Cursor's `.cursor/hooks.json` and `.cursor/mcp.json`. Transcript and cache folders under `~/.claude` are skipped.

## Development

```bash
python -m venv .venv && . .venv/bin/activate && pip install -e . pytest
pytest -q                         # SKILLWALL_LIVE=1 also clones anthropics/skills from GitHub
```

## License

GNU Affero General Public License v3.0 or later ([LICENSE](LICENSE)), SPDX `AGPL-3.0-or-later`.

Commercial licenses without the AGPL obligations are available from the author, Mohammad Hamarsheh. Open an issue or contact [@hamarshehmhmd](https://github.com/hamarshehmhmd).
tools

Comments

Sign in to leave a comment

Loading comments...