@pwddd/skills-scanner 1.0.0-beta.2 → 1.0.0-beta.21

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/src/report.ts DELETED
@@ -1,128 +0,0 @@
1
- /**
2
- * Report generation module
3
- */
4
-
5
- import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync, readdirSync, statSync } from "node:fs";
6
- import { join, basename } from "node:path";
7
- import { runScan } from "./scanner.js";
8
- import { loadState, saveState, STATE_DIR } from "./state.js";
9
- import type { ScanRecord, PluginLogger } from "./types.js";
10
-
11
- export async function buildDailyReport(
12
- dirs: string[],
13
- behavioral: boolean,
14
- apiUrl: string,
15
- useLLM: boolean,
16
- policy: string,
17
- logger: PluginLogger
18
- ): Promise<string> {
19
- const now = new Date();
20
- const dateStr = now.toLocaleDateString("en-US", {
21
- year: "numeric",
22
- month: "2-digit",
23
- day: "2-digit",
24
- });
25
- const timeStr = now.toLocaleTimeString("en-US", {
26
- hour: "2-digit",
27
- minute: "2-digit",
28
- });
29
- const jsonOut = join(STATE_DIR, `report-${now.toISOString().slice(0, 10)}.json`);
30
- mkdirSync(STATE_DIR, { recursive: true });
31
-
32
- let total = 0;
33
- let safe = 0;
34
- let unsafe = 0;
35
- let errors = 0;
36
- const unsafeList: string[] = [];
37
- const allResults: ScanRecord[] = [];
38
-
39
- for (const dir of dirs) {
40
- if (!existsSync(dir)) continue;
41
-
42
- // Find all skills in directory
43
- const skills: string[] = [];
44
- try {
45
- const entries = readdirSync(dir);
46
- for (const entry of entries) {
47
- const skillPath = join(dir, entry);
48
- if (statSync(skillPath).isDirectory() && existsSync(join(skillPath, "SKILL.md"))) {
49
- skills.push(skillPath);
50
- }
51
- }
52
- } catch {
53
- continue;
54
- }
55
-
56
- for (const skillPath of skills) {
57
- try {
58
- const res = await runScan("scan", skillPath, {
59
- behavioral,
60
- detailed: false,
61
- apiUrl,
62
- useLLM,
63
- policy,
64
- });
65
-
66
- const name = basename(skillPath);
67
- total++;
68
-
69
- if (res.exitCode === 0) {
70
- safe++;
71
- allResults.push({
72
- name,
73
- path: skillPath,
74
- is_safe: true,
75
- max_severity: "NONE",
76
- findings: 0,
77
- });
78
- } else {
79
- unsafe++;
80
- unsafeList.push(name);
81
- allResults.push({
82
- name,
83
- path: skillPath,
84
- is_safe: false,
85
- max_severity: res.data?.max_severity || "UNKNOWN",
86
- findings: res.data?.findings_count || 0,
87
- });
88
- }
89
- } catch (err: any) {
90
- errors++;
91
- allResults.push({
92
- name: basename(skillPath),
93
- path: skillPath,
94
- error: err.message,
95
- });
96
- }
97
- }
98
- }
99
-
100
- writeFileSync(jsonOut, JSON.stringify(allResults, null, 2));
101
- saveState({
102
- ...loadState(),
103
- lastScanAt: now.toISOString(),
104
- lastUnsafeSkills: unsafeList,
105
- });
106
-
107
- const lines = [`🔍 *Skills 安全日报* — ${dateStr} ${timeStr}`, "─".repeat(36)];
108
- if (total === 0) {
109
- lines.push("📭 未找到任何 Skill,请检查扫描目录。");
110
- } else {
111
- lines.push(`📊 扫描总计:${total} 个 Skill`);
112
- lines.push(`✅ 安全:${safe} 个`);
113
- lines.push(`❌ 问题:${unsafe} 个`);
114
- if (errors) lines.push(`⚠️ 错误:${errors} 个`);
115
- if (unsafe > 0) {
116
- lines.push("", "🚨 *需要关注的 Skills:*");
117
- for (const name of unsafeList) {
118
- const r = allResults.find((x) => x.name === name);
119
- lines.push(` • ${name} [${r?.max_severity ?? "?"}] — ${r?.findings ?? "?"} 条发现`);
120
- }
121
- lines.push("", "💡 运行 `/skills-scanner scan <路径> --detailed` 查看详情");
122
- } else {
123
- lines.push("", "🎉 所有 Skills 安全,未发现威胁。");
124
- }
125
- }
126
- lines.push("", `📁 完整报告:${jsonOut}`);
127
- return lines.join("\n");
128
- }
package/src/watcher.ts DELETED
@@ -1,178 +0,0 @@
1
- /**
2
- * File watcher module for pre-installation scanning
3
- */
4
-
5
- import { watch as fsWatch, existsSync, renameSync, rmSync } from "node:fs";
6
- import { join, basename } from "node:path";
7
- import { mkdirSync } from "node:fs";
8
- import { runScan } from "./scanner.js";
9
- import type { OnUnsafeAction, PluginLogger } from "./types.js";
10
-
11
- // Debounce delay in milliseconds
12
- const DEBOUNCE_DELAY = 1000; // Increased from 500ms to 1000ms for better stability
13
-
14
- export async function handleNewSkill(
15
- skillPath: string,
16
- onUnsafe: OnUnsafeAction,
17
- behavioral: boolean,
18
- apiUrl: string,
19
- useLLM: boolean,
20
- policy: string,
21
- notifyFn: (msg: string) => void,
22
- logger: PluginLogger,
23
- quarantineDir: string
24
- ): Promise<void> {
25
- if (!existsSync(join(skillPath, "SKILL.md"))) return;
26
-
27
- const name = basename(skillPath);
28
- logger.info(`[skills-scanner] 🔍 检测到新 Skill,开始安装前扫描:${name}`);
29
- notifyFn(`🔍 检测到新 Skill \`${name}\`,正在安全扫描...`);
30
-
31
- try {
32
- const res = await runScan("scan", skillPath, {
33
- behavioral,
34
- detailed: true,
35
- apiUrl,
36
- useLLM,
37
- policy,
38
- });
39
-
40
- if (res.exitCode === 0) {
41
- notifyFn(`✅ \`${name}\` 安全检查通过,可以正常使用。`);
42
- return;
43
- }
44
-
45
- let action = "";
46
- try {
47
- if (onUnsafe === "quarantine") {
48
- mkdirSync(quarantineDir, { recursive: true });
49
- const dest = join(quarantineDir, `${name}-${Date.now()}`);
50
- renameSync(skillPath, dest);
51
- action = `已移入隔离目录:\`${dest}\``;
52
- } else if (onUnsafe === "delete") {
53
- rmSync(skillPath, { recursive: true, force: true });
54
- action = "已自动删除";
55
- } else {
56
- action = "仅警告,Skill 已保留(请谨慎使用)";
57
- }
58
- } catch (e: any) {
59
- action = `处置失败:${e.message}`;
60
- logger.error("[skills-scanner] Failed to handle unsafe skill", {
61
- skill: name,
62
- error: e.message,
63
- });
64
- }
65
-
66
- notifyFn(
67
- [
68
- `❌ *安全警告:\`${name}\` 未通过扫描*`,
69
- `处置:${action}`,
70
- "```",
71
- res.output.slice(0, 600),
72
- "```",
73
- ].join("\n")
74
- );
75
- } catch (err: any) {
76
- logger.error("[skills-scanner] Scan failed for new skill", {
77
- skill: name,
78
- error: err.message,
79
- stack: err.stack,
80
- });
81
- notifyFn(`⚠️ \`${name}\` 扫描失败:${err.message}`);
82
- }
83
- }
84
-
85
- export function startWatcher(
86
- dirs: string[],
87
- onUnsafe: OnUnsafeAction,
88
- behavioral: boolean,
89
- apiUrl: string,
90
- useLLM: boolean,
91
- policy: string,
92
- notifyFn: (msg: string) => void,
93
- logger: PluginLogger,
94
- quarantineDir: string
95
- ): () => void {
96
- const timers = new Map<string, NodeJS.Timeout>();
97
- const processing = new Set<string>(); // Track files being processed
98
-
99
- const watchers = dirs.map((dir) => {
100
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
101
- logger.info(`[skills-scanner] 👁 监听目录:${dir}`);
102
-
103
- const watcher = fsWatch(dir, { persistent: false }, (_evt, filename) => {
104
- if (!filename) return;
105
- const skillPath = join(dir, filename);
106
-
107
- // Skip if file doesn't exist or is already being processed
108
- if (!existsSync(skillPath) || processing.has(skillPath)) return;
109
-
110
- // Clear existing timer for this path (debounce)
111
- const prev = timers.get(skillPath);
112
- if (prev) clearTimeout(prev);
113
-
114
- // Set new timer with debounce delay
115
- timers.set(
116
- skillPath,
117
- setTimeout(async () => {
118
- timers.delete(skillPath);
119
- processing.add(skillPath);
120
-
121
- try {
122
- await handleNewSkill(
123
- skillPath,
124
- onUnsafe,
125
- behavioral,
126
- apiUrl,
127
- useLLM,
128
- policy,
129
- notifyFn,
130
- logger,
131
- quarantineDir
132
- );
133
- } catch (err: any) {
134
- logger.error("[skills-scanner] Watcher handler failed", {
135
- path: skillPath,
136
- error: err.message,
137
- });
138
- } finally {
139
- processing.delete(skillPath);
140
- }
141
- }, DEBOUNCE_DELAY)
142
- );
143
- });
144
-
145
- // Add error handler for watcher
146
- watcher.on("error", (error: Error) => {
147
- logger.error("[skills-scanner] Watcher error", {
148
- directory: dir,
149
- error: error.message,
150
- stack: error.stack,
151
- });
152
- });
153
-
154
- return watcher;
155
- });
156
-
157
- return () => {
158
- try {
159
- watchers.forEach((w) => {
160
- try {
161
- w.close();
162
- } catch (err: any) {
163
- logger.warn("[skills-scanner] Failed to close watcher", {
164
- error: err.message,
165
- });
166
- }
167
- });
168
- timers.forEach((t) => clearTimeout(t));
169
- timers.clear();
170
- processing.clear();
171
- logger.info("[skills-scanner] 目录监听已停止");
172
- } catch (err: any) {
173
- logger.error("[skills-scanner] Error stopping watcher", {
174
- error: err.message,
175
- });
176
- }
177
- };
178
- }