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.
Files changed (42) hide show
  1. clawcare-0.1.0/PKG-INFO +290 -0
  2. clawcare-0.1.0/README.md +273 -0
  3. clawcare-0.1.0/clawcare/__init__.py +3 -0
  4. clawcare-0.1.0/clawcare/adapters/__init__.py +1 -0
  5. clawcare-0.1.0/clawcare/adapters/base.py +42 -0
  6. clawcare-0.1.0/clawcare/adapters/registry.py +86 -0
  7. clawcare-0.1.0/clawcare/cli.py +214 -0
  8. clawcare-0.1.0/clawcare/config.py +101 -0
  9. clawcare-0.1.0/clawcare/discovery.py +13 -0
  10. clawcare-0.1.0/clawcare/gate.py +40 -0
  11. clawcare-0.1.0/clawcare/integrations/__init__.py +1 -0
  12. clawcare-0.1.0/clawcare/integrations/claude_code.py +171 -0
  13. clawcare-0.1.0/clawcare/integrations/codex.py +177 -0
  14. clawcare-0.1.0/clawcare/integrations/cursor.py +178 -0
  15. clawcare-0.1.0/clawcare/integrations/openclaw.py +128 -0
  16. clawcare-0.1.0/clawcare/models.py +143 -0
  17. clawcare-0.1.0/clawcare/policy.py +174 -0
  18. clawcare-0.1.0/clawcare/report.py +148 -0
  19. clawcare-0.1.0/clawcare/rulesets/default/command-injection.yml +131 -0
  20. clawcare-0.1.0/clawcare/rulesets/default/sensitive-data.yml +91 -0
  21. clawcare-0.1.0/clawcare/scanner/__init__.py +1 -0
  22. clawcare-0.1.0/clawcare/scanner/rules.py +171 -0
  23. clawcare-0.1.0/clawcare/scanner/scanner.py +156 -0
  24. clawcare-0.1.0/clawcare.egg-info/PKG-INFO +290 -0
  25. clawcare-0.1.0/clawcare.egg-info/SOURCES.txt +40 -0
  26. clawcare-0.1.0/clawcare.egg-info/dependency_links.txt +1 -0
  27. clawcare-0.1.0/clawcare.egg-info/entry_points.txt +8 -0
  28. clawcare-0.1.0/clawcare.egg-info/requires.txt +9 -0
  29. clawcare-0.1.0/clawcare.egg-info/top_level.txt +1 -0
  30. clawcare-0.1.0/pyproject.toml +71 -0
  31. clawcare-0.1.0/setup.cfg +4 -0
  32. clawcare-0.1.0/tests/test_adapters.py +114 -0
  33. clawcare-0.1.0/tests/test_codex.py +141 -0
  34. clawcare-0.1.0/tests/test_config.py +102 -0
  35. clawcare-0.1.0/tests/test_cursor.py +136 -0
  36. clawcare-0.1.0/tests/test_gate.py +71 -0
  37. clawcare-0.1.0/tests/test_golden.py +163 -0
  38. clawcare-0.1.0/tests/test_models.py +80 -0
  39. clawcare-0.1.0/tests/test_policy.py +105 -0
  40. clawcare-0.1.0/tests/test_report.py +72 -0
  41. clawcare-0.1.0/tests/test_scan_scope.py +180 -0
  42. clawcare-0.1.0/tests/test_scanner.py +126 -0
@@ -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)
@@ -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,3 @@
1
+ """ClawCare β€” Guardian engine for agentic-tool extension supply-chain security."""
2
+
3
+ __version__ = "0.1.0"
@@ -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()