clawcare 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clawcare-0.1.0/PKG-INFO +290 -0
- clawcare-0.1.0/README.md +273 -0
- clawcare-0.1.0/clawcare/__init__.py +3 -0
- clawcare-0.1.0/clawcare/adapters/__init__.py +1 -0
- clawcare-0.1.0/clawcare/adapters/base.py +42 -0
- clawcare-0.1.0/clawcare/adapters/registry.py +86 -0
- clawcare-0.1.0/clawcare/cli.py +214 -0
- clawcare-0.1.0/clawcare/config.py +101 -0
- clawcare-0.1.0/clawcare/discovery.py +13 -0
- clawcare-0.1.0/clawcare/gate.py +40 -0
- clawcare-0.1.0/clawcare/integrations/__init__.py +1 -0
- clawcare-0.1.0/clawcare/integrations/claude_code.py +171 -0
- clawcare-0.1.0/clawcare/integrations/codex.py +177 -0
- clawcare-0.1.0/clawcare/integrations/cursor.py +178 -0
- clawcare-0.1.0/clawcare/integrations/openclaw.py +128 -0
- clawcare-0.1.0/clawcare/models.py +143 -0
- clawcare-0.1.0/clawcare/policy.py +174 -0
- clawcare-0.1.0/clawcare/report.py +148 -0
- clawcare-0.1.0/clawcare/rulesets/default/command-injection.yml +131 -0
- clawcare-0.1.0/clawcare/rulesets/default/sensitive-data.yml +91 -0
- clawcare-0.1.0/clawcare/scanner/__init__.py +1 -0
- clawcare-0.1.0/clawcare/scanner/rules.py +171 -0
- clawcare-0.1.0/clawcare/scanner/scanner.py +156 -0
- clawcare-0.1.0/clawcare.egg-info/PKG-INFO +290 -0
- clawcare-0.1.0/clawcare.egg-info/SOURCES.txt +40 -0
- clawcare-0.1.0/clawcare.egg-info/dependency_links.txt +1 -0
- clawcare-0.1.0/clawcare.egg-info/entry_points.txt +8 -0
- clawcare-0.1.0/clawcare.egg-info/requires.txt +9 -0
- clawcare-0.1.0/clawcare.egg-info/top_level.txt +1 -0
- clawcare-0.1.0/pyproject.toml +71 -0
- clawcare-0.1.0/setup.cfg +4 -0
- clawcare-0.1.0/tests/test_adapters.py +114 -0
- clawcare-0.1.0/tests/test_codex.py +141 -0
- clawcare-0.1.0/tests/test_config.py +102 -0
- clawcare-0.1.0/tests/test_cursor.py +136 -0
- clawcare-0.1.0/tests/test_gate.py +71 -0
- clawcare-0.1.0/tests/test_golden.py +163 -0
- clawcare-0.1.0/tests/test_models.py +80 -0
- clawcare-0.1.0/tests/test_policy.py +105 -0
- clawcare-0.1.0/tests/test_report.py +72 -0
- clawcare-0.1.0/tests/test_scan_scope.py +180 -0
- clawcare-0.1.0/tests/test_scanner.py +126 -0
clawcare-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clawcare
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Guardian engine for agentic-tool extension supply-chain security
|
|
5
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: click>=8.1
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff>=0.8.0; extra == "dev"
|
|
15
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# π¦ClawCare
|
|
19
|
+
|
|
20
|
+
**Security scanner for AI agent skills and plugins β catch malicious attacks before they execute.**
|
|
21
|
+
|
|
22
|
+
ClawCare scans AI agent skills, plugins, and rules for supply-chain threats like command injection, credential theft, and data extraction. It enforces permission policies, and can be used as a CLI tool or integrated into CI/CD pipelines.
|
|
23
|
+
|
|
24
|
+
## Why
|
|
25
|
+
|
|
26
|
+
AI coding agents (Claude Code, Cursor, Codex, OpenClaw) let you install third-party skills and plugins that can read your files, run commands, access secrets and extract sensitive data. A malicious skill can:
|
|
27
|
+
|
|
28
|
+
- Pipe remote scripts into your shell (`curl ... | bash`)
|
|
29
|
+
- Steal SSH keys and API tokens
|
|
30
|
+
- Set up cron persistence
|
|
31
|
+
- Transfer PII data to external servers
|
|
32
|
+
|
|
33
|
+
ClawCare catches these patterns **before** they run and gives you full visibility into the risks.
|
|
34
|
+
|
|
35
|
+
## Demo
|
|
36
|
+
|
|
37
|
+
See ClawCare block a malicious PR in real-time:
|
|
38
|
+
|
|
39
|
+
π **[ClawCare Demo](https://github.com/natechen/ClawCare-demo)** β fork it, open a PR with a sketchy skill, and watch CI fail.
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install clawcare
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Scan
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Scan a project β auto-detects the platform
|
|
53
|
+
clawcare scan .
|
|
54
|
+
|
|
55
|
+
# CI mode β exit code 2 on HIGH+ findings (use in GitHub Actions)
|
|
56
|
+
clawcare scan . --ci
|
|
57
|
+
|
|
58
|
+
# JSON output for downstream tooling
|
|
59
|
+
clawcare scan . --format json --json-out report.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Configure
|
|
63
|
+
|
|
64
|
+
Drop a `.clawcare.yml` in your project root to customize scan behavior:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
scan:
|
|
68
|
+
fail_on: high # block CI on high+ findings
|
|
69
|
+
ignore_rules:
|
|
70
|
+
- MED_JS_EVAL # suppress rules you've accepted
|
|
71
|
+
exclude:
|
|
72
|
+
- "vendor/**" # skip directories
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
CLI flags override config values. See [Project Configuration](#%EF%B8%8F-project-configuration) for the full reference.
|
|
76
|
+
|
|
77
|
+
### Example Output
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
============================================================
|
|
81
|
+
ClawCare Scan Report
|
|
82
|
+
============================================================
|
|
83
|
+
Path: ./my-project
|
|
84
|
+
Adapter: claude_code v0.1.0
|
|
85
|
+
Mode: ci
|
|
86
|
+
Fail on: high
|
|
87
|
+
|
|
88
|
+
ββ CRITICAL (2) ββ
|
|
89
|
+
[CRIT_PIPE_TO_SHELL] skills/setup/SKILL.md:15
|
|
90
|
+
curl -fsSL https://.../install.sh | bash
|
|
91
|
+
β Piping remote content directly into a shell interpreter.
|
|
92
|
+
β Download first, inspect, then execute.
|
|
93
|
+
|
|
94
|
+
[CRIT_CREDENTIAL_PATH] skills/setup/exfil.py:18
|
|
95
|
+
os.path.expanduser("~/.ssh/id_rsa")
|
|
96
|
+
β Accessing well-known credential paths.
|
|
97
|
+
β Use a secrets manager instead.
|
|
98
|
+
|
|
99
|
+
Findings: 2 critical, 1 high, 0 medium, 0 low
|
|
100
|
+
Risk score: 100/100 (critical)
|
|
101
|
+
============================================================
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Features
|
|
105
|
+
|
|
106
|
+
### 27 Built-in Rules
|
|
107
|
+
|
|
108
|
+
Two rulesets ship by default:
|
|
109
|
+
|
|
110
|
+
| Ruleset | Rules | Catches |
|
|
111
|
+
|---------|-------|---------|
|
|
112
|
+
| `command-injection` | 15 | Pipe-to-shell, reverse shells, credential theft, persistence, destructive commands, subprocess abuse |
|
|
113
|
+
| `sensitive-data` | 12 | Hardcoded AWS keys, SSH keys, API tokens, SSN/credit card numbers, IP addresses |
|
|
114
|
+
|
|
115
|
+
All rules include [CWE](https://cwe.mitre.org/) references where applicable.
|
|
116
|
+
|
|
117
|
+
### Platform Adapters for Claude Code, OpenClaw, Codex and Cursor Agent Skills
|
|
118
|
+
|
|
119
|
+
Auto-detects the AI agent platform and scans the right files:
|
|
120
|
+
|
|
121
|
+
| Platform | Scans | Detection |
|
|
122
|
+
|----------|-------|-----------|
|
|
123
|
+
| **Claude Code** | `.claude/skills/*/SKILL.md` + code | `.claude-plugin/`, `SKILL.md` |
|
|
124
|
+
| **Cursor** | `.cursor/rules/*.mdc`, `.cursorrules` + skills | `.cursor/` directory |
|
|
125
|
+
| **Codex** | `AGENTS.md`, `AGENTS.override.md` + skills | `AGENTS.md` |
|
|
126
|
+
| **OpenClaw** | `SKILL.md` + code in skill directories | `.opencode/` |
|
|
127
|
+
|
|
128
|
+
All following the file structure of the respective AI agent platforms.
|
|
129
|
+
|
|
130
|
+
Only plugin and skill files are scanned β your application code, README, and CI configs are never touched.
|
|
131
|
+
|
|
132
|
+
### Project Configuration
|
|
133
|
+
|
|
134
|
+
Create a `.clawcare.yml` in your project root:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
scan:
|
|
138
|
+
fail_on: high # minimum severity to block CI (critical | high | medium | low)
|
|
139
|
+
block_local: false # block locally too? (default: warn only)
|
|
140
|
+
ignore_rules:
|
|
141
|
+
- MED_JS_EVAL # suppress specific rules
|
|
142
|
+
exclude:
|
|
143
|
+
- "vendor/**" # skip directories
|
|
144
|
+
max_file_size_kb: 512 # skip large files
|
|
145
|
+
rulesets:
|
|
146
|
+
- default # built-in rules (included by default)
|
|
147
|
+
- ./my-custom-rules # add your own
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
CLI flags override config values. Excludes and rulesets from both sources merge.
|
|
151
|
+
|
|
152
|
+
### Policy Manifests
|
|
153
|
+
|
|
154
|
+
Skills/Plugins can declare their permissions in a `clawcare.manifest.yml`:
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
permissions:
|
|
158
|
+
exec: none # no shell execution
|
|
159
|
+
network: allowlist # only listed domains
|
|
160
|
+
filesystem: read_only
|
|
161
|
+
secrets: none
|
|
162
|
+
persistence: forbidden
|
|
163
|
+
|
|
164
|
+
allowed_domains:
|
|
165
|
+
- api.anthropic.com
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
ClawCare enforces these declarations β violations appear as HIGH/CRITICAL findings.
|
|
169
|
+
|
|
170
|
+
### Custom Rulesets
|
|
171
|
+
|
|
172
|
+
Create your own rules as YAML:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
rules:
|
|
176
|
+
- id: MY_NO_INTERNAL_URLS
|
|
177
|
+
pattern: "https://internal\\.corp\\.com"
|
|
178
|
+
severity: high
|
|
179
|
+
description: "References to internal URLs should not appear in extensions."
|
|
180
|
+
recommendation: "Use environment variables for internal endpoints."
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Place in a folder, then: `clawcare scan . --ruleset ./my-rules`
|
|
184
|
+
|
|
185
|
+
### Custom Adapters
|
|
186
|
+
|
|
187
|
+
ClawCare supports custom adapters for scanning any AI agent platform. An adapter implements four methods:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# my_adapter.py
|
|
191
|
+
from clawcare.models import ExtensionRoot
|
|
192
|
+
|
|
193
|
+
class MyAdapter:
|
|
194
|
+
name = "my_platform"
|
|
195
|
+
version = "0.1.0"
|
|
196
|
+
priority = 50
|
|
197
|
+
|
|
198
|
+
def detect(self, target_path: str) -> float:
|
|
199
|
+
"""Return 0.0β1.0 confidence that this adapter applies."""
|
|
200
|
+
...
|
|
201
|
+
|
|
202
|
+
def discover_roots(self, target_path: str) -> list[ExtensionRoot]:
|
|
203
|
+
"""Return extension roots to scan."""
|
|
204
|
+
...
|
|
205
|
+
|
|
206
|
+
def scan_scope(self, root: ExtensionRoot) -> dict:
|
|
207
|
+
"""Return include/exclude globs for this root."""
|
|
208
|
+
return {
|
|
209
|
+
"include_globs": ["*.md", "*.py", "*.yml"],
|
|
210
|
+
"exclude_globs": [".git", "node_modules"],
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
def default_manifest(self, root: ExtensionRoot) -> str | None:
|
|
214
|
+
"""Return path to clawcare.manifest.yml, or None."""
|
|
215
|
+
return None
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use it via import string:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
clawcare scan path/ --adapter import:my_adapter:MyAdapter
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Or register permanently via entry point in your `pyproject.toml`:
|
|
225
|
+
|
|
226
|
+
```toml
|
|
227
|
+
[project.entry-points."clawcare.adapters"]
|
|
228
|
+
my_platform = "my_adapter:MyAdapter"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
See [`clawcare/adapters/base.py`](clawcare/adapters/base.py) for the full protocol, or any of the [built-in adapters](clawcare/integrations/) for real examples.
|
|
232
|
+
|
|
233
|
+
## CI Integration
|
|
234
|
+
|
|
235
|
+
### GitHub Actions
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
- name: Install ClawCare
|
|
239
|
+
run: pip install clawcare
|
|
240
|
+
|
|
241
|
+
- name: Scan for malicious extensions
|
|
242
|
+
run: clawcare scan . --ci
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Full workflow example: [ClawCare Demo CI](https://github.com/natechen/ClawCare-demo/blob/main/.github/workflows/clawcare.yml)
|
|
246
|
+
|
|
247
|
+
## CLI Reference
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
clawcare scan <path> [OPTIONS]
|
|
251
|
+
|
|
252
|
+
Options:
|
|
253
|
+
--ci CI mode (exit 2 on findings above threshold)
|
|
254
|
+
--fail-on SEVERITY Minimum severity to block: critical|high|medium|low (default: high)
|
|
255
|
+
--block-local Block locally too (default: warn only, exit 0)
|
|
256
|
+
--format FORMAT Output format: text|json (default: text)
|
|
257
|
+
--json-out FILE Write JSON report to file
|
|
258
|
+
--adapter NAME Force a specific adapter (default: auto-detect)
|
|
259
|
+
--ruleset PATH Additional rulesets (repeatable)
|
|
260
|
+
--exclude GLOB Exclude glob patterns (repeatable)
|
|
261
|
+
--max-file-size-kb N Skip files larger than N KB
|
|
262
|
+
--manifest MODE Manifest enforcement: auto|skip|strict (default: auto)
|
|
263
|
+
|
|
264
|
+
clawcare adapters list List registered adapters
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Development
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
git clone https://github.com/natechen/ClawCare
|
|
271
|
+
cd ClawCare
|
|
272
|
+
make install # install in dev mode with ruff + pytest
|
|
273
|
+
make check # lint + test (run before opening a PR)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Available make targets:
|
|
277
|
+
|
|
278
|
+
| Command | What it does |
|
|
279
|
+
|---------|-------------|
|
|
280
|
+
| `make install` | Install in dev mode |
|
|
281
|
+
| `make lint` | Run ruff linter |
|
|
282
|
+
| `make format` | Auto-format code |
|
|
283
|
+
| `make test` | Run pytest |
|
|
284
|
+
| `make check` | Lint + test (pre-PR gate) |
|
|
285
|
+
|
|
286
|
+
We use **GitHub Flow** β branch off `main`, open a PR, get a review, merge.
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
[Apache 2.0](LICENSE)
|
clawcare-0.1.0/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# π¦ClawCare
|
|
2
|
+
|
|
3
|
+
**Security scanner for AI agent skills and plugins β catch malicious attacks before they execute.**
|
|
4
|
+
|
|
5
|
+
ClawCare scans AI agent skills, plugins, and rules for supply-chain threats like command injection, credential theft, and data extraction. It enforces permission policies, and can be used as a CLI tool or integrated into CI/CD pipelines.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
AI coding agents (Claude Code, Cursor, Codex, OpenClaw) let you install third-party skills and plugins that can read your files, run commands, access secrets and extract sensitive data. A malicious skill can:
|
|
10
|
+
|
|
11
|
+
- Pipe remote scripts into your shell (`curl ... | bash`)
|
|
12
|
+
- Steal SSH keys and API tokens
|
|
13
|
+
- Set up cron persistence
|
|
14
|
+
- Transfer PII data to external servers
|
|
15
|
+
|
|
16
|
+
ClawCare catches these patterns **before** they run and gives you full visibility into the risks.
|
|
17
|
+
|
|
18
|
+
## Demo
|
|
19
|
+
|
|
20
|
+
See ClawCare block a malicious PR in real-time:
|
|
21
|
+
|
|
22
|
+
π **[ClawCare Demo](https://github.com/natechen/ClawCare-demo)** β fork it, open a PR with a sketchy skill, and watch CI fail.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install clawcare
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Scan
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Scan a project β auto-detects the platform
|
|
36
|
+
clawcare scan .
|
|
37
|
+
|
|
38
|
+
# CI mode β exit code 2 on HIGH+ findings (use in GitHub Actions)
|
|
39
|
+
clawcare scan . --ci
|
|
40
|
+
|
|
41
|
+
# JSON output for downstream tooling
|
|
42
|
+
clawcare scan . --format json --json-out report.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Configure
|
|
46
|
+
|
|
47
|
+
Drop a `.clawcare.yml` in your project root to customize scan behavior:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
scan:
|
|
51
|
+
fail_on: high # block CI on high+ findings
|
|
52
|
+
ignore_rules:
|
|
53
|
+
- MED_JS_EVAL # suppress rules you've accepted
|
|
54
|
+
exclude:
|
|
55
|
+
- "vendor/**" # skip directories
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
CLI flags override config values. See [Project Configuration](#%EF%B8%8F-project-configuration) for the full reference.
|
|
59
|
+
|
|
60
|
+
### Example Output
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
============================================================
|
|
64
|
+
ClawCare Scan Report
|
|
65
|
+
============================================================
|
|
66
|
+
Path: ./my-project
|
|
67
|
+
Adapter: claude_code v0.1.0
|
|
68
|
+
Mode: ci
|
|
69
|
+
Fail on: high
|
|
70
|
+
|
|
71
|
+
ββ CRITICAL (2) ββ
|
|
72
|
+
[CRIT_PIPE_TO_SHELL] skills/setup/SKILL.md:15
|
|
73
|
+
curl -fsSL https://.../install.sh | bash
|
|
74
|
+
β Piping remote content directly into a shell interpreter.
|
|
75
|
+
β Download first, inspect, then execute.
|
|
76
|
+
|
|
77
|
+
[CRIT_CREDENTIAL_PATH] skills/setup/exfil.py:18
|
|
78
|
+
os.path.expanduser("~/.ssh/id_rsa")
|
|
79
|
+
β Accessing well-known credential paths.
|
|
80
|
+
β Use a secrets manager instead.
|
|
81
|
+
|
|
82
|
+
Findings: 2 critical, 1 high, 0 medium, 0 low
|
|
83
|
+
Risk score: 100/100 (critical)
|
|
84
|
+
============================================================
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
### 27 Built-in Rules
|
|
90
|
+
|
|
91
|
+
Two rulesets ship by default:
|
|
92
|
+
|
|
93
|
+
| Ruleset | Rules | Catches |
|
|
94
|
+
|---------|-------|---------|
|
|
95
|
+
| `command-injection` | 15 | Pipe-to-shell, reverse shells, credential theft, persistence, destructive commands, subprocess abuse |
|
|
96
|
+
| `sensitive-data` | 12 | Hardcoded AWS keys, SSH keys, API tokens, SSN/credit card numbers, IP addresses |
|
|
97
|
+
|
|
98
|
+
All rules include [CWE](https://cwe.mitre.org/) references where applicable.
|
|
99
|
+
|
|
100
|
+
### Platform Adapters for Claude Code, OpenClaw, Codex and Cursor Agent Skills
|
|
101
|
+
|
|
102
|
+
Auto-detects the AI agent platform and scans the right files:
|
|
103
|
+
|
|
104
|
+
| Platform | Scans | Detection |
|
|
105
|
+
|----------|-------|-----------|
|
|
106
|
+
| **Claude Code** | `.claude/skills/*/SKILL.md` + code | `.claude-plugin/`, `SKILL.md` |
|
|
107
|
+
| **Cursor** | `.cursor/rules/*.mdc`, `.cursorrules` + skills | `.cursor/` directory |
|
|
108
|
+
| **Codex** | `AGENTS.md`, `AGENTS.override.md` + skills | `AGENTS.md` |
|
|
109
|
+
| **OpenClaw** | `SKILL.md` + code in skill directories | `.opencode/` |
|
|
110
|
+
|
|
111
|
+
All following the file structure of the respective AI agent platforms.
|
|
112
|
+
|
|
113
|
+
Only plugin and skill files are scanned β your application code, README, and CI configs are never touched.
|
|
114
|
+
|
|
115
|
+
### Project Configuration
|
|
116
|
+
|
|
117
|
+
Create a `.clawcare.yml` in your project root:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
scan:
|
|
121
|
+
fail_on: high # minimum severity to block CI (critical | high | medium | low)
|
|
122
|
+
block_local: false # block locally too? (default: warn only)
|
|
123
|
+
ignore_rules:
|
|
124
|
+
- MED_JS_EVAL # suppress specific rules
|
|
125
|
+
exclude:
|
|
126
|
+
- "vendor/**" # skip directories
|
|
127
|
+
max_file_size_kb: 512 # skip large files
|
|
128
|
+
rulesets:
|
|
129
|
+
- default # built-in rules (included by default)
|
|
130
|
+
- ./my-custom-rules # add your own
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
CLI flags override config values. Excludes and rulesets from both sources merge.
|
|
134
|
+
|
|
135
|
+
### Policy Manifests
|
|
136
|
+
|
|
137
|
+
Skills/Plugins can declare their permissions in a `clawcare.manifest.yml`:
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
permissions:
|
|
141
|
+
exec: none # no shell execution
|
|
142
|
+
network: allowlist # only listed domains
|
|
143
|
+
filesystem: read_only
|
|
144
|
+
secrets: none
|
|
145
|
+
persistence: forbidden
|
|
146
|
+
|
|
147
|
+
allowed_domains:
|
|
148
|
+
- api.anthropic.com
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
ClawCare enforces these declarations β violations appear as HIGH/CRITICAL findings.
|
|
152
|
+
|
|
153
|
+
### Custom Rulesets
|
|
154
|
+
|
|
155
|
+
Create your own rules as YAML:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
rules:
|
|
159
|
+
- id: MY_NO_INTERNAL_URLS
|
|
160
|
+
pattern: "https://internal\\.corp\\.com"
|
|
161
|
+
severity: high
|
|
162
|
+
description: "References to internal URLs should not appear in extensions."
|
|
163
|
+
recommendation: "Use environment variables for internal endpoints."
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Place in a folder, then: `clawcare scan . --ruleset ./my-rules`
|
|
167
|
+
|
|
168
|
+
### Custom Adapters
|
|
169
|
+
|
|
170
|
+
ClawCare supports custom adapters for scanning any AI agent platform. An adapter implements four methods:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# my_adapter.py
|
|
174
|
+
from clawcare.models import ExtensionRoot
|
|
175
|
+
|
|
176
|
+
class MyAdapter:
|
|
177
|
+
name = "my_platform"
|
|
178
|
+
version = "0.1.0"
|
|
179
|
+
priority = 50
|
|
180
|
+
|
|
181
|
+
def detect(self, target_path: str) -> float:
|
|
182
|
+
"""Return 0.0β1.0 confidence that this adapter applies."""
|
|
183
|
+
...
|
|
184
|
+
|
|
185
|
+
def discover_roots(self, target_path: str) -> list[ExtensionRoot]:
|
|
186
|
+
"""Return extension roots to scan."""
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
def scan_scope(self, root: ExtensionRoot) -> dict:
|
|
190
|
+
"""Return include/exclude globs for this root."""
|
|
191
|
+
return {
|
|
192
|
+
"include_globs": ["*.md", "*.py", "*.yml"],
|
|
193
|
+
"exclude_globs": [".git", "node_modules"],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
def default_manifest(self, root: ExtensionRoot) -> str | None:
|
|
197
|
+
"""Return path to clawcare.manifest.yml, or None."""
|
|
198
|
+
return None
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Use it via import string:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
clawcare scan path/ --adapter import:my_adapter:MyAdapter
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Or register permanently via entry point in your `pyproject.toml`:
|
|
208
|
+
|
|
209
|
+
```toml
|
|
210
|
+
[project.entry-points."clawcare.adapters"]
|
|
211
|
+
my_platform = "my_adapter:MyAdapter"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
See [`clawcare/adapters/base.py`](clawcare/adapters/base.py) for the full protocol, or any of the [built-in adapters](clawcare/integrations/) for real examples.
|
|
215
|
+
|
|
216
|
+
## CI Integration
|
|
217
|
+
|
|
218
|
+
### GitHub Actions
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
- name: Install ClawCare
|
|
222
|
+
run: pip install clawcare
|
|
223
|
+
|
|
224
|
+
- name: Scan for malicious extensions
|
|
225
|
+
run: clawcare scan . --ci
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Full workflow example: [ClawCare Demo CI](https://github.com/natechen/ClawCare-demo/blob/main/.github/workflows/clawcare.yml)
|
|
229
|
+
|
|
230
|
+
## CLI Reference
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
clawcare scan <path> [OPTIONS]
|
|
234
|
+
|
|
235
|
+
Options:
|
|
236
|
+
--ci CI mode (exit 2 on findings above threshold)
|
|
237
|
+
--fail-on SEVERITY Minimum severity to block: critical|high|medium|low (default: high)
|
|
238
|
+
--block-local Block locally too (default: warn only, exit 0)
|
|
239
|
+
--format FORMAT Output format: text|json (default: text)
|
|
240
|
+
--json-out FILE Write JSON report to file
|
|
241
|
+
--adapter NAME Force a specific adapter (default: auto-detect)
|
|
242
|
+
--ruleset PATH Additional rulesets (repeatable)
|
|
243
|
+
--exclude GLOB Exclude glob patterns (repeatable)
|
|
244
|
+
--max-file-size-kb N Skip files larger than N KB
|
|
245
|
+
--manifest MODE Manifest enforcement: auto|skip|strict (default: auto)
|
|
246
|
+
|
|
247
|
+
clawcare adapters list List registered adapters
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
git clone https://github.com/natechen/ClawCare
|
|
254
|
+
cd ClawCare
|
|
255
|
+
make install # install in dev mode with ruff + pytest
|
|
256
|
+
make check # lint + test (run before opening a PR)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Available make targets:
|
|
260
|
+
|
|
261
|
+
| Command | What it does |
|
|
262
|
+
|---------|-------------|
|
|
263
|
+
| `make install` | Install in dev mode |
|
|
264
|
+
| `make lint` | Run ruff linter |
|
|
265
|
+
| `make format` | Auto-format code |
|
|
266
|
+
| `make test` | Run pytest |
|
|
267
|
+
| `make check` | Lint + test (pre-PR gate) |
|
|
268
|
+
|
|
269
|
+
We use **GitHub Flow** β branch off `main`, open a PR, get a review, merge.
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
[Apache 2.0](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Adapter framework β protocol, registry, and selection logic."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Adapter protocol β the stable contract every adapter must satisfy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from clawcare.models import ExtensionRoot
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class Adapter(Protocol):
|
|
12
|
+
"""Pluggable adapter contract (Β§6.3 of design doc)."""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
version: str
|
|
16
|
+
priority: int # tie-break for auto-detect; higher wins
|
|
17
|
+
|
|
18
|
+
def detect(self, target_path: str) -> float:
|
|
19
|
+
"""Return confidence 0.0β1.0 that this adapter applies.
|
|
20
|
+
|
|
21
|
+
Must be fast and side-effect free.
|
|
22
|
+
"""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def discover_roots(self, target_path: str) -> list[ExtensionRoot]:
|
|
26
|
+
"""Return discovered extension roots under *target_path*."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def scan_scope(self, root: ExtensionRoot) -> dict:
|
|
30
|
+
"""Return scan scope for *root*.
|
|
31
|
+
|
|
32
|
+
Expected keys:
|
|
33
|
+
include_globs: list[str]
|
|
34
|
+
exclude_globs: list[str]
|
|
35
|
+
max_file_size_kb: int (optional override)
|
|
36
|
+
languages: list[str] (optional hints)
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def default_manifest(self, root: ExtensionRoot) -> str | None:
|
|
41
|
+
"""Return manifest path if adapter has conventions; else None."""
|
|
42
|
+
...
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Adapter registry β loading, registration, and selection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from clawcare.adapters.base import Adapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_entry_point_adapters() -> list[Adapter]:
|
|
12
|
+
"""Load adapters registered via the ``clawcare.adapters`` entry-point group."""
|
|
13
|
+
adapters: list[Adapter] = []
|
|
14
|
+
if sys.version_info >= (3, 12):
|
|
15
|
+
from importlib.metadata import entry_points
|
|
16
|
+
eps = entry_points(group="clawcare.adapters")
|
|
17
|
+
else:
|
|
18
|
+
from importlib.metadata import entry_points as _ep
|
|
19
|
+
all_eps = _ep()
|
|
20
|
+
eps = all_eps.get("clawcare.adapters", []) if isinstance(all_eps, dict) else all_eps.select(group="clawcare.adapters")
|
|
21
|
+
for ep in eps:
|
|
22
|
+
try:
|
|
23
|
+
cls = ep.load()
|
|
24
|
+
adapters.append(cls() if isinstance(cls, type) else cls)
|
|
25
|
+
except Exception:
|
|
26
|
+
pass # skip broken adapters silently
|
|
27
|
+
return adapters
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_import_adapter(import_string: str) -> Adapter:
|
|
31
|
+
"""Load an adapter from ``import:pkg.module:ClassName``.
|
|
32
|
+
|
|
33
|
+
The *import_string* is the part after ``import:``.
|
|
34
|
+
"""
|
|
35
|
+
module_path, class_name = import_string.rsplit(":", 1)
|
|
36
|
+
mod = importlib.import_module(module_path)
|
|
37
|
+
cls = getattr(mod, class_name)
|
|
38
|
+
return cls() if isinstance(cls, type) else cls
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_adapters(adapter_spec: str = "auto") -> list[Adapter]:
|
|
42
|
+
"""Return a list of candidate adapters for the given *adapter_spec*.
|
|
43
|
+
|
|
44
|
+
``auto`` β entry-point adapters
|
|
45
|
+
``<name>`` β entry-point adapters filtered to that name
|
|
46
|
+
``import:...`` β single adapter from import string
|
|
47
|
+
"""
|
|
48
|
+
if adapter_spec.startswith("import:"):
|
|
49
|
+
return [load_import_adapter(adapter_spec[len("import:"):])]
|
|
50
|
+
|
|
51
|
+
all_adapters = _load_entry_point_adapters()
|
|
52
|
+
|
|
53
|
+
if adapter_spec != "auto":
|
|
54
|
+
matched = [a for a in all_adapters if a.name == adapter_spec]
|
|
55
|
+
if not matched:
|
|
56
|
+
raise ValueError(f"No registered adapter named '{adapter_spec}'")
|
|
57
|
+
return matched
|
|
58
|
+
|
|
59
|
+
return all_adapters
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def select_adapter(
|
|
63
|
+
adapters: list[Adapter],
|
|
64
|
+
target_path: str,
|
|
65
|
+
) -> Adapter | None:
|
|
66
|
+
"""Pick the best adapter for *target_path* by confidence, with priority tie-break.
|
|
67
|
+
|
|
68
|
+
Returns ``None`` if all confidences are 0.0.
|
|
69
|
+
"""
|
|
70
|
+
scored: list[tuple[float, int, Adapter]] = []
|
|
71
|
+
for a in adapters:
|
|
72
|
+
conf = a.detect(target_path)
|
|
73
|
+
if conf > 0.0:
|
|
74
|
+
scored.append((conf, a.priority, a))
|
|
75
|
+
|
|
76
|
+
if not scored:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# highest confidence first, then highest priority
|
|
80
|
+
scored.sort(key=lambda t: (t[0], t[1]), reverse=True)
|
|
81
|
+
return scored[0][2]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def list_registered_adapters() -> list[Adapter]:
|
|
85
|
+
"""Return all entry-point-registered adapters (for ``adapters list``)."""
|
|
86
|
+
return _load_entry_point_adapters()
|