@pwddd/skills-scanner 1.0.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.
Potentially problematic release.
This version of @pwddd/skills-scanner might be problematic. Click here for more details.
- package/README.md +392 -0
- package/index.ts +373 -0
- package/openclaw.plugin.json +60 -0
- package/package.json +49 -0
- package/skills/skills-scanner/SKILL.md +180 -0
- package/skills/skills-scanner/scan.py +373 -0
- package/src/commands.ts +277 -0
- package/src/config.ts +170 -0
- package/src/cron.ts +143 -0
- package/src/deps.ts +73 -0
- package/src/prompt-guidance.ts +25 -0
- package/src/report.ts +100 -0
- package/src/scanner.ts +50 -0
- package/src/state.ts +70 -0
- package/src/types.ts +48 -0
- package/src/watcher.ts +125 -0
package/src/watcher.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
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 } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export async function handleNewSkill(
|
|
12
|
+
skillPath: string,
|
|
13
|
+
onUnsafe: OnUnsafeAction,
|
|
14
|
+
behavioral: boolean,
|
|
15
|
+
apiUrl: string,
|
|
16
|
+
useLLM: boolean,
|
|
17
|
+
policy: string,
|
|
18
|
+
notifyFn: (msg: string) => void,
|
|
19
|
+
logger: any,
|
|
20
|
+
venvPython: string,
|
|
21
|
+
scanScript: string,
|
|
22
|
+
quarantineDir: string
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
if (!existsSync(join(skillPath, "SKILL.md"))) return;
|
|
25
|
+
|
|
26
|
+
const name = basename(skillPath);
|
|
27
|
+
logger.info(`[skills-scanner] 🔍 检测到新 Skill,开始安装前扫描: ${name}`);
|
|
28
|
+
notifyFn(`🔍 检测到新 Skill \`${name}\`,正在安全扫描...`);
|
|
29
|
+
|
|
30
|
+
const res = await runScan(venvPython, scanScript, "scan", skillPath, {
|
|
31
|
+
behavioral,
|
|
32
|
+
detailed: true,
|
|
33
|
+
apiUrl,
|
|
34
|
+
useLLM,
|
|
35
|
+
policy,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (res.exitCode === 0) {
|
|
39
|
+
notifyFn(`✅ \`${name}\` 安全检查通过,可以正常使用。`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let action = "";
|
|
44
|
+
try {
|
|
45
|
+
if (onUnsafe === "quarantine") {
|
|
46
|
+
mkdirSync(quarantineDir, { recursive: true });
|
|
47
|
+
const dest = join(quarantineDir, `${name}-${Date.now()}`);
|
|
48
|
+
renameSync(skillPath, dest);
|
|
49
|
+
action = `已移入隔离目录:\`${dest}\``;
|
|
50
|
+
} else if (onUnsafe === "delete") {
|
|
51
|
+
rmSync(skillPath, { recursive: true, force: true });
|
|
52
|
+
action = "已自动删除";
|
|
53
|
+
} else {
|
|
54
|
+
action = "仅警告,Skill 已保留(请谨慎使用)";
|
|
55
|
+
}
|
|
56
|
+
} catch (e: any) {
|
|
57
|
+
action = `处置失败:${e.message}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
notifyFn(
|
|
61
|
+
[
|
|
62
|
+
`❌ *安全警告:\`${name}\` 未通过扫描*`,
|
|
63
|
+
`处置:${action}`,
|
|
64
|
+
"```",
|
|
65
|
+
res.output.slice(0, 600),
|
|
66
|
+
"```",
|
|
67
|
+
].join("\n")
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function startWatcher(
|
|
72
|
+
dirs: string[],
|
|
73
|
+
onUnsafe: OnUnsafeAction,
|
|
74
|
+
behavioral: boolean,
|
|
75
|
+
apiUrl: string,
|
|
76
|
+
useLLM: boolean,
|
|
77
|
+
policy: string,
|
|
78
|
+
notifyFn: (msg: string) => void,
|
|
79
|
+
logger: any,
|
|
80
|
+
venvPython: string,
|
|
81
|
+
scanScript: string,
|
|
82
|
+
quarantineDir: string
|
|
83
|
+
): () => void {
|
|
84
|
+
const timers = new Map<string, NodeJS.Timeout>();
|
|
85
|
+
|
|
86
|
+
const watchers = dirs.map((dir) => {
|
|
87
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
88
|
+
logger.info(`[skills-scanner] 👁 监听目录:${dir}`);
|
|
89
|
+
|
|
90
|
+
return fsWatch(dir, { persistent: false }, (_evt, filename) => {
|
|
91
|
+
if (!filename) return;
|
|
92
|
+
const skillPath = join(dir, filename);
|
|
93
|
+
if (!existsSync(skillPath)) return;
|
|
94
|
+
|
|
95
|
+
const prev = timers.get(skillPath);
|
|
96
|
+
if (prev) clearTimeout(prev);
|
|
97
|
+
timers.set(
|
|
98
|
+
skillPath,
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
timers.delete(skillPath);
|
|
101
|
+
handleNewSkill(
|
|
102
|
+
skillPath,
|
|
103
|
+
onUnsafe,
|
|
104
|
+
behavioral,
|
|
105
|
+
apiUrl,
|
|
106
|
+
useLLM,
|
|
107
|
+
policy,
|
|
108
|
+
notifyFn,
|
|
109
|
+
logger,
|
|
110
|
+
venvPython,
|
|
111
|
+
scanScript,
|
|
112
|
+
quarantineDir
|
|
113
|
+
);
|
|
114
|
+
}, 500)
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return () => {
|
|
120
|
+
watchers.forEach((w) => w.close());
|
|
121
|
+
timers.forEach((t) => clearTimeout(t));
|
|
122
|
+
timers.clear();
|
|
123
|
+
logger.info("[skills-scanner] 目录监听已停止");
|
|
124
|
+
};
|
|
125
|
+
}
|