dsh-plugin-inspector 0.6.0 → 0.8.0
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 +10 -8
- package/lib/attestation.js +374 -0
- package/lib/checks/tier-a.js +110 -1
- package/lib/checks/tier-b.js +341 -28
- package/lib/checks/tier-c.js +146 -18
- package/lib/index.js +2 -1
- package/lib/inspect.js +7 -2
- package/lib/knowledge.js +289 -25
- package/lib/npm.js +58 -5
- package/lib/registry.js +54 -0
- package/lib/report.js +58 -7
- package/lib/syntax.js +135 -0
- package/lib/types/attestation.d.ts +173 -0
- package/lib/types/checks/input.d.ts +7 -0
- package/lib/types/checks/tier-c.d.ts +5 -3
- package/lib/types/index.d.ts +2 -1
- package/lib/types/inspect.d.ts +5 -2
- package/lib/types/knowledge.d.ts +212 -4
- package/lib/types/model.d.ts +13 -1
- package/lib/types/npm.d.ts +12 -2
- package/lib/types/registry.d.ts +40 -0
- package/lib/types/syntax.d.ts +51 -0
- package/package.json +2 -1
package/lib/types/knowledge.d.ts
CHANGED
|
@@ -11,9 +11,17 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
13
|
* Harness version these tables were transcribed from — the version string in
|
|
14
|
-
* the
|
|
14
|
+
* the shipped bundles' own `package.json`, which is `dsh`'s own version.
|
|
15
|
+
*
|
|
16
|
+
* Re-verified against `0.1.2-rc.1`, the release npm tags `latest`, by
|
|
17
|
+
* extracting each table from the published packages and diffing it against the
|
|
18
|
+
* one here. What moved: thirteen row ids the bundles gained against three they
|
|
19
|
+
* dropped, four rows the base layer inserts that only the web bundle carried,
|
|
20
|
+
* eleven seam keys against one dropped, and two waterfall events against one
|
|
21
|
+
* dropped. What did not: the sandbox trap table, the teardown surfaces, and
|
|
22
|
+
* every `DECISION_EVENT_DEFAULTS` citation, all re-read at this release.
|
|
15
23
|
*/
|
|
16
|
-
export declare const HARNESS_REFERENCE = "0.1.
|
|
24
|
+
export declare const HARNESS_REFERENCE = "0.1.2-rc.1";
|
|
17
25
|
/** The shipped bundles, each of which is one patch layer over the profile root. */
|
|
18
26
|
export type BundleName = 'base' | 'headless' | 'web-app';
|
|
19
27
|
/**
|
|
@@ -62,7 +70,99 @@ export declare const SECURITY_ROW_IDS: ReadonlyMap<string, string>;
|
|
|
62
70
|
* replaces a core service for every consumer in its scope.
|
|
63
71
|
*/
|
|
64
72
|
export declare const SEAM_KEYS: ReadonlySet<string>;
|
|
65
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* The subset of {@link SEAM_KEYS} whose replacement removes a constraint.
|
|
75
|
+
*
|
|
76
|
+
* `authorization` is the registry of flows that obtain a credential through a
|
|
77
|
+
* conversation with the user, so providing it means owning that conversation.
|
|
78
|
+
* That is the same class of substitution as `credentials`, which this set
|
|
79
|
+
* already holds. `fileReferences` decides which paths are offered for
|
|
80
|
+
* completion and `agentTeams` is the team form of `subagents`, which is
|
|
81
|
+
* deliberately not here either, so neither of those is in this set.
|
|
82
|
+
*
|
|
83
|
+
* Seven of the eleven keys `0.1.2-rc.1` adds are Remote controllers: host
|
|
84
|
+
* services that own one `ctx.remote.*` namespace the browser client calls
|
|
85
|
+
* across the wire. A controller belongs here only when the traffic a
|
|
86
|
+
* substitution redirects to it carries a secret, an execution boundary, or a
|
|
87
|
+
* decision. A controller that forwards its seam's own verbs and adds a wire
|
|
88
|
+
* failure vocabulary does not, because the seam it fronts is reachable from
|
|
89
|
+
* `ctx` without substituting anything.
|
|
90
|
+
*
|
|
91
|
+
* Included:
|
|
92
|
+
* - `credentialsController` is what a browser configuration page calls to store
|
|
93
|
+
* a credential. `set(ref, value)` receives the plaintext secret and hands it
|
|
94
|
+
* to `ctx.credentials`
|
|
95
|
+
* (`@deepseek-ai/dsh-api-settings-controller/lib/index.js:171`), and
|
|
96
|
+
* `projectCredentialInfo` at `lib/index.js:78` is what holds a `describe`
|
|
97
|
+
* answer to the three fields `CredentialInfo` declares. A layer that provides
|
|
98
|
+
* it takes both halves: every secret typed into the settings page, and the
|
|
99
|
+
* freedom to answer a read with the stored value.
|
|
100
|
+
* - `settingsController` passes `redactSecrets: true` on every remote read
|
|
101
|
+
* (`@deepseek-ai/dsh-api-settings-controller/lib/index.js:429` and `:544`),
|
|
102
|
+
* which is what keeps a `role('secret')` field out of a settings response;
|
|
103
|
+
* `@deepseek-ai/dsh-web-search-deepseek/lib/index.js:245` declares one, an
|
|
104
|
+
* `apiKey`. Its `update`, `replace` and `mutate` verbs carry that same field's
|
|
105
|
+
* value in plaintext from the configuration page. Providing it puts the layer
|
|
106
|
+
* on both directions of a secret's path.
|
|
107
|
+
* - `sessionController` resolves each new Session's cwd from the wire request
|
|
108
|
+
* and hands it to `ensureSession`
|
|
109
|
+
* (`@deepseek-ai/dsh-api-session-controller/lib/index.js:574`), and
|
|
110
|
+
* `@deepseek-ai/dsh-sandbox-policy` resolves that immutable cwd as the
|
|
111
|
+
* `workspace-write` root the enforcing filesystem, bash and terminal backends
|
|
112
|
+
* fence against. Its `prompt` verb builds the message admitted to the agent
|
|
113
|
+
* under `source.kind: 'user'` (`lib/index.js:731`). Providing it chooses the
|
|
114
|
+
* sandbox root for every session created from the client, and the text that
|
|
115
|
+
* reaches the model under the user's own source label.
|
|
116
|
+
* - `webhookRuntime` is what a provider adapter such as
|
|
117
|
+
* `@deepseek-ai/dsh-webhook-github` dispatches verified deliveries into. Its
|
|
118
|
+
* one built-in action creates a Session from a rule result whose fields
|
|
119
|
+
* include `workspacePath`, `permissionPreset` and `prompt`
|
|
120
|
+
* (`@deepseek-ai/dsh-webhook/lib/types/types.d.ts`), and
|
|
121
|
+
* `createWebhookSession` applies that preset through
|
|
122
|
+
* `ctx.permissionPresets.set` before admitting the prompt
|
|
123
|
+
* (`@deepseek-ai/dsh-webhook/lib/types/session.js:94`, `:117`, `:120`).
|
|
124
|
+
* Providing it picks the approval and sandbox preset for an agent started by
|
|
125
|
+
* a remote delivery with no user present.
|
|
126
|
+
*
|
|
127
|
+
* Excluded:
|
|
128
|
+
* - `workspaceController` forwards `request.path` to
|
|
129
|
+
* `ctx.workspaceRegistry.create` unchanged and adds an ordering queue and
|
|
130
|
+
* error mapping (`@deepseek-ai/dsh-api-workspace-controller/lib/index.js:196`
|
|
131
|
+
* to `:212`). The registry it fronts is `workspaceRegistry`, which is not
|
|
132
|
+
* here, so the substitution reaches nothing the seam does not already offer.
|
|
133
|
+
* - `directoryPickerController` is three delegations to
|
|
134
|
+
* `ctx.directoryPicker.capability()` behind a check that refuses a verb the
|
|
135
|
+
* composed backend does not serve
|
|
136
|
+
* (`@deepseek-ai/dsh-api-workspace-controller/lib/index.js:423` to `:470`).
|
|
137
|
+
* Path fencing lives in the backend, and `directoryPicker` is not here.
|
|
138
|
+
* - `sessionFileReferences` is the Remote adapter over `fileReferences`, which
|
|
139
|
+
* is excluded above for the same reason: the traffic is path candidates
|
|
140
|
+
* offered for completion.
|
|
141
|
+
* - `sessionSkillCatalog` answers with `SkillListValue`, declared as the list
|
|
142
|
+
* for one Session's human-facing composer
|
|
143
|
+
* (`@deepseek-ai/dsh-api-session-controller/lib/types/types.d.ts:213` to
|
|
144
|
+
* `:227`), and its only consumer in this release is the client skill picker
|
|
145
|
+
* (`@deepseek-ai/dsh-client-ui-skill/lib/client.js:236`). Skill text reaches a
|
|
146
|
+
* model through `ctx.skills.list()` in
|
|
147
|
+
* `@deepseek-ai/dsh-tool-skill/lib/index.js:145`, which is the `skills` seam.
|
|
148
|
+
* - `subagentModelSelection` is a settings owner answering `{ enabled,
|
|
149
|
+
* allowedModels }`, sampled when an Agent receives its delegation tools. Model
|
|
150
|
+
* routing is `llm` and delegation is `subagents`; neither is here.
|
|
151
|
+
* - `deepseekLlmApiExtensions` hands a substitute the serialized request body
|
|
152
|
+
* and merges the fields it returns, but
|
|
153
|
+
* `@deepseek-ai/dsh-llm-deepseek/lib/index.js:1748` rejects any extension
|
|
154
|
+
* field colliding with the base request, so `messages`, `tools` and `model`
|
|
155
|
+
* are not writable through it. The constraint is in the adapter, not the
|
|
156
|
+
* registry the substitution replaces.
|
|
157
|
+
* - `inspector` is declared by the catalogue and implemented by no shipped
|
|
158
|
+
* package: in `0.1.2-rc.1` the key, `InspectorJsonValue` and
|
|
159
|
+
* `CordisRuntimeTreeReader` occur only in
|
|
160
|
+
* `@deepseek-ai/dsh-tool-cordis/lib/index.js`, and nothing provides or reads
|
|
161
|
+
* `ctx.inspector`. No composed row enforces anything through it, so a
|
|
162
|
+
* substitution displaces nothing. This is the one entry decided from the
|
|
163
|
+
* catalogue rather than from an implementation; a release that ships one is a
|
|
164
|
+
* reason to decide it again.
|
|
165
|
+
*/
|
|
66
166
|
export declare const SECURITY_SEAM_KEYS: ReadonlySet<string>;
|
|
67
167
|
/**
|
|
68
168
|
* Waterfall events, from `EVENT_API` in the api-catalog. A listener on one of
|
|
@@ -70,9 +170,24 @@ export declare const SECURITY_SEAM_KEYS: ReadonlySet<string>;
|
|
|
70
170
|
* without calling it short-circuits the chain including the built-in behavior.
|
|
71
171
|
*
|
|
72
172
|
* Note there is no `fs/read-intent` — the intent family is write and edit only.
|
|
173
|
+
*
|
|
174
|
+
* `0.1.2-rc.1` renames `tools/code-dispatch-log` to `tools/ptc-dispatch-log`
|
|
175
|
+
* and adds `user-questions/request`. Both replace content in a durable log copy
|
|
176
|
+
* or answer a pending request; neither is an `emit` event, so both hand a
|
|
177
|
+
* listener the trailing `next`.
|
|
73
178
|
*/
|
|
74
179
|
export declare const WATERFALL_EVENTS: ReadonlySet<string>;
|
|
75
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Waterfall events whose short-circuit removes a decision the user would
|
|
182
|
+
* otherwise make.
|
|
183
|
+
*
|
|
184
|
+
* `user-questions/request` is here for the same reason `approval/request` is:
|
|
185
|
+
* `ctx.userQuestions` pauses a tool call until a human answers, and the
|
|
186
|
+
* answerers that put the question on a screen are listeners in the chain rather
|
|
187
|
+
* than the inner callback (`@deepseek-ai/dsh-user-questions/lib/index.js:69`).
|
|
188
|
+
* A listener that returns an answer without calling `next()` answers on the
|
|
189
|
+
* user's behalf and the question is never shown.
|
|
190
|
+
*/
|
|
76
191
|
export declare const DECISION_EVENTS: ReadonlySet<string>;
|
|
77
192
|
/**
|
|
78
193
|
* Globals the dynamic-package sandbox (`cordis-host-runner/src/sandbox.ts`)
|
|
@@ -188,4 +303,97 @@ export declare const HARNESS_INERT_CALLS: ReadonlySet<string>;
|
|
|
188
303
|
* in code, and it is plain YAML.
|
|
189
304
|
*/
|
|
190
305
|
export declare const SERVICE_REMAPPING_FIELDS: readonly string[];
|
|
306
|
+
/**
|
|
307
|
+
* How a Cordis waterfall listener delegates, and what happens when it does not.
|
|
308
|
+
*
|
|
309
|
+
* Read out of the installed `@deepseek-ai/cordis@4.0.2` build,
|
|
310
|
+
* `lib/index.js:317-327`:
|
|
311
|
+
*
|
|
312
|
+
* ```js
|
|
313
|
+
* waterfall(...args) {
|
|
314
|
+
* const cbs = this.dispatch("waterfall", args);
|
|
315
|
+
* const inner = args.pop();
|
|
316
|
+
* const next = () => { return (cbs.shift() ?? inner)(...args); };
|
|
317
|
+
* args.push(next);
|
|
318
|
+
* return next();
|
|
319
|
+
* }
|
|
320
|
+
* ```
|
|
321
|
+
*
|
|
322
|
+
* `next` is the trailing argument every listener receives, and `inner` is the
|
|
323
|
+
* harness's own built-in behavior. A listener that returns without calling
|
|
324
|
+
* `next()` therefore ends the chain: neither the listeners still in `cbs` nor
|
|
325
|
+
* `inner` run.
|
|
326
|
+
*
|
|
327
|
+
* The scope of that is one dispatch, not the registry. `dispatch()` builds
|
|
328
|
+
* `cbs` with `.filter(…).map(…)`, which allocates, so `this._hooks[name]` is
|
|
329
|
+
* never touched and every skipped listener is registered and runs normally on
|
|
330
|
+
* the next dispatch. The precise word is veto, not removal — Cordis's own
|
|
331
|
+
* JSDoc at `lib/index.js:311-313` says "vetoes the rest of the chain, including
|
|
332
|
+
* the built-in behavior". Removal is a different capability with a different
|
|
333
|
+
* reach, and it has its own table below.
|
|
334
|
+
*/
|
|
335
|
+
export declare const WATERFALL_NEXT_PARAMETER = "next";
|
|
336
|
+
/**
|
|
337
|
+
* What each decision waterfall's built-in `next` settles on when no listener
|
|
338
|
+
* claims the dispatch, transcribed from the installed harness `0.1.2-rc.1`.
|
|
339
|
+
*
|
|
340
|
+
* This is what a listener that never calls `next()` replaces. The inner
|
|
341
|
+
* callback is the last argument at each site:
|
|
342
|
+
* - `tools/pre-execute` — `@deepseek-ai/dsh-tools/lib/index.js:3117`,
|
|
343
|
+
* `() => Promise.resolve({ kind: "allow" })`
|
|
344
|
+
* - `tools/execute` — `dsh-tools/lib/index.js:3214`,
|
|
345
|
+
* `() => this.dispatchToolBody(mutableExec)`, so vetoing it substitutes the
|
|
346
|
+
* body of the tool call itself
|
|
347
|
+
* - `approval/request` — `@deepseek-ai/dsh-user-approval/lib/index.js:179`,
|
|
348
|
+
* `() => Promise.resolve("unavailable")`, and the surface that would ask the
|
|
349
|
+
* user is one of the listeners in the chain rather than the inner callback
|
|
350
|
+
* - `user-questions/request` —
|
|
351
|
+
* `@deepseek-ai/dsh-user-questions/lib/index.js:67`, the `noAnswerer`
|
|
352
|
+
* callback passed at `:69`, which rejects with a `UserQuestionError` carrying
|
|
353
|
+
* code `NO_PROVIDER`
|
|
354
|
+
*
|
|
355
|
+
* The three tables in this module that name events (`WATERFALL_EVENTS`,
|
|
356
|
+
* `DECISION_EVENTS`, and this one) are keyed to {@link HARNESS_REFERENCE}.
|
|
357
|
+
*/
|
|
358
|
+
export declare const DECISION_EVENT_DEFAULTS: ReadonlyMap<string, string>;
|
|
359
|
+
/**
|
|
360
|
+
* Receivers whose members name a plugin context.
|
|
361
|
+
*
|
|
362
|
+
* The same set the Tier C detached-member check guards on, minus `process`:
|
|
363
|
+
* a seam is read off the context, never off `process`.
|
|
364
|
+
*/
|
|
365
|
+
export declare const CONTEXT_RECEIVERS: ReadonlySet<string>;
|
|
366
|
+
/**
|
|
367
|
+
* Array and collection methods that change the receiver rather than reading it.
|
|
368
|
+
*
|
|
369
|
+
* Used to tell a write into a service's internals from a read of them. The
|
|
370
|
+
* distinction is not academic: `dsh-dlp` reads
|
|
371
|
+
* `ctx.events._hooks['approval/request']?.length` to decide whether an ask
|
|
372
|
+
* would reach a human, which is an honest use of the same property a hostile
|
|
373
|
+
* layer splices.
|
|
374
|
+
*/
|
|
375
|
+
export declare const MUTATING_METHODS: ReadonlySet<string>;
|
|
376
|
+
/** One Cordis bookkeeping surface that owns other plugins' registrations. */
|
|
377
|
+
export interface TeardownSurface {
|
|
378
|
+
/** The member read off the context, e.g. `events`. */
|
|
379
|
+
readonly service: string;
|
|
380
|
+
/** The member read off that, e.g. `_hooks`. */
|
|
381
|
+
readonly member: string;
|
|
382
|
+
/**
|
|
383
|
+
* True when merely naming the surface is the finding. False when only a
|
|
384
|
+
* write counts, because reading it is something an honest plugin does.
|
|
385
|
+
*/
|
|
386
|
+
readonly readIsEnough: boolean;
|
|
387
|
+
/** What reaching it does, phrased for a report. */
|
|
388
|
+
readonly effect: string;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Cordis internals through which one plugin removes another plugin's
|
|
392
|
+
* registrations. Read from the installed `@deepseek-ai/cordis@4.0.2` build.
|
|
393
|
+
*
|
|
394
|
+
* None of these is guarded by ownership. `ctx.events`, `ctx.registry` and
|
|
395
|
+
* `ctx.reflect` are own properties of the root context inherited by every
|
|
396
|
+
* child, so no `inject` declaration is needed to reach any of them.
|
|
397
|
+
*/
|
|
398
|
+
export declare const TEARDOWN_SURFACES: readonly TeardownSurface[];
|
|
191
399
|
//# sourceMappingURL=knowledge.d.ts.map
|
package/lib/types/model.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* would fire on every legitimate plugin and train users to ignore the tool.
|
|
10
10
|
* @module dsh-plugin-inspector/model
|
|
11
11
|
*/
|
|
12
|
+
import type { ProvenanceFact } from './attestation.ts';
|
|
12
13
|
/** How much a finding should weigh on an install decision. */
|
|
13
14
|
export type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
14
15
|
/**
|
|
@@ -25,7 +26,11 @@ export declare const SEVERITY_RANK: Readonly<Record<Severity, number>>;
|
|
|
25
26
|
export declare const SEVERITIES: readonly Severity[];
|
|
26
27
|
/** Where in the analysed package a finding was observed. */
|
|
27
28
|
export interface Evidence {
|
|
28
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Package-relative path of the file the finding came from, or the name of the
|
|
31
|
+
* registry field it came from when the finding is about what the registry
|
|
32
|
+
* published rather than about the package contents.
|
|
33
|
+
*/
|
|
29
34
|
readonly file: string;
|
|
30
35
|
/** A locator inside that file: a YAML path, a JSON pointer, or `line:column`. */
|
|
31
36
|
readonly path?: string;
|
|
@@ -93,6 +98,13 @@ export interface Facts {
|
|
|
93
98
|
readonly packageName: string;
|
|
94
99
|
readonly packageVersion: string;
|
|
95
100
|
readonly license: string | null;
|
|
101
|
+
/**
|
|
102
|
+
* What the registry says about where this tarball was built, and which of
|
|
103
|
+
* those claims this run checked. Always present: the two modes that read
|
|
104
|
+
* local bytes report `unavailable`, which is a different answer from a
|
|
105
|
+
* published package that has no attestation.
|
|
106
|
+
*/
|
|
107
|
+
readonly provenance: ProvenanceFact;
|
|
96
108
|
/** True when `package.json` declares `dsh.bundle.patch` — a mounted patch layer. */
|
|
97
109
|
readonly mountsAsBundle: boolean;
|
|
98
110
|
/** The declared patch path, verbatim and unresolved, or `null`. */
|
package/lib/types/npm.d.ts
CHANGED
|
@@ -7,10 +7,20 @@
|
|
|
7
7
|
* fixed and their order is the guarantee:
|
|
8
8
|
*
|
|
9
9
|
* 1. read the version document (~3 KB) — which already answers
|
|
10
|
-
* `hasInstallScript`, the install lifecycle scripts,
|
|
10
|
+
* `hasInstallScript`, the install lifecycle scripts, `dsh.bundle`, and
|
|
11
|
+
* whether the registry holds a provenance attestation at all;
|
|
11
12
|
* 2. download the tarball into memory;
|
|
12
13
|
* 3. verify `dist.integrity` **before** anything parses a byte of it;
|
|
13
|
-
* 4.
|
|
14
|
+
* 4. read the provenance attestation, when step 1 said there is one, and check
|
|
15
|
+
* it against the bytes step 3 vouched for;
|
|
16
|
+
* 5. decode in memory and analyse, exactly as the tarball path does.
|
|
17
|
+
*
|
|
18
|
+
* Step 4 is the only request this module makes that is not unconditional, and
|
|
19
|
+
* it is skipped for every package the version document says has no attestation
|
|
20
|
+
* — which on the measured corpus is 28 packages in 40. It never fails an
|
|
21
|
+
* analysis: an endpoint that is down or a bundle that does not decode leaves
|
|
22
|
+
* the provenance fact in state `unreadable`, which is a different answer from
|
|
23
|
+
* `absent` and is printed as one.
|
|
14
24
|
*
|
|
15
25
|
* No subprocess, no disk write, no lifecycle script, and no `npm pack`.
|
|
16
26
|
* @module dsh-plugin-inspector/npm
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -54,6 +54,13 @@ export interface ResolvedPackage {
|
|
|
54
54
|
readonly lifecycleScripts: readonly string[];
|
|
55
55
|
/** The `dsh.bundle.patch` value, which is what makes a package a mounted layer. */
|
|
56
56
|
readonly bundlePatch: string | null;
|
|
57
|
+
/**
|
|
58
|
+
* The predicate type of the provenance attestation the registry says it
|
|
59
|
+
* holds for this version, from `dist.attestations.provenance`, or `null` when
|
|
60
|
+
* it says it holds none. Reading it here is what keeps the attestation
|
|
61
|
+
* endpoint unasked for the majority of packages that have no attestation.
|
|
62
|
+
*/
|
|
63
|
+
readonly provenancePredicateType: string | null;
|
|
57
64
|
/** Bytes of metadata read to learn all of the above. */
|
|
58
65
|
readonly metadataBytes: number;
|
|
59
66
|
}
|
|
@@ -116,4 +123,37 @@ export declare function verifyIntegrity(bytes: Buffer, resolved: ResolvedPackage
|
|
|
116
123
|
* @throws RegistryError on a transport failure, an oversized body, or a hash mismatch.
|
|
117
124
|
*/
|
|
118
125
|
export declare function fetchVerifiedTarball(resolved: ResolvedPackage, options?: RegistryOptions): Promise<VerifiedTarball>;
|
|
126
|
+
/**
|
|
127
|
+
* The endpoint an npm-compatible registry serves a version's attestation
|
|
128
|
+
* bundle from.
|
|
129
|
+
*
|
|
130
|
+
* Built from the registry base URL rather than read out of
|
|
131
|
+
* `dist.attestations.url`, which is the opposite of how the tarball URL is
|
|
132
|
+
* handled and is deliberate: the tarball has to come from wherever the registry
|
|
133
|
+
* says because there is no other way to name it, so that URL is taken from the
|
|
134
|
+
* document and then refused unless it is same-origin. An attestation needs no
|
|
135
|
+
* such freedom. Constructing the path here means a doctored packument cannot
|
|
136
|
+
* redirect the request at all, not even to another path on the same host.
|
|
137
|
+
*
|
|
138
|
+
* The name and version are re-validated because both come out of the version
|
|
139
|
+
* document, which is registry-controlled: `name` is not necessarily the name
|
|
140
|
+
* that was asked for, and it is interpolated into a URL.
|
|
141
|
+
* @param registry - the registry base URL, without a trailing slash.
|
|
142
|
+
* @param name - the resolved package name.
|
|
143
|
+
* @param version - the resolved version.
|
|
144
|
+
* @returns the absolute URL.
|
|
145
|
+
* @throws RegistryError when the document's name or version would not address this endpoint.
|
|
146
|
+
*/
|
|
147
|
+
export declare function attestationUrl(registry: string, name: string, version: string): string;
|
|
148
|
+
/**
|
|
149
|
+
* Download an attestation document.
|
|
150
|
+
*
|
|
151
|
+
* Only ever called when the version document said there is one, so a package
|
|
152
|
+
* without provenance costs no request at all.
|
|
153
|
+
* @param url - the endpoint, from {@link attestationUrl}.
|
|
154
|
+
* @param options - where to fetch from.
|
|
155
|
+
* @returns the document as served.
|
|
156
|
+
* @throws RegistryError on a transport failure, a non-2xx status, or an oversized body.
|
|
157
|
+
*/
|
|
158
|
+
export declare function fetchAttestation(url: string, options?: RegistryOptions): Promise<Buffer>;
|
|
119
159
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readings of a syntax tree that both capability detection and readability
|
|
3
|
+
* detection need, and that must agree between them.
|
|
4
|
+
*
|
|
5
|
+
* Tier B matches a name; Tier C reports the names it could not match. If the
|
|
6
|
+
* two disagree about which expressions are constant, a package gets both a
|
|
7
|
+
* finding and a degrade for the same site, or neither. They agree because they
|
|
8
|
+
* ask the same function.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here evaluates anything. {@link foldConstantString} reads literals
|
|
11
|
+
* out of an already-parsed tree and concatenates them; it never constructs a
|
|
12
|
+
* function, and it never touches an identifier's value.
|
|
13
|
+
* @module dsh-plugin-inspector/syntax
|
|
14
|
+
*/
|
|
15
|
+
import ts from 'typescript';
|
|
16
|
+
/**
|
|
17
|
+
* The text a constant string expression holds, or `null` when the expression is
|
|
18
|
+
* not constant.
|
|
19
|
+
*
|
|
20
|
+
* Four forms, chosen because each one is a spelling of a name that a reader
|
|
21
|
+
* sees and a name-matching check does not: a literal, a `+` chain of them, a
|
|
22
|
+
* template whose every span is one, and `[…].join(…)` over an array of them.
|
|
23
|
+
* Anything reaching an identifier, a property, or any other call answers
|
|
24
|
+
* `null` — resolving those is value tracking, which this tool does not do and
|
|
25
|
+
* which Tier C exists to admit.
|
|
26
|
+
* @param node - the expression, or `undefined` for a missing argument.
|
|
27
|
+
* @returns the text, or `null`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function foldConstantString(node: ts.Node | undefined): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* The Node API that hands back a builtin module without `require` and without
|
|
32
|
+
* an `import` declaration, added in Node 22.3.
|
|
33
|
+
*
|
|
34
|
+
* It reaches the same modules the harness sandbox's `require` trap covers,
|
|
35
|
+
* from a call that sandbox never sees: the sandbox leaves
|
|
36
|
+
* `process` `undefined`, so inside it this expression throws, and a mounted
|
|
37
|
+
* bundle layer is not inside it.
|
|
38
|
+
* @see https://nodejs.org/api/process.html#processgetbuiltinmoduleid
|
|
39
|
+
*/
|
|
40
|
+
export declare const BUILTIN_MODULE_GETTER = "getBuiltinModule";
|
|
41
|
+
/**
|
|
42
|
+
* Whether a call is `process.getBuiltinModule(…)`.
|
|
43
|
+
*
|
|
44
|
+
* The receiver is required. `getBuiltinModule` pulled off `process` and bound
|
|
45
|
+
* to a bare name is not this — it is a detached member, which Tier C reports as
|
|
46
|
+
* dispatch it cannot follow.
|
|
47
|
+
* @param node - the call expression.
|
|
48
|
+
* @returns true when the call loads a builtin through `process`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isBuiltinModuleGetter(node: ts.CallExpression): boolean;
|
|
51
|
+
//# sourceMappingURL=syntax.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Know what a DeepSeek Harness plugin does before you install it — static pre-install analysis of a plugin directory or tarball",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ivan Tyshchenko",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"test:coverage": "vitest run --config vitest.config.ts --coverage",
|
|
58
58
|
"inspect": "node --experimental-strip-types src/cli.ts",
|
|
59
59
|
"sweep": "node --experimental-strip-types scripts/ecosystem-sweep.ts",
|
|
60
|
+
"sync": "node --experimental-strip-types scripts/harness-sync.ts",
|
|
60
61
|
"test:e2e": "pnpm run build && vitest run --config vitest.e2e.config.ts"
|
|
61
62
|
}
|
|
62
63
|
}
|