dsh-plugin-inspector 0.1.0 → 0.2.1

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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `--from-npm` — inspecting a published package without installing it.
3
+ *
4
+ * This module is the only path in the tool that reaches a network, and it is
5
+ * separate from `inspect.ts` on purpose: a directory or tarball scan cannot
6
+ * arrive here, because nothing on that path imports this file. The steps are
7
+ * fixed and their order is the guarantee:
8
+ *
9
+ * 1. read the version document (~3 KB) — which already answers
10
+ * `hasInstallScript`, the install lifecycle scripts, and `dsh.bundle`;
11
+ * 2. download the tarball into memory;
12
+ * 3. verify `dist.integrity` **before** anything parses a byte of it;
13
+ * 4. decode in memory and analyse, exactly as the tarball path does.
14
+ *
15
+ * No subprocess, no disk write, no lifecycle script, and no `npm pack`.
16
+ * @module dsh-plugin-inspector/npm
17
+ */
18
+ import type { Report } from './model.ts';
19
+ import { type RegistryOptions, type ResolvedPackage } from './registry.ts';
20
+ /**
21
+ * The metadata pre-check, which needs no tarball.
22
+ *
23
+ * A caller sweeping many packages can read this for each of them at a few
24
+ * kilobytes apiece and decide which ones are worth downloading.
25
+ * @param spec - `<name>` or `<name>@<version>`.
26
+ * @param options - where to fetch from.
27
+ * @returns what the version document says.
28
+ * @throws RegistryError when the package or version does not resolve.
29
+ */
30
+ export declare function precheck(spec: string, options?: RegistryOptions): Promise<ResolvedPackage>;
31
+ /**
32
+ * Fetch a published package and inspect it in memory.
33
+ * @param spec - `<name>` or `<name>@<version>`; no version means the `latest` tag.
34
+ * @param options - where to fetch from.
35
+ * @returns the complete report, carrying the registry provenance.
36
+ * @throws RegistryError when the package cannot be resolved, fetched, or verified.
37
+ * @throws SourceError or ManifestError when the fetched tarball is not a package.
38
+ */
39
+ export declare function inspectFromNpm(spec: string, options?: RegistryOptions): Promise<Report>;
40
+ //# sourceMappingURL=npm.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Fetching a published package from an npm registry, and proving the bytes are
3
+ * the ones the registry vouched for.
4
+ *
5
+ * This is the one module in the tool that opens a socket, and it exists behind
6
+ * one explicit flag. A network fetch is not execution — nothing here installs,
7
+ * unpacks to disk, or runs a lifecycle script — but it is a side effect the
8
+ * tool's other two modes do not have, so it is never reached implicitly: a
9
+ * directory or tarball scan cannot get here, because neither imports this
10
+ * module.
11
+ *
12
+ * The order of operations is the security property. The packument is read
13
+ * first, which is ~3 KB and already answers `hasInstallScript`, the install
14
+ * lifecycle scripts, and whether the package declares `dsh.bundle` — a
15
+ * pre-check that needs no tarball at all. Only then is the tarball fetched, and
16
+ * its `dist.integrity` hash is verified **before** any byte of it reaches the
17
+ * tar parser. A hash mismatch is a refusal, not a warning: the whole point of
18
+ * the mode is that the analysed bytes are the published bytes.
19
+ * @module dsh-plugin-inspector/registry
20
+ */
21
+ /** The public npm registry, used when no other is named. */
22
+ export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org";
23
+ /** Largest packument the tool will read. A version document is a few kilobytes. */
24
+ export declare const MAX_METADATA_BYTES: number;
25
+ /**
26
+ * Largest compressed tarball the tool will download. The decompressed stream is
27
+ * capped separately, and lower, by the tar reader.
28
+ */
29
+ export declare const MAX_TARBALL_BYTES: number;
30
+ /** Thrown when a package cannot be resolved, fetched, or verified. */
31
+ export declare class RegistryError extends Error {
32
+ }
33
+ /** One `<name>` or `<name>@<version>` argument, split. */
34
+ export interface PackageSpec {
35
+ readonly name: string;
36
+ /** A version or dist-tag, or `null` to mean the `latest` tag. */
37
+ readonly version: string | null;
38
+ }
39
+ /**
40
+ * What the packument says, before anything is downloaded. Every field here
41
+ * costs one small request and no tarball.
42
+ */
43
+ export interface ResolvedPackage {
44
+ readonly name: string;
45
+ readonly version: string;
46
+ readonly tarball: string;
47
+ /** The registry's own SRI string, e.g. `sha512-…`, or `null` on old packages. */
48
+ readonly integrity: string | null;
49
+ /** The legacy SHA-1 digest, the only check available when `integrity` is absent. */
50
+ readonly shasum: string | null;
51
+ /** The registry's own flag, which is set when npm would run an install script. */
52
+ readonly hasInstallScript: boolean;
53
+ /** Install lifecycle script names the manifest declares. */
54
+ readonly lifecycleScripts: readonly string[];
55
+ /** The `dsh.bundle.patch` value, which is what makes a package a mounted layer. */
56
+ readonly bundlePatch: string | null;
57
+ /** Bytes of metadata read to learn all of the above. */
58
+ readonly metadataBytes: number;
59
+ }
60
+ /** How a fetched tarball was verified. */
61
+ export interface VerifiedTarball {
62
+ readonly bytes: Buffer;
63
+ /** The algorithm the digest was taken with, e.g. `sha512`. */
64
+ readonly algorithm: string;
65
+ /** The digest that matched, in the registry's own encoding. */
66
+ readonly digest: string;
67
+ }
68
+ /** Where to fetch from. */
69
+ export interface RegistryOptions {
70
+ /** Registry base URL, without a trailing slash. Defaults to {@link DEFAULT_REGISTRY}. */
71
+ readonly registry?: string;
72
+ /** Injected for tests; defaults to the global `fetch`. */
73
+ readonly fetch?: typeof globalThis.fetch;
74
+ }
75
+ /**
76
+ * Split a `<name>` or `<name>@<version>` argument.
77
+ *
78
+ * The `@` that starts a scope is not a separator, so `@scope/name` has no
79
+ * version and `@scope/name@1.2.3` has one.
80
+ * @param spec - the argument as typed.
81
+ * @returns the package name and the requested version, if any.
82
+ * @throws RegistryError when the name or version is not one the registry accepts.
83
+ */
84
+ export declare function parseSpec(spec: string): PackageSpec;
85
+ /**
86
+ * Read the packument for one version, which is the pre-check.
87
+ * @param spec - the package name and requested version.
88
+ * @param options - where to fetch from.
89
+ * @returns everything the metadata says, including the tarball URL and its hash.
90
+ * @throws RegistryError when the package or version does not resolve, or when
91
+ * the document does not carry a tarball URL on the registry's own origin.
92
+ */
93
+ export declare function resolvePackage(spec: PackageSpec, options?: RegistryOptions): Promise<ResolvedPackage>;
94
+ /**
95
+ * Check downloaded bytes against what the registry published.
96
+ *
97
+ * `dist.integrity` is preferred and is a real check. `dist.shasum` is SHA-1 and
98
+ * is accepted only when there is no `integrity` field at all, which happens on
99
+ * packages published before npm 5; it is recorded in the report as the weaker
100
+ * check it is. No digest at all is a refusal, because "verified" would then be
101
+ * a claim the tool cannot make.
102
+ * @param bytes - the downloaded tarball.
103
+ * @param resolved - what the packument said about it.
104
+ * @returns the algorithm and digest that matched.
105
+ * @throws RegistryError on a mismatch, an unusable digest, or no digest at all.
106
+ */
107
+ export declare function verifyIntegrity(bytes: Buffer, resolved: ResolvedPackage): VerifiedTarball;
108
+ /**
109
+ * Download a resolved package's tarball and verify it before returning it.
110
+ *
111
+ * Nothing parses the bytes on the way in — they are counted against a ceiling
112
+ * and hashed, and a failed hash throws before any caller can see them.
113
+ * @param resolved - the packument reading for the version to fetch.
114
+ * @param options - where to fetch from.
115
+ * @returns the verified tarball, in memory.
116
+ * @throws RegistryError on a transport failure, an oversized body, or a hash mismatch.
117
+ */
118
+ export declare function fetchVerifiedTarball(resolved: ResolvedPackage, options?: RegistryOptions): Promise<VerifiedTarball>;
119
+ //# sourceMappingURL=registry.d.ts.map
@@ -37,9 +37,15 @@ export declare const DEFAULT_LIMITS: ReadLimits;
37
37
  /** Thrown when the target cannot be read at all, which is exit code 2, not a finding. */
