@hasna/skills 0.10.51 → 0.10.52
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.
- package/README.md +78 -0
- package/bin/index.js +1795 -1346
- package/bin/mcp.js +702 -457
- package/bin/migrate.js +1 -1
- package/bin/server.js +11 -4
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1023 -679
- package/dist/lib/agent-discovery.d.ts +37 -1
- package/dist/lib/agent-integration.d.ts +4 -1
- package/dist/lib/claude-marketplace-entry-witness.d.ts +26 -0
- package/dist/lib/claude-settings-witness.d.ts +25 -1
- package/dist/lib/codex-hook-trust-files.d.ts +17 -0
- package/dist/lib/selection-cache.d.ts +2 -2
- package/dist/lib/session-reconciliation.d.ts +9 -0
- package/dist/sdk/index.js +990 -649
- package/docs/plugin-admission.md +185 -0
- package/package.json +1 -1
package/docs/plugin-admission.md
CHANGED
|
@@ -173,6 +173,74 @@ new loaders still require their dedicated review. API failure refuses an update;
|
|
|
173
173
|
stale projection as synchronized. Claude may retain its previous installation
|
|
174
174
|
after a refused update, whose existing local drift checks continue to apply.
|
|
175
175
|
|
|
176
|
+
## Reviewed plugin-hook attestation scope
|
|
177
|
+
|
|
178
|
+
A reviewed discovery input carries `pluginHooks: "reviewed-no-skill-injection"`.
|
|
179
|
+
That value is the reviewer's statement; Skills does not inspect hook behavior.
|
|
180
|
+
What the software currently enforces is narrower:
|
|
181
|
+
|
|
182
|
+
- It is point-in-time. It binds the listed sources as they were when the review
|
|
183
|
+
was applied, apart from the manifest projection described here and the
|
|
184
|
+
retired-root exemption described below. A manifest listed as a plain byte
|
|
185
|
+
witness with a non-null `sha256` is stored as a `claude-plugin-manifest-v1`
|
|
186
|
+
projection that ignores the descriptive fields `description`, `version`,
|
|
187
|
+
`author`, `homepage`, `repository`, `license` and `keywords`. The projection
|
|
188
|
+
also normalises whitespace and JSON string escaping; key order stays bound.
|
|
189
|
+
Every other manifest field, including `hooks`, `skills`, `commands` and
|
|
190
|
+
unknown fields, stays bound.
|
|
191
|
+
- It enforces one coupling only, and only for a Claude
|
|
192
|
+
`.claude-plugin/plugin.json` listed as a plain byte witness (`hashMode`
|
|
193
|
+
omitted or `"bytes"`) with a non-null `sha256`. The review must then also list
|
|
194
|
+
that plugin's `hooks/hooks.json` (when the file exists at review time) and any
|
|
195
|
+
string `hooks` target in the manifest, each as a plain byte witness with a
|
|
196
|
+
non-null hash. A non-string `hooks` value and a `hooks` target outside the
|
|
197
|
+
plugin root are refused. A manifest listed as `claude-plugin-manifest-v1`
|
|
198
|
+
(the form Skills writes into the stored policy) or as `path-bytes` triggers
|
|
199
|
+
no hook-file requirement. Nothing else is required.
|
|
200
|
+
- A `hooks/hooks.json` that is absent at review time and appears later is
|
|
201
|
+
detected if the review pins it with `sha256: null` or covers it with a
|
|
202
|
+
reviewed `directories` membership witness on the plugin root or its `hooks/`
|
|
203
|
+
directory. Such a witness, from `captureDiscoveryDirectories(paths)`, binds
|
|
204
|
+
the recursive path and type of every member, so every check detects added or
|
|
205
|
+
removed files and directories under it, but not content changes.
|
|
206
|
+
- For plugins not covered by a `claude-plugin-registry` admission witness (see
|
|
207
|
+
[Discovery transition contract](#discovery-transition-contract)):
|
|
208
|
+
- other plugin files, such as hook modules (`register.ts`, `runtime.ts` and
|
|
209
|
+
their imports), `bin/` files and `package.json`, are bound only if the
|
|
210
|
+
review lists them. A content change to an unlisted file is not detected,
|
|
211
|
+
and an added or removed file is detected only under a reviewed
|
|
212
|
+
`directories` witness that covers it;
|
|
213
|
+
- the review is not anchored to the roots registered in
|
|
214
|
+
`~/.claude/plugins/installed_plugins.json`. When a review is present, the
|
|
215
|
+
enabled-plugin walk does not run, so Skills does not check that the listed
|
|
216
|
+
plugin roots are the registered ones or that every enabled plugin is listed.
|
|
217
|
+
- A `claude-plugin-registry` witness covers its admitted, receipt-backed
|
|
218
|
+
plugins on every check. It re-reads `installed_plugins.json`, requires each
|
|
219
|
+
managed row's `installPath` to be `cache/<marketplace>/<plugin>/<version>`,
|
|
220
|
+
checks every file in every retained cache version against the admission
|
|
221
|
+
receipts without the review listing them, and binds unmanaged rows exactly.
|
|
222
|
+
- An entirely absent retired Claude version root exempts only its plain byte
|
|
223
|
+
sources (`hashMode` omitted or `"bytes"`, with no `format`, `fields` or
|
|
224
|
+
`managedPlugins`). Such a source with a non-null `sha256` is also the only
|
|
225
|
+
thing that identifies the root, and the root qualifies only while it stays
|
|
226
|
+
entirely absent and the reviewed settings and registry select a different,
|
|
227
|
+
verified version. Every other source under that root is re-hashed as usual,
|
|
228
|
+
and one whose file existed at review no longer matches its hash, so the
|
|
229
|
+
check refuses. That includes the plugin manifest in the
|
|
230
|
+
`claude-plugin-manifest-v1` form Skills stores, so a stored review that lists
|
|
231
|
+
the retired version's manifest refuses until a fresh review replaces it. A
|
|
232
|
+
`directories` witness that covers the retired root is not exempt either. If
|
|
233
|
+
the root reappears, its sources are checked again.
|
|
234
|
+
- It is detection-only. Later checks re-hash the stored sources and re-walk any
|
|
235
|
+
reviewed `directories` witnesses. Drift makes the
|
|
236
|
+
Skills hooks refuse with `NATIVE_SKILL_DRIFT`; it does not stop, unload or
|
|
237
|
+
sandbox plugin code, which the native client keeps running.
|
|
238
|
+
- It is a tripwire, not a security boundary. A process running as the same user
|
|
239
|
+
can rewrite the plugin files and the managed policy that holds the witnesses.
|
|
240
|
+
|
|
241
|
+
For plugins outside receipt-backed admission, a stronger witness anchored to the
|
|
242
|
+
registered plugin roots is planned and not yet implemented.
|
|
243
|
+
|
|
176
244
|
## Marketplace registry timestamps
|
|
177
245
|
|
|
178
246
|
For an explicitly reviewed `known_marketplaces.json`,
|
|
@@ -196,6 +264,123 @@ not proof of a timestamp-only change: review the complete current registration
|
|
|
196
264
|
and its complementary sources before explicitly replacing an old witness.
|
|
197
265
|
Capturing this witness does not write a policy or approve native registration.
|
|
198
266
|
|
|
267
|
+
## Marketplace plugin entry witness
|
|
268
|
+
|
|
269
|
+
`claude-marketplace-entry-v1` witnesses one plugin entry of a Claude
|
|
270
|
+
`marketplace.json`, for a plugin whose only declaration is its marketplace entry
|
|
271
|
+
(for example a `strict: false` entry with no `plugin.json`, such as
|
|
272
|
+
`swift-lsp@claude-plugins-official`). A bytes witness of the whole catalog
|
|
273
|
+
drifts every time Claude refreshes the marketplace by itself; this mode moves
|
|
274
|
+
only when something that decides how the selected entry resolves or what it
|
|
275
|
+
injects changes.
|
|
276
|
+
|
|
277
|
+
The source names an absolute, normalized `<root>/.claude-plugin/marketplace.json`
|
|
278
|
+
path, the exact marketplace `name` and the exact plugin entry `name`:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{ "path": "/home/user/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json",
|
|
282
|
+
"hashMode": "claude-marketplace-entry-v1", "marketplace": "claude-plugins-official",
|
|
283
|
+
"plugin": "swift-lsp", "sha256": "<digest>" }
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
It is accepted only in explicit reviewed Claude discovery. Paths with `..`,
|
|
287
|
+
`//`, a trailing slash, control characters, another file name or another parent
|
|
288
|
+
directory refuse, as do names outside `[A-Za-z0-9][A-Za-z0-9._-]{0,127}`.
|
|
289
|
+
|
|
290
|
+
The digest is SHA-256 over the domain prefix
|
|
291
|
+
`hasna.skills.claude-marketplace-entry.v1\0` and canonical JSON (sorted object
|
|
292
|
+
keys, array order and number spelling kept, no whitespace) of:
|
|
293
|
+
|
|
294
|
+
- the marketplace `name`, `metadata.pluginRoot` (`null` when absent) and
|
|
295
|
+
`allowCrossMarketplaceDependenciesOn` (`null` when absent), for every entry
|
|
296
|
+
whether or not it declares `dependencies`: the root allowlist also governs
|
|
297
|
+
dependencies declared in the plugin's own `plugin.json`, which Claude's
|
|
298
|
+
auto-update and `/reload-plugins` act on. A present allowlist must be an
|
|
299
|
+
array of strings, so `null` only ever means absent;
|
|
300
|
+
- the selected entry projected to its bound keys: `name`, `source`, `strict`,
|
|
301
|
+
`defaultEnabled`, `dependencies`, `relevance`, `headers`, `headersHelper`,
|
|
302
|
+
`settings`, `userConfig`, `types`, `channels`, `skills`, `commands`, `agents`,
|
|
303
|
+
`hooks`, `mcpServers`, `lspServers`, `outputStyles`, `workflows`,
|
|
304
|
+
`experimental`, `themes` and `monitors`, each with its full value.
|
|
305
|
+
|
|
306
|
+
Key order: this canonical JSON sorts object keys at every level, under the
|
|
307
|
+
domain prefix above. That deliberately differs from `claude-plugin-manifest-v1`,
|
|
308
|
+
which keeps the manifest's own key order and has no domain prefix. Reordering
|
|
309
|
+
keys in the catalog therefore never moves this digest, while array order,
|
|
310
|
+
including the order of `allowCrossMarketplaceDependenciesOn`, stays bound.
|
|
311
|
+
|
|
312
|
+
The entry's display and catalog metadata is validated and omitted:
|
|
313
|
+
`$schema`, `description`, `version`, `author`, `homepage`, `repository`,
|
|
314
|
+
`license`, `keywords`, `category`, `tags`, `displayName` and `metadata`. A real
|
|
315
|
+
version or install-path change is still caught by the separate
|
|
316
|
+
`installed_plugins.json` witness, which this mode never replaces. Other entries,
|
|
317
|
+
the catalog `description`, `version`, `owner` and `$schema`,
|
|
318
|
+
`forceRemoveDeletedPlugins` and renames of other plugins do not move the digest.
|
|
319
|
+
Adding, removing or changing `allowCrossMarketplaceDependenciesOn` always does.
|
|
320
|
+
|
|
321
|
+
The mode fails closed. Capture and verification refuse when:
|
|
322
|
+
|
|
323
|
+
- the file is missing, a link, a special file, larger than 1 MiB, not strict
|
|
324
|
+
UTF-8, not one JSON object, has trailing content or a duplicate key;
|
|
325
|
+
- the marketplace `name` differs from the bound name, or `plugins` is not an
|
|
326
|
+
array;
|
|
327
|
+
- the selected entry is missing, appears twice, or has a case-only variant;
|
|
328
|
+
- `renames` maps the selected name to another entry or to `null`;
|
|
329
|
+
- the entry has a key outside the two lists above (including the directory
|
|
330
|
+
listing fields), `experimental` has a key other than `themes`, `monitors` or
|
|
331
|
+
`evals`, or an object `source` has an unknown type or a field outside that
|
|
332
|
+
type's documented fields (a `url` source may also carry `path`, as the
|
|
333
|
+
official catalog does);
|
|
334
|
+
- the catalog has a top-level key outside `$schema`, `name`, `owner`,
|
|
335
|
+
`plugins`, `description`, `version`, `metadata`, `forceRemoveDeletedPlugins`,
|
|
336
|
+
`allowCrossMarketplaceDependenciesOn` and `renames`, or a `metadata` key other
|
|
337
|
+
than `description`, `version` and `pluginRoot`;
|
|
338
|
+
- an omitted metadata field has the wrong type.
|
|
339
|
+
|
|
340
|
+
New fields are refused, never ignored, until a reviewed version of this mode
|
|
341
|
+
covers them. A refusal for an unknown key names the mode, the bound marketplace
|
|
342
|
+
name and the exact key path, and never echoes a value. Key names are bounded to
|
|
343
|
+
64 characters and JSON-quoted, with every character outside printable ASCII
|
|
344
|
+
escaped. A plain key extends the path with `.key`; any other key appears as
|
|
345
|
+
`["key"]`. For example:
|
|
346
|
+
|
|
347
|
+
```text
|
|
348
|
+
claude-marketplace-entry-v1: unknown top-level key "pluginSearchPaths" in claude-plugins-official
|
|
349
|
+
claude-marketplace-entry-v1: unknown key "metadata.skillRoot" in claude-plugins-official
|
|
350
|
+
claude-marketplace-entry-v1: unknown key "plugins[swift-lsp].futureInjector" in claude-plugins-official
|
|
351
|
+
claude-marketplace-entry-v1: unknown key "plugins[swift-lsp].source.script" in claude-plugins-official
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Inside a hook check the same text follows `NATIVE_SKILL_DRIFT:`. The only way
|
|
355
|
+
forward after such a refusal is a fresh human review of the changed catalog
|
|
356
|
+
and a guarded exact re-pin through hook installation: write the reviewed
|
|
357
|
+
witnesses to a discovery inputs file, preview `skills hook install --agent
|
|
358
|
+
claude --discovery-inputs <file>`, then run the same command with `--apply`.
|
|
359
|
+
While the unknown key is present this mode refuses capture too, so that review
|
|
360
|
+
must bind the catalog another way, for example an exact `bytes` witness from
|
|
361
|
+
`captureDiscoveryByteSources`, until a reviewed version of this mode covers the
|
|
362
|
+
key. There is no bypass, ignore list or relaxed mode.
|
|
363
|
+
|
|
364
|
+
The field lists follow the
|
|
365
|
+
[marketplace reference](https://code.claude.com/docs/en/plugins/marketplace-reference)
|
|
366
|
+
and the [plugin manifest reference](https://code.claude.com/docs/en/plugins/manifest-reference),
|
|
367
|
+
read on 2026-10-07.
|
|
368
|
+
|
|
369
|
+
Capture a witness with
|
|
370
|
+
`skills hook witness --kind claude-marketplace-entry-v1 --path <marketplace.json>
|
|
371
|
+
--marketplace <name> --plugin <name> --json` (or
|
|
372
|
+
`captureClaudeMarketplaceEntry(path, marketplace, plugin)`). Capture writes
|
|
373
|
+
nothing and does not authorize a review. Put the witness in reviewed discovery
|
|
374
|
+
inputs, keep the `settings.json`, `installed_plugins.json` and
|
|
375
|
+
`known_marketplaces.json` witnesses and the plugin's absence witnesses, and
|
|
376
|
+
preview `skills hook install --discovery-inputs <file>` before applying. Hook
|
|
377
|
+
installation never rewrites the catalog, so an entry change between planning
|
|
378
|
+
and applying refuses.
|
|
379
|
+
|
|
380
|
+
Install a runtime that recognizes `claude-marketplace-entry-v1`, including any
|
|
381
|
+
bundled copy of the verifier, before a policy carries it; an older runtime
|
|
382
|
+
refuses the whole policy because the hash mode is unknown.
|
|
383
|
+
|
|
199
384
|
## Claude settings preferences
|
|
200
385
|
|
|
201
386
|
`captureClaudeSettings(canonicalSettingsPath)` returns a versioned
|