@perrylink/dsh-skill-pack-security-provider 1.3.0 → 2.0.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.
- package/lib/index.js +36 -8
- package/lib/types/index.d.ts +20 -8
- package/lib/types/vet/checks.d.ts +43 -0
- package/lib/types/vet/config.d.ts +41 -0
- package/lib/types/vet/engine.d.ts +24 -0
- package/lib/types/vet/fetch.d.ts +42 -0
- package/lib/types/vet/manifest.d.ts +53 -0
- package/lib/types/vet/redact.d.ts +14 -0
- package/lib/types/vet/report.d.ts +17 -0
- package/lib/types/vet/skills.d.ts +56 -0
- package/lib/types/vet/source.d.ts +58 -0
- package/lib/types/vet/tar.d.ts +35 -0
- package/lib/types/vet/tool.d.ts +16 -0
- package/lib/types/vet/vocabulary.d.ts +123 -0
- package/lib/types/vet/walk.d.ts +37 -0
- package/lib/vet/checks.js +755 -0
- package/lib/vet/config.js +36 -0
- package/lib/vet/engine.js +236 -0
- package/lib/vet/fetch.js +124 -0
- package/lib/vet/manifest.js +345 -0
- package/lib/vet/redact.js +46 -0
- package/lib/vet/report.js +95 -0
- package/lib/vet/skills.js +99 -0
- package/lib/vet/source.js +218 -0
- package/lib/vet/tar.js +137 -0
- package/lib/vet/tool.js +164 -0
- package/lib/vet/vocabulary.js +19 -0
- package/lib/vet/walk.js +147 -0
- package/pack/skills/dependency-audit/SKILL.md +5 -1
- package/pack/skills/incident-response/SKILL.md +1 -1
- package/pack/skills/prompt-injection-review/SKILL.md +1 -1
- package/pack/skills/secret-scan/SKILL.md +1 -1
- package/pack/skills/security-audit/SKILL.md +5 -1
- package/pack/skills/supply-chain-review/SKILL.md +5 -1
- package/pack/skills/threat-model/SKILL.md +1 -1
- package/pack/skills/vuln-intel/SKILL.md +1 -1
- package/pack/skills-en/dependency-audit/SKILL.md +5 -1
- package/pack/skills-en/incident-response/SKILL.md +1 -1
- package/pack/skills-en/prompt-injection-review/SKILL.md +1 -1
- package/pack/skills-en/secret-scan/SKILL.md +1 -1
- package/pack/skills-en/security-audit/SKILL.md +5 -1
- package/pack/skills-en/supply-chain-review/SKILL.md +5 -1
- package/pack/skills-en/threat-model/SKILL.md +1 -1
- package/pack/skills-en/vuln-intel/SKILL.md +1 -1
- package/package.json +47 -4
package/lib/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Provider plugin for dsh-skill-pack-security — "skill 教流程,插件自动执行".
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Registers two capabilities:
|
|
5
|
+
* 1. a SkillProvider on `ctx.skills` whose candidates are this package's own
|
|
6
|
+
* `skills/` directory (reusing the official `FileSystemSkillProvider`, so
|
|
7
|
+
* frontmatter parsing semantics are byte-identical with the built-in
|
|
8
|
+
* provider), and
|
|
9
|
+
* 2. the `plugin_vet` supply-chain gate tool on `ctx.tools`: an automated
|
|
10
|
+
* pre-install scanner (license / SBOM / commit pinning / malicious
|
|
11
|
+
* patterns / five-dimension risk score) whose every finding cites the
|
|
12
|
+
* pack skill section to continue as a manual audit.
|
|
13
|
+
*
|
|
14
|
+
* The scan engine is zero-dependency (Node built-ins only) and the plugin
|
|
15
|
+
* injects no prompt paragraphs: the gate lives entirely in the tool result,
|
|
16
|
+
* keeping the session persona untouched.
|
|
10
17
|
*
|
|
11
18
|
* @module dsh-skill-pack-security/provider
|
|
12
19
|
*/
|
|
@@ -15,12 +22,27 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
15
22
|
import { fileURLToPath } from 'node:url';
|
|
16
23
|
import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem';
|
|
17
24
|
import z from '@deepseek-ai/schemastery';
|
|
25
|
+
import { resolveVetConfig } from './vet/config.js';
|
|
26
|
+
import { buildVetTool } from './vet/tool.js';
|
|
18
27
|
export const name = 'skill-pack-security';
|
|
19
|
-
export const inject = ['skills'];
|
|
28
|
+
export const inject = ['skills', 'tools'];
|
|
20
29
|
export const Config = z.object({
|
|
21
30
|
watch: z.boolean().default(false),
|
|
22
31
|
language: z.union(['zh', 'en']).default('zh'),
|
|
23
32
|
skillsDir: z.string().min(1),
|
|
33
|
+
vet: z.object({
|
|
34
|
+
enable: z.boolean().default(true),
|
|
35
|
+
timeoutMs: z.natural().min(1000).max(300000).default(15000),
|
|
36
|
+
maxFiles: z.natural().min(1).max(20000).default(800),
|
|
37
|
+
maxFileBytes: z.natural().min(1024).max(16 * 1024 * 1024).default(256 * 1024),
|
|
38
|
+
maxExtractBytes: z.natural().min(1024).max(512 * 1024 * 1024).default(64 * 1024 * 1024),
|
|
39
|
+
maxDepNodes: z.natural().min(1).max(10000).default(600),
|
|
40
|
+
maxFindingsPerCheck: z.natural().min(1).max(100).default(12),
|
|
41
|
+
userAgent: z.string().max(200).default('dsh-skill-pack-security/2.0.0 (+https://github.com/PerryLink/dsh-skill-pack-security)'),
|
|
42
|
+
gate: z.object({
|
|
43
|
+
policy: z.union(['warn', 'deny']).default('warn'),
|
|
44
|
+
}),
|
|
45
|
+
}),
|
|
24
46
|
});
|
|
25
47
|
/** Directory of this module: `provider/src` under tsx or `provider/lib` when built. */
|
|
26
48
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
@@ -79,4 +101,10 @@ export function apply(ctx, config = {}) {
|
|
|
79
101
|
watch: config.watch ?? false,
|
|
80
102
|
}));
|
|
81
103
|
});
|
|
104
|
+
const vet = resolveVetConfig(config.vet);
|
|
105
|
+
if (vet.enable) {
|
|
106
|
+
ctx.effect(function* () {
|
|
107
|
+
yield ctx.tools.register(buildVetTool(vet, config.language ?? 'zh'));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
82
110
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Provider plugin for dsh-skill-pack-security — "skill 教流程,插件自动执行".
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Registers two capabilities:
|
|
5
|
+
* 1. a SkillProvider on `ctx.skills` whose candidates are this package's own
|
|
6
|
+
* `skills/` directory (reusing the official `FileSystemSkillProvider`, so
|
|
7
|
+
* frontmatter parsing semantics are byte-identical with the built-in
|
|
8
|
+
* provider), and
|
|
9
|
+
* 2. the `plugin_vet` supply-chain gate tool on `ctx.tools`: an automated
|
|
10
|
+
* pre-install scanner (license / SBOM / commit pinning / malicious
|
|
11
|
+
* patterns / five-dimension risk score) whose every finding cites the
|
|
12
|
+
* pack skill section to continue as a manual audit.
|
|
13
|
+
*
|
|
14
|
+
* The scan engine is zero-dependency (Node built-ins only) and the plugin
|
|
15
|
+
* injects no prompt paragraphs: the gate lives entirely in the tool result,
|
|
16
|
+
* keeping the session persona untouched.
|
|
10
17
|
*
|
|
11
18
|
* @module dsh-skill-pack-security/provider
|
|
12
19
|
*/
|
|
13
20
|
import type { Context } from '@deepseek-ai/cordis';
|
|
14
21
|
import type Schema from '@deepseek-ai/schemastery';
|
|
22
|
+
import { type VetConfigInput } from './vet/config.js';
|
|
15
23
|
export declare const name = "skill-pack-security";
|
|
16
24
|
export declare const inject: string[];
|
|
17
|
-
/** The skill language the provider publishes. */
|
|
25
|
+
/** The skill language the provider publishes (and the plugin_vet report language). */
|
|
18
26
|
export type PackLanguage = 'zh' | 'en';
|
|
27
|
+
/** plugin_vet gate policy: warn (default, non-blocking) or deny (blocks install on FAIL). */
|
|
28
|
+
export type GatePolicy = 'warn' | 'deny';
|
|
19
29
|
/** Configuration for the packaged skill provider. */
|
|
20
30
|
export interface Config {
|
|
21
31
|
/** Whether to watch the packaged skills directory; packaged content is static, so default false. */
|
|
@@ -24,6 +34,8 @@ export interface Config {
|
|
|
24
34
|
language?: PackLanguage;
|
|
25
35
|
/** Explicit skills root; overrides the `language`-derived default. Must be a non-empty path to an existing root with `<skill>/SKILL.md` bundles. */
|
|
26
36
|
skillsDir?: string;
|
|
37
|
+
/** plugin_vet scanner + installation-gate configuration. */
|
|
38
|
+
vet?: VetConfigInput;
|
|
27
39
|
}
|
|
28
40
|
export declare const Config: Schema<Config>;
|
|
29
41
|
/** Register the packaged skills directory as a custom-root provider. */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eight plugin_vet checks. Every function is pure: it reads the shared
|
|
3
|
+
* inputs and returns one `VetCheck` with redacted, capped findings. No check
|
|
4
|
+
* touches the network or the filesystem — resolution happened upstream in
|
|
5
|
+
* `source.ts`.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-skill-pack-security/vet/checks
|
|
8
|
+
*/
|
|
9
|
+
import { type LockData, type Manifest } from './manifest.js';
|
|
10
|
+
import { type GitHubMeta, type NpmMeta, type ResolvedTarget } from './source.js';
|
|
11
|
+
import { type Lang } from './skills.js';
|
|
12
|
+
import type { VetConfig } from './config.js';
|
|
13
|
+
import type { ScannedFile } from './walk.js';
|
|
14
|
+
import type { CheckId, VetCheck, VetSbom } from './vocabulary.js';
|
|
15
|
+
/** Shared inputs every check reads. */
|
|
16
|
+
export interface CheckInputs {
|
|
17
|
+
readonly files: ScannedFile[];
|
|
18
|
+
readonly manifest: Manifest;
|
|
19
|
+
readonly lock: LockData;
|
|
20
|
+
readonly github: GitHubMeta | null;
|
|
21
|
+
readonly npm: NpmMeta | null;
|
|
22
|
+
readonly target: ResolvedTarget;
|
|
23
|
+
readonly config: VetConfig;
|
|
24
|
+
readonly lang: Lang;
|
|
25
|
+
/** 40-hex HEAD of a local git target, when readable without spawning git. */
|
|
26
|
+
readonly localHead: string;
|
|
27
|
+
readonly now: number;
|
|
28
|
+
}
|
|
29
|
+
/** One check run: the check plus its optional SBOM payload. */
|
|
30
|
+
export interface CheckResult {
|
|
31
|
+
readonly check: VetCheck;
|
|
32
|
+
readonly sbom?: VetSbom;
|
|
33
|
+
}
|
|
34
|
+
export declare function licenseCheck(inputs: CheckInputs): VetCheck;
|
|
35
|
+
export declare function sbomCheck(inputs: CheckInputs): CheckResult;
|
|
36
|
+
export declare function commitLockCheck(inputs: CheckInputs): VetCheck;
|
|
37
|
+
export declare function installScriptsCheck(inputs: CheckInputs): VetCheck;
|
|
38
|
+
export declare function networkExfilCheck(inputs: CheckInputs): VetCheck;
|
|
39
|
+
export declare function obfuscationCheck(inputs: CheckInputs): VetCheck;
|
|
40
|
+
export declare function sourceCheck(inputs: CheckInputs): VetCheck;
|
|
41
|
+
export declare function maintenanceCheck(inputs: CheckInputs): VetCheck;
|
|
42
|
+
/** Run the requested checks over shared inputs. */
|
|
43
|
+
export declare function runChecks(inputs: CheckInputs, ids: readonly CheckId[]): CheckResult[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved plugin_vet configuration: plain values with defaults applied.
|
|
3
|
+
*
|
|
4
|
+
* The Schemastery schema lives in `../index.ts` next to the skills-provider
|
|
5
|
+
* config; this module only consumes the already-validated plain object so the
|
|
6
|
+
* engine never depends on Schemastery at runtime.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-skill-pack-security/vet/config
|
|
9
|
+
*/
|
|
10
|
+
import type { GatePolicy } from './vocabulary.js';
|
|
11
|
+
/** Raw config block as validated by the Schemastery schema. */
|
|
12
|
+
export interface VetConfigInput {
|
|
13
|
+
readonly enable?: boolean;
|
|
14
|
+
readonly timeoutMs?: number;
|
|
15
|
+
readonly maxFiles?: number;
|
|
16
|
+
readonly maxFileBytes?: number;
|
|
17
|
+
readonly maxExtractBytes?: number;
|
|
18
|
+
readonly maxDepNodes?: number;
|
|
19
|
+
readonly maxFindingsPerCheck?: number;
|
|
20
|
+
readonly userAgent?: string;
|
|
21
|
+
readonly gate?: {
|
|
22
|
+
readonly policy?: GatePolicy;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Fully resolved configuration with defaults applied. */
|
|
26
|
+
export interface VetConfig {
|
|
27
|
+
readonly enable: boolean;
|
|
28
|
+
readonly timeoutMs: number;
|
|
29
|
+
readonly maxFiles: number;
|
|
30
|
+
readonly maxFileBytes: number;
|
|
31
|
+
readonly maxExtractBytes: number;
|
|
32
|
+
readonly maxDepNodes: number;
|
|
33
|
+
readonly maxFindingsPerCheck: number;
|
|
34
|
+
readonly userAgent: string;
|
|
35
|
+
readonly gate: {
|
|
36
|
+
readonly policy: GatePolicy;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare const VET_DEFAULTS: VetConfig;
|
|
40
|
+
/** Merge raw config over the defaults (the schema already validated shape/ranges). */
|
|
41
|
+
export declare function resolveVetConfig(raw: VetConfigInput | undefined): VetConfig;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plugin_vet engine: resolves a target, runs the eight checks, scores the five
|
|
3
|
+
* risk dimensions, applies the installation gate, and returns the canonical
|
|
4
|
+
* report. Pure orchestration — all side effects live in `source.ts`/`fetch.ts`
|
|
5
|
+
* and every value produced here is JSON-safe and redacted.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-skill-pack-security/vet/engine
|
|
8
|
+
*/
|
|
9
|
+
import { type GatePolicy, type VetReport } from './vocabulary.js';
|
|
10
|
+
import type { VetConfig } from './config.js';
|
|
11
|
+
import { type Lang } from './skills.js';
|
|
12
|
+
/** Engine-level failure: target unusable (not found, offline, budget). */
|
|
13
|
+
export declare class VetTargetError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
/** Tool argument shape (already JSON-validated by defineTool). */
|
|
17
|
+
export interface VetArgs {
|
|
18
|
+
readonly target: string;
|
|
19
|
+
readonly ref?: string;
|
|
20
|
+
readonly checks?: string[];
|
|
21
|
+
readonly policy?: 'inherit' | GatePolicy;
|
|
22
|
+
}
|
|
23
|
+
/** Run the whole pipeline for one tool call. */
|
|
24
|
+
export declare function runVet(args: VetArgs, config: VetConfig, lang: Lang, signal: AbortSignal | undefined): Promise<VetReport>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency network access for the scan engine.
|
|
3
|
+
*
|
|
4
|
+
* Only `globalThis.fetch` (Node 18+ built-in undici) is used. Every request:
|
|
5
|
+
* - honors the caller's AbortSignal AND a cooperative timeout
|
|
6
|
+
* (`AbortSignal.any` + `AbortSignal.timeout`), so a hung upstream can never
|
|
7
|
+
* stall a session;
|
|
8
|
+
* - enforces a hard byte cap while reading the body (stream-counted, no
|
|
9
|
+
* unbounded buffering);
|
|
10
|
+
* - sends a fixed User-Agent and never attaches credentials.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-skill-pack-security/vet/fetch
|
|
13
|
+
*/
|
|
14
|
+
/** Options shared by all fetch helpers. */
|
|
15
|
+
export interface FetchOptions {
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
readonly timeoutMs: number;
|
|
18
|
+
readonly userAgent: string;
|
|
19
|
+
/** Hard cap on the response body in bytes; oversized bodies abort early. */
|
|
20
|
+
readonly maxBytes: number;
|
|
21
|
+
}
|
|
22
|
+
/** A successfully read (possibly truncated) text body. */
|
|
23
|
+
export interface FetchedText {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
readonly text: string;
|
|
26
|
+
readonly truncated: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** A successfully read binary body (kept in memory; capped). */
|
|
29
|
+
export interface FetchedBuffer {
|
|
30
|
+
readonly status: number;
|
|
31
|
+
readonly buffer: Uint8Array;
|
|
32
|
+
readonly truncated: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** A network failure — always surfaced as a check `skip`, never as a finding. */
|
|
35
|
+
export declare class VetFetchError extends Error {
|
|
36
|
+
readonly kind: 'timeout' | 'aborted' | 'http' | 'network' | 'too-large';
|
|
37
|
+
constructor(kind: VetFetchError['kind'], message: string);
|
|
38
|
+
}
|
|
39
|
+
/** Fetch a text body with size cap. */
|
|
40
|
+
export declare function fetchText(url: string, options: FetchOptions): Promise<FetchedText>;
|
|
41
|
+
/** Fetch a binary body with size cap (tarballs). */
|
|
42
|
+
export declare function fetchBuffer(url: string, options: FetchOptions): Promise<FetchedBuffer>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest and lockfile parsing for the SBOM check — pure text/JSON parsing,
|
|
3
|
+
* zero dependencies. Supports package.json plus pnpm-lock.yaml,
|
|
4
|
+
* package-lock.json (v1–v3) and yarn.lock (v1); unknown formats are reported
|
|
5
|
+
* as `unsupported`, never guessed.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-skill-pack-security/vet/manifest
|
|
8
|
+
*/
|
|
9
|
+
import type { ScannedFile } from './walk.js';
|
|
10
|
+
import type { VetPackage } from './vocabulary.js';
|
|
11
|
+
/** Parsed package.json facts the checks consume. */
|
|
12
|
+
export interface Manifest {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly version: string;
|
|
15
|
+
readonly license: string;
|
|
16
|
+
readonly scripts: Record<string, string>;
|
|
17
|
+
readonly dependencies: Record<string, string>;
|
|
18
|
+
readonly devDependencies: Record<string, string>;
|
|
19
|
+
readonly optionalDependencies: Record<string, string>;
|
|
20
|
+
readonly peerDependencies: Record<string, string>;
|
|
21
|
+
readonly repository: string;
|
|
22
|
+
readonly present: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** One dependency edge inside a lockfile entry: name → raw spec. */
|
|
25
|
+
type DepMap = Record<string, string>;
|
|
26
|
+
/** Parsed lockfile summary. */
|
|
27
|
+
export interface LockData {
|
|
28
|
+
readonly kind: 'pnpm' | 'npm' | 'yarn' | 'unsupported' | null;
|
|
29
|
+
readonly lockfile: string | null;
|
|
30
|
+
readonly lockfileVersion: string;
|
|
31
|
+
/** name@version → its dependencies (name → spec). */
|
|
32
|
+
readonly entries: Map<string, DepMap>;
|
|
33
|
+
/** Whether any integrity/shasum field was seen (pinning signal). */
|
|
34
|
+
readonly hasIntegrity: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Find and parse the first supported lockfile in the scan set. */
|
|
37
|
+
export declare function parseLockfile(files: ScannedFile[]): LockData;
|
|
38
|
+
/** Parse package.json when present. */
|
|
39
|
+
export declare function parseManifest(files: ScannedFile[]): Manifest;
|
|
40
|
+
/** Normalize a dependency spec into a bare version when possible. */
|
|
41
|
+
export declare function specVersion(spec: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Build the dependency tree: BFS from the manifest's direct dependencies
|
|
44
|
+
* through lockfile edges, deduped by name@version, depth- and node-capped.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildDependencyTree(manifest: Manifest, lock: LockData, maxNodes: number): {
|
|
47
|
+
packages: VetPackage[];
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
total: number;
|
|
50
|
+
};
|
|
51
|
+
/** Direct specs that are not exact-version pinned (a supply-chain signal). */
|
|
52
|
+
export declare function unpinnedSpecs(manifest: Manifest): string[];
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report redaction: no secret-shaped text may ever leave the scan engine.
|
|
3
|
+
*
|
|
4
|
+
* Applied to every evidence snippet and finding message before a value enters
|
|
5
|
+
* the canonical report, mirroring the pack's `secret-scan` redaction rule
|
|
6
|
+
* (type marker only, never the value). Patterns cover the token families the
|
|
7
|
+
* `secret-scan` skill documents plus webhook/bot URLs.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-skill-pack-security/vet/redact
|
|
10
|
+
*/
|
|
11
|
+
/** Replace every secret-shaped substring with a type marker. */
|
|
12
|
+
export declare function redact(text: string): string;
|
|
13
|
+
/** Cap a raw snippet and redact it for use as finding evidence. */
|
|
14
|
+
export declare function redactSnippet(text: string, maxChars?: number): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing renderers for the plugin_vet report: the canonical JSON value
|
|
3
|
+
* becomes a compact markdown report (render), a pending-call card
|
|
4
|
+
* (presentCall), and a completed gate card (presentResult). Everything is
|
|
5
|
+
* pure and capped — no secrets (the engine redacts before this layer), no
|
|
6
|
+
* unbounded trees.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-skill-pack-security/vet/report
|
|
9
|
+
*/
|
|
10
|
+
import { type Lang } from './skills.js';
|
|
11
|
+
import type { VetReport, VetScores } from './vocabulary.js';
|
|
12
|
+
/** Five-dimension score line. */
|
|
13
|
+
export declare function scoresLine(scores: VetScores, lang: Lang): string;
|
|
14
|
+
/** Render the canonical report as model-facing markdown. */
|
|
15
|
+
export declare function renderReport(report: VetReport, lang: Lang): string;
|
|
16
|
+
/** Short gate summary for the completed card (≤ a few lines). */
|
|
17
|
+
export declare function gateSummary(report: VetReport, lang: Lang): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill cross-references and report-language strings for plugin_vet.
|
|
3
|
+
*
|
|
4
|
+
* Every check cites the pack skill (and section) that continues its subject
|
|
5
|
+
* as a manual audit — "skill 教流程,插件自动执行". Skill names are identical
|
|
6
|
+
* in the zh/en editions, so the reference is language-neutral.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-skill-pack-security/vet/skills
|
|
9
|
+
*/
|
|
10
|
+
import type { CheckId, Dimension } from './vocabulary.js';
|
|
11
|
+
export type Lang = 'zh' | 'en';
|
|
12
|
+
/** check id → pack skill + section (the manual deep-dive continuation). */
|
|
13
|
+
export declare const SKILL_REF: Record<CheckId, string>;
|
|
14
|
+
/** check id → human-readable check name per language. */
|
|
15
|
+
export declare const CHECK_NAME: Record<Lang, Record<CheckId, string>>;
|
|
16
|
+
/** dimension → label per language. */
|
|
17
|
+
export declare const DIMENSION_LABEL: Record<Lang, Record<Dimension, string>>;
|
|
18
|
+
/** Messages shared by checks and the report renderer. */
|
|
19
|
+
export declare const T: {
|
|
20
|
+
readonly zh: {
|
|
21
|
+
readonly pass: '通过';
|
|
22
|
+
readonly warn: '警告';
|
|
23
|
+
readonly fail: '失败';
|
|
24
|
+
readonly skip: '跳过';
|
|
25
|
+
readonly verdictPass: 'PASS';
|
|
26
|
+
readonly verdictWarn: 'WARN';
|
|
27
|
+
readonly verdictFail: 'FAIL';
|
|
28
|
+
readonly gateDenyTitle: '门禁 DENY:此插件未通过供应链检查,安装已被策略拒绝';
|
|
29
|
+
readonly gateDenyBody: '请加载 supply-chain-review / dependency-audit 技能人工深审;或由可信维护者修改门禁策略后重试。';
|
|
30
|
+
readonly gateWarnTitle: '门禁警告:plugin_vet 结果为 FAIL,强烈建议停止安装';
|
|
31
|
+
readonly gateWarnBody: '默认策略 warn 不阻断。继续安装前请按下方 skill 引用人工深审,确认风险可接受。';
|
|
32
|
+
readonly followup: '人工深审建议(加载对应技能继续)';
|
|
33
|
+
readonly budget: '扫描预算';
|
|
34
|
+
readonly budgetTruncated: '扫描被预算截断:结果不完整';
|
|
35
|
+
readonly offline: '离线/受限';
|
|
36
|
+
readonly evidence: '证据';
|
|
37
|
+
};
|
|
38
|
+
readonly en: {
|
|
39
|
+
readonly pass: 'pass';
|
|
40
|
+
readonly warn: 'warn';
|
|
41
|
+
readonly fail: 'fail';
|
|
42
|
+
readonly skip: 'skip';
|
|
43
|
+
readonly verdictPass: 'PASS';
|
|
44
|
+
readonly verdictWarn: 'WARN';
|
|
45
|
+
readonly verdictFail: 'FAIL';
|
|
46
|
+
readonly gateDenyTitle: 'Gate DENY: this plugin failed the supply-chain checks; installation is blocked by policy';
|
|
47
|
+
readonly gateDenyBody: 'Load the supply-chain-review / dependency-audit skills for a manual deep-dive, or have a trusted maintainer change the gate policy and retry.';
|
|
48
|
+
readonly gateWarnTitle: 'Gate warning: plugin_vet returned FAIL — installation is strongly discouraged';
|
|
49
|
+
readonly gateWarnBody: 'The default policy is warn (non-blocking). Before continuing the install, follow the skill references below for a manual review and confirm the risk is acceptable.';
|
|
50
|
+
readonly followup: 'Manual deep-dive (load these skills to continue)';
|
|
51
|
+
readonly budget: 'Scan budget';
|
|
52
|
+
readonly budgetTruncated: 'Scan truncated by budget: results are incomplete';
|
|
53
|
+
readonly offline: 'Offline/limited';
|
|
54
|
+
readonly evidence: 'Evidence';
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target resolution: one `plugin_vet` target string becomes either
|
|
3
|
+
* - a local directory walk,
|
|
4
|
+
* - a GitHub repo (API metadata + codeload tarball, ref-aware), or
|
|
5
|
+
* - an npm package (registry metadata + published tarball).
|
|
6
|
+
*
|
|
7
|
+
* All remote work flows through the zero-dependency fetch helpers; every
|
|
8
|
+
* failure is typed so the caller can mark affected checks `skip` instead of
|
|
9
|
+
* inventing findings.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-skill-pack-security/vet/source
|
|
12
|
+
*/
|
|
13
|
+
import { type ScannedFile, type ScanBudget } from './walk.js';
|
|
14
|
+
import type { VetConfig } from './config.js';
|
|
15
|
+
import type { TargetKind } from './vocabulary.js';
|
|
16
|
+
/** GitHub repo metadata the checks consume (subset of the REST API response). */
|
|
17
|
+
export interface GitHubMeta {
|
|
18
|
+
readonly exists: boolean;
|
|
19
|
+
readonly defaultBranch: string;
|
|
20
|
+
readonly licenseSpdx: string | null;
|
|
21
|
+
readonly licenseName: string | null;
|
|
22
|
+
readonly pushedAt: string;
|
|
23
|
+
readonly createdAt: string;
|
|
24
|
+
readonly archived: boolean;
|
|
25
|
+
readonly stars: number;
|
|
26
|
+
readonly description: string;
|
|
27
|
+
/** The REST API was rate-limited: timestamps/stars are unavailable (files still scanned). */
|
|
28
|
+
readonly rateLimited: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** npm registry metadata the checks consume. */
|
|
31
|
+
export interface NpmMeta {
|
|
32
|
+
readonly exists: boolean;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly version: string;
|
|
35
|
+
readonly license: string;
|
|
36
|
+
readonly gitHead: string;
|
|
37
|
+
readonly repository: string;
|
|
38
|
+
readonly scripts: Record<string, string>;
|
|
39
|
+
readonly dependencies: Record<string, string>;
|
|
40
|
+
readonly devDependencies: Record<string, string>;
|
|
41
|
+
readonly distIntegrity: string;
|
|
42
|
+
readonly timeModified: string;
|
|
43
|
+
readonly deprecated: string;
|
|
44
|
+
}
|
|
45
|
+
/** A resolved scan target: files, metadata, and identity. */
|
|
46
|
+
export interface ResolvedTarget {
|
|
47
|
+
readonly kind: TargetKind;
|
|
48
|
+
readonly resolved: string;
|
|
49
|
+
readonly ref: string;
|
|
50
|
+
readonly files: ScannedFile[];
|
|
51
|
+
readonly budget: ScanBudget;
|
|
52
|
+
readonly github: GitHubMeta | null;
|
|
53
|
+
readonly npm: NpmMeta | null;
|
|
54
|
+
}
|
|
55
|
+
/** Resolve one target string into scan inputs. Never throws for network skips; hard budget violations propagate. */
|
|
56
|
+
export declare function resolveTarget(raw: string, config: VetConfig, signal?: AbortSignal): Promise<ResolvedTarget>;
|
|
57
|
+
/** Whether a ref string is a 40-hex immutable commit. */
|
|
58
|
+
export declare function isCommitRef(ref: string): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal gzip + ustar (POSIX tar) extractor — zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Only the pieces needed to unpack registry/codeload tarballs: regular files,
|
|
5
|
+
* directories, symlinks (recorded, never followed), GNU long-name (`L`) and
|
|
6
|
+
* pax (`x`/`g`) extended headers. Hard constraints:
|
|
7
|
+
* - path traversal is rejected (absolute paths, `..`, drive letters);
|
|
8
|
+
* - total bytes, per-file bytes, and file count are capped;
|
|
9
|
+
* - symlink targets are stored as strings only (never materialized).
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-skill-pack-security/vet/tar
|
|
12
|
+
*/
|
|
13
|
+
/** Budget caps for one extraction. */
|
|
14
|
+
export interface TarBudget {
|
|
15
|
+
/** Hard cap on total extracted payload bytes. */
|
|
16
|
+
readonly maxTotalBytes: number;
|
|
17
|
+
/** Per-file cap; larger members are dropped and `truncated` is set. */
|
|
18
|
+
readonly maxFileBytes: number;
|
|
19
|
+
/** Hard cap on the number of extracted file members. */
|
|
20
|
+
readonly maxFiles: number;
|
|
21
|
+
}
|
|
22
|
+
/** One extracted member. Directories are implied by file paths. */
|
|
23
|
+
export interface TarEntry {
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly content: Uint8Array;
|
|
26
|
+
}
|
|
27
|
+
/** Result of one extraction. */
|
|
28
|
+
export interface TarResult {
|
|
29
|
+
readonly files: Map<string, Uint8Array>;
|
|
30
|
+
readonly symlinks: Map<string, string>;
|
|
31
|
+
readonly truncated: boolean;
|
|
32
|
+
readonly totalBytes: number;
|
|
33
|
+
}
|
|
34
|
+
/** Extract one gzipped ustar archive into an in-memory file map. */
|
|
35
|
+
export declare function extractTarGz(gzipped: Uint8Array, budget: TarBudget): TarResult;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `plugin_vet` tool definition: schema-validated arguments, a strictly
|
|
3
|
+
* validated canonical report, a model-facing markdown render, and the
|
|
4
|
+
* pending/completed UI cards. This is the only module in the vet engine that
|
|
5
|
+
* imports harness packages; everything below it is plain zero-dependency code.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-skill-pack-security/vet/tool
|
|
8
|
+
*/
|
|
9
|
+
import type { VetConfig } from './config.js';
|
|
10
|
+
import type { Lang } from './skills.js';
|
|
11
|
+
/**
|
|
12
|
+
* Build the plugin_vet tool bound to the resolved plugin configuration.
|
|
13
|
+
* @param config - resolved vet configuration (defaults already applied).
|
|
14
|
+
* @param lang - report language, driven by the plugin's `language` config.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildVetTool(config: VetConfig, lang: Lang): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared vocabulary for the plugin_vet supply-chain gate.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is plain data (JSON-safe, no live runtime objects) so the
|
|
5
|
+
* scan engine stays a pure, harness-free module: the tool definition in
|
|
6
|
+
* `tool.ts` is the only part that touches ctx/defineTool.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-skill-pack-security/vet/vocabulary
|
|
9
|
+
*/
|
|
10
|
+
/** The five risk dimensions every report scores. */
|
|
11
|
+
export type Dimension = 'license' | 'source' | 'dependencies' | 'build-scripts' | 'maintenance';
|
|
12
|
+
/** Per-check and overall verdicts. `skip` = the check could not run (offline, budget). */
|
|
13
|
+
export type Verdict = 'pass' | 'warn' | 'fail' | 'skip';
|
|
14
|
+
/** One of the two gate policies. */
|
|
15
|
+
export type GatePolicy = 'warn' | 'deny';
|
|
16
|
+
/** How a scan target is addressed. */
|
|
17
|
+
export type TargetKind = 'github-repo' | 'local-path' | 'npm-package';
|
|
18
|
+
/** Stable check ids; each maps to exactly one dimension and one skill section. */
|
|
19
|
+
export type CheckId = 'license' | 'sbom' | 'commit-lock' | 'install-scripts' | 'network-exfil' | 'obfuscation' | 'source' | 'maintenance';
|
|
20
|
+
export declare const ALL_CHECK_IDS: readonly CheckId[];
|
|
21
|
+
/** Finding severity: fail blocks the check, warn downgrades, info is an observation. */
|
|
22
|
+
export type FindingLevel = 'fail' | 'warn' | 'info';
|
|
23
|
+
/**
|
|
24
|
+
* One machine-checkable finding. `skill` points at the pack skill (and its
|
|
25
|
+
* section) that continues the manual audit; `evidence` is a redacted, capped
|
|
26
|
+
* snippet or fact — never a secret, never a full file body.
|
|
27
|
+
*/
|
|
28
|
+
export interface VetFinding {
|
|
29
|
+
readonly level: FindingLevel;
|
|
30
|
+
/** Short human-readable claim, in the report language. */
|
|
31
|
+
readonly message: string;
|
|
32
|
+
/** File:line location when known (e.g. `package.json:7` or `README.md:12`). */
|
|
33
|
+
readonly location?: string;
|
|
34
|
+
/** Pack skill + section to continue the manual audit, e.g. `supply-chain-review §1`. */
|
|
35
|
+
readonly skill: string;
|
|
36
|
+
/** Redacted, capped evidence text (≤ 160 chars per entry). */
|
|
37
|
+
readonly evidence?: string;
|
|
38
|
+
}
|
|
39
|
+
/** One executed check: verdict, findings, skill pointer, and its 0–100 dimension score. */
|
|
40
|
+
export interface VetCheck {
|
|
41
|
+
readonly id: CheckId;
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly verdict: Verdict;
|
|
44
|
+
/** Why the check was skipped (offline, budget, unsupported target). */
|
|
45
|
+
readonly skipReason?: string;
|
|
46
|
+
readonly score: number;
|
|
47
|
+
/** All findings capped by config.maxFindingsPerCheck (truncated notes the cut). */
|
|
48
|
+
readonly findings: VetFinding[];
|
|
49
|
+
readonly truncatedFindings: boolean;
|
|
50
|
+
/** Primary skill + section for manual deep-dive of this check's subject. */
|
|
51
|
+
readonly skill: string;
|
|
52
|
+
}
|
|
53
|
+
/** One SBOM tree entry (deduped by name@version). */
|
|
54
|
+
export interface VetPackage {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly version: string;
|
|
57
|
+
/** Depth from the root manifest (0 = direct dependency). */
|
|
58
|
+
readonly depth: number;
|
|
59
|
+
/** License string as declared, when the lockfile/manifest carried one. */
|
|
60
|
+
readonly license?: string;
|
|
61
|
+
/** Whether the entry came from devDependencies only. */
|
|
62
|
+
readonly dev: boolean;
|
|
63
|
+
}
|
|
64
|
+
/** SBOM summary produced by the `sbom` check. */
|
|
65
|
+
export interface VetSbom {
|
|
66
|
+
readonly lockfile: string | null;
|
|
67
|
+
readonly lockfileVersion?: string;
|
|
68
|
+
readonly directDependencies: number;
|
|
69
|
+
readonly directDevDependencies: number;
|
|
70
|
+
readonly packages: VetPackage[];
|
|
71
|
+
readonly truncated: boolean;
|
|
72
|
+
readonly totalPackages: number;
|
|
73
|
+
/** Direct dependency specs that are not pinned to an exact version. */
|
|
74
|
+
readonly unpinned: string[];
|
|
75
|
+
}
|
|
76
|
+
/** Scorecard: the five dimensions plus the overall weighted score. */
|
|
77
|
+
export interface VetScores {
|
|
78
|
+
readonly license: number;
|
|
79
|
+
readonly source: number;
|
|
80
|
+
readonly dependencies: number;
|
|
81
|
+
readonly 'build-scripts': number;
|
|
82
|
+
readonly maintenance: number;
|
|
83
|
+
readonly overall: number;
|
|
84
|
+
}
|
|
85
|
+
/** Resolved target metadata recorded in the report. */
|
|
86
|
+
export interface VetTarget {
|
|
87
|
+
readonly raw: string;
|
|
88
|
+
readonly kind: TargetKind;
|
|
89
|
+
/** owner/repo for remote targets, resolved absolute path for local ones. */
|
|
90
|
+
readonly resolved: string;
|
|
91
|
+
/** Effective ref for remote targets (branch/tag/commit or npm version). */
|
|
92
|
+
readonly ref: string;
|
|
93
|
+
}
|
|
94
|
+
/** Gate result: policy applied and whether installation is blocked. */
|
|
95
|
+
export interface VetGate {
|
|
96
|
+
readonly policy: GatePolicy;
|
|
97
|
+
readonly applied: boolean;
|
|
98
|
+
readonly blocked: boolean;
|
|
99
|
+
/** One-line reason when blocked (deny policy + fail verdict). */
|
|
100
|
+
readonly reason?: string;
|
|
101
|
+
}
|
|
102
|
+
/** Scan budget facts so truncation is never presented as completeness. */
|
|
103
|
+
export interface VetBudget {
|
|
104
|
+
readonly filesScanned: number;
|
|
105
|
+
readonly filesSkipped: number;
|
|
106
|
+
readonly bytesScanned: number;
|
|
107
|
+
readonly truncated: boolean;
|
|
108
|
+
readonly truncatedReason?: string;
|
|
109
|
+
}
|
|
110
|
+
/** The canonical plugin_vet report (validated by the tool output schema). */
|
|
111
|
+
export interface VetReport {
|
|
112
|
+
readonly kind: 'vet-report';
|
|
113
|
+
readonly target: VetTarget;
|
|
114
|
+
readonly fetchedAt: string;
|
|
115
|
+
readonly checks: VetCheck[];
|
|
116
|
+
readonly scores: VetScores;
|
|
117
|
+
readonly verdict: Verdict;
|
|
118
|
+
readonly gate: VetGate;
|
|
119
|
+
readonly sbom: VetSbom;
|
|
120
|
+
readonly budget: VetBudget;
|
|
121
|
+
/** Skill names worth loading for the manual follow-up audit. */
|
|
122
|
+
readonly followupSkills: string[];
|
|
123
|
+
}
|