38
38
  export declare class SourceError extends Error {
39
39
  }
40
+ /**
41
+ * Where the analysed bytes came from. `registry` is a tarball too — the same
42
+ * in-memory decoding, over bytes fetched by `--from-npm` instead of read from
43
+ * disk — and is kept distinct so a report says which one it was.
44
+ */
45
+ export type SourceKind = 'directory' | 'tarball' | 'registry';
40
46
  /** The analysed package, decoded into memory. */
41
47
  export interface PluginSource {
42
- readonly kind: 'directory' | 'tarball';
48
+ readonly kind: SourceKind;
43
49
  /** The target as the user gave it, resolved to an absolute path. */
44
50
  readonly path: string;
45
51
  /** Package-relative POSIX path to UTF-8 text, for every readable text file. */
@@ -68,4 +74,18 @@ export declare const MAX_STREAM_BYTES: number;
68
74
  * tarball, or holds no `package.json`.
69
75
  */
70
76
  export declare function loadSource(target: string, limits?: ReadLimits): Promise<PluginSource>;
77
+ /**
78
+ * Decode an npm tarball held in memory, for bytes that never touched the disk.
79
+ *
80
+ * The only caller is the `--from-npm` path, which fetches a tarball and
81
+ * verifies its `dist.integrity` hash before handing the buffer here. Reading it
82
+ * goes through the same `Parser` as the on-disk path, so the "no extraction,
83
+ * ever" property covers both.
84
+ * @param bytes - the complete tarball, gzipped or not.
85
+ * @param label - what to report as the target path, e.g. `npm:pkg@1.2.3`.
86
+ * @param limits - resource ceilings; defaults to {@link DEFAULT_LIMITS}.
87
+ * @returns the decoded package.
88
+ * @throws SourceError when the buffer is not a readable npm tarball.
89
+ */
90
+ export declare function loadTarballBuffer(bytes: Buffer, label: string, limits?: ReadLimits): Promise<PluginSource>;
71
91
  //# sourceMappingURL=source.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-inspector",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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",
@@ -56,6 +56,7 @@
56
56
  "test": "vitest run --config vitest.config.ts",
57
57
  "test:coverage": "vitest run --config vitest.config.ts --coverage",
58
58
  "inspect": "node --experimental-strip-types src/cli.ts",
59
+ "sweep": "node --experimental-strip-types scripts/ecosystem-sweep.ts",
59
60
  "test:e2e": "pnpm run build && vitest run --config vitest.e2e.config.ts"
60
61
  }
61
62
  }