@panguard-ai/panguard-skill-auditor 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/dist/checks/code-check.d.ts +7 -0
  3. package/dist/checks/code-check.d.ts.map +1 -0
  4. package/dist/checks/code-check.js +67 -0
  5. package/dist/checks/code-check.js.map +1 -0
  6. package/dist/checks/dependency-check.d.ts +10 -0
  7. package/dist/checks/dependency-check.d.ts.map +1 -0
  8. package/dist/checks/dependency-check.js +99 -0
  9. package/dist/checks/dependency-check.js.map +1 -0
  10. package/dist/checks/instruction-check.d.ts +13 -0
  11. package/dist/checks/instruction-check.d.ts.map +1 -0
  12. package/dist/checks/instruction-check.js +158 -0
  13. package/dist/checks/instruction-check.js.map +1 -0
  14. package/dist/checks/manifest-check.d.ts +7 -0
  15. package/dist/checks/manifest-check.d.ts.map +1 -0
  16. package/dist/checks/manifest-check.js +75 -0
  17. package/dist/checks/manifest-check.js.map +1 -0
  18. package/dist/checks/permission-check.d.ts +10 -0
  19. package/dist/checks/permission-check.d.ts.map +1 -0
  20. package/dist/checks/permission-check.js +63 -0
  21. package/dist/checks/permission-check.js.map +1 -0
  22. package/dist/index.d.ts +22 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +59 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/manifest-parser.d.ts +16 -0
  27. package/dist/manifest-parser.d.ts.map +1 -0
  28. package/dist/manifest-parser.js +74 -0
  29. package/dist/manifest-parser.js.map +1 -0
  30. package/dist/risk-scorer.d.ts +17 -0
  31. package/dist/risk-scorer.d.ts.map +1 -0
  32. package/dist/risk-scorer.js +36 -0
  33. package/dist/risk-scorer.js.map +1 -0
  34. package/dist/types.d.ts +66 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +8 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +34 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Panguard AI Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Code security check - wraps panguard-scan SAST + secrets scanners
3
+ * 程式碼安全檢查 - 包裝 panguard-scan 的 SAST 和密鑰掃描器
4
+ */
5
+ import type { CheckResult } from '../types.js';
6
+ export declare function checkCode(skillDir: string): Promise<CheckResult>;
7
+ //# sourceMappingURL=code-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-check.d.ts","sourceRoot":"","sources":["../../src/checks/code-check.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,aAAa,CAAC;AAG7D,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAsEtE"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Code security check - wraps panguard-scan SAST + secrets scanners
3
+ * 程式碼安全檢查 - 包裝 panguard-scan 的 SAST 和密鑰掃描器
4
+ */
5
+ export async function checkCode(skillDir) {
6
+ const findings = [];
7
+ // Dynamic import to handle case where panguard-scan is not available
8
+ let checkSourceCode = null;
9
+ let checkHardcodedSecrets = null;
10
+ try {
11
+ const scan = await import('@panguard-ai/panguard-scan');
12
+ if (typeof scan.checkSourceCode === 'function') {
13
+ checkSourceCode = scan.checkSourceCode;
14
+ }
15
+ if (typeof scan.checkHardcodedSecrets === 'function') {
16
+ checkHardcodedSecrets = scan.checkHardcodedSecrets;
17
+ }
18
+ }
19
+ catch {
20
+ return {
21
+ status: 'info',
22
+ label: 'Code: panguard-scan not available, skipping code analysis',
23
+ findings: [],
24
+ };
25
+ }
26
+ const [codeResults, secretResults] = await Promise.all([
27
+ checkSourceCode ? checkSourceCode(skillDir) : Promise.resolve([]),
28
+ checkHardcodedSecrets ? checkHardcodedSecrets(skillDir) : Promise.resolve([]),
29
+ ]);
30
+ let scriptFileCount = 0;
31
+ for (const finding of codeResults) {
32
+ findings.push({
33
+ id: `code-${finding.id}`,
34
+ title: finding.title,
35
+ description: finding.description,
36
+ severity: finding.severity,
37
+ category: 'code',
38
+ location: finding.details,
39
+ });
40
+ }
41
+ scriptFileCount += codeResults.length > 0 ? codeResults.length : 0;
42
+ for (const finding of secretResults) {
43
+ findings.push({
44
+ id: `secret-${finding.id}`,
45
+ title: finding.title,
46
+ description: finding.description,
47
+ severity: finding.severity,
48
+ category: 'secrets',
49
+ location: finding.details,
50
+ });
51
+ }
52
+ const hasCritical = findings.some((f) => f.severity === 'critical');
53
+ const hasHigh = findings.some((f) => f.severity === 'high');
54
+ const status = hasCritical ? 'fail' : hasHigh ? 'warn' : findings.length > 0 ? 'warn' : 'pass';
55
+ const codeLabel = findings.filter((f) => f.category === 'code').length === 0
56
+ ? `Code: No vulnerabilities found`
57
+ : `Code: ${findings.filter((f) => f.category === 'code').length} issue(s) found`;
58
+ const secretLabel = findings.filter((f) => f.category === 'secrets').length === 0
59
+ ? 'Secrets: No hardcoded credentials found'
60
+ : `Secrets: ${findings.filter((f) => f.category === 'secrets').length} credential(s) exposed`;
61
+ return {
62
+ status,
63
+ label: `${codeLabel}; ${secretLabel}`,
64
+ findings,
65
+ };
66
+ }
67
+ //# sourceMappingURL=code-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-check.js","sourceRoot":"","sources":["../../src/checks/code-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dependency and external resource analysis
3
+ * 依賴和外部資源分析
4
+ *
5
+ * Extracts URLs, API endpoints, and tool references from skill instructions.
6
+ * 從技能指令中擷取 URL、API 端點和工具引用。
7
+ */
8
+ import type { SkillManifest, CheckResult } from '../types.js';
9
+ export declare function checkDependencies(manifest: SkillManifest): CheckResult;
10
+ //# sourceMappingURL=dependency-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-check.d.ts","sourceRoot":"","sources":["../../src/checks/dependency-check.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgB,WAAW,EAAE,MAAM,aAAa,CAAC;AAe5E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAuFtE"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Dependency and external resource analysis
3
+ * 依賴和外部資源分析
4
+ *
5
+ * Extracts URLs, API endpoints, and tool references from skill instructions.
6
+ * 從技能指令中擷取 URL、API 端點和工具引用。
7
+ */
8
+ /** Well-known safe domains */
9
+ const SAFE_DOMAINS = new Set([
10
+ 'github.com', 'gitlab.com', 'bitbucket.org',
11
+ 'npmjs.com', 'pypi.org', 'crates.io',
12
+ 'google.com', 'googleapis.com', 'microsoft.com',
13
+ 'stackoverflow.com', 'developer.mozilla.org',
14
+ 'docs.python.org', 'nodejs.org',
15
+ ]);
16
+ const URL_RE = /https?:\/\/[^\s"'<>)\]]+/gi;
17
+ const NPM_INSTALL_RE = /npm\s+install\s+(?:-[gD]\s+)?([^\s;|&]+)/gi;
18
+ const PIP_INSTALL_RE = /pip\s+install\s+([^\s;|&]+)/gi;
19
+ export function checkDependencies(manifest) {
20
+ const findings = [];
21
+ const instructions = manifest.instructions;
22
+ // Extract URLs
23
+ const urls = [...new Set(instructions.match(URL_RE) ?? [])];
24
+ const externalDomains = [];
25
+ for (const url of urls) {
26
+ try {
27
+ const parsed = new URL(url);
28
+ const domain = parsed.hostname;
29
+ const baseDomain = domain.split('.').slice(-2).join('.');
30
+ if (!SAFE_DOMAINS.has(baseDomain) && !SAFE_DOMAINS.has(domain)) {
31
+ externalDomains.push(domain);
32
+ }
33
+ }
34
+ catch {
35
+ // Invalid URL, skip
36
+ }
37
+ }
38
+ if (externalDomains.length > 0) {
39
+ findings.push({
40
+ id: 'dep-external-urls',
41
+ title: `References ${externalDomains.length} external domain(s)`,
42
+ description: `Skill references non-standard domains: ${externalDomains.join(', ')}. Verify these are legitimate.`,
43
+ severity: 'low',
44
+ category: 'dependency',
45
+ });
46
+ }
47
+ // Check for npm/pip installs
48
+ const npmPackages = [];
49
+ let npmMatch;
50
+ while ((npmMatch = NPM_INSTALL_RE.exec(instructions)) !== null) {
51
+ if (npmMatch[1])
52
+ npmPackages.push(npmMatch[1]);
53
+ }
54
+ const pipPackages = [];
55
+ let pipMatch;
56
+ while ((pipMatch = PIP_INSTALL_RE.exec(instructions)) !== null) {
57
+ if (pipMatch[1])
58
+ pipPackages.push(pipMatch[1]);
59
+ }
60
+ if (npmPackages.length > 0 || pipPackages.length > 0) {
61
+ const all = [...npmPackages.map((p) => `npm:${p}`), ...pipPackages.map((p) => `pip:${p}`)];
62
+ findings.push({
63
+ id: 'dep-package-installs',
64
+ title: 'Skill installs external packages',
65
+ description: `Packages: ${all.join(', ')}. Review these for supply chain risks.`,
66
+ severity: 'medium',
67
+ category: 'dependency',
68
+ });
69
+ }
70
+ // Check metadata requires
71
+ const requires = manifest.metadata?.openclaw?.requires;
72
+ if (requires?.bins && requires.bins.length > 0) {
73
+ findings.push({
74
+ id: 'dep-required-bins',
75
+ title: `Requires ${requires.bins.length} system binary(ies)`,
76
+ description: `Required binaries: ${requires.bins.join(', ')}`,
77
+ severity: 'low',
78
+ category: 'dependency',
79
+ });
80
+ }
81
+ if (requires?.env && requires.env.length > 0) {
82
+ findings.push({
83
+ id: 'dep-required-env',
84
+ title: `Requires ${requires.env.length} environment variable(s)`,
85
+ description: `Required env vars: ${requires.env.join(', ')}. Ensure these don't expose sensitive credentials.`,
86
+ severity: 'low',
87
+ category: 'dependency',
88
+ });
89
+ }
90
+ const status = findings.some((f) => f.severity === 'high' || f.severity === 'critical')
91
+ ? 'warn'
92
+ : 'info';
93
+ const urlCount = urls.length;
94
+ const label = urlCount > 0
95
+ ? `Dependencies: ${urlCount} URL(s), ${externalDomains.length} external domain(s)`
96
+ : 'Dependencies: No external references found';
97
+ return { status, label, findings };
98
+ }
99
+ //# sourceMappingURL=dependency-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-check.js","sourceRoot":"","sources":["../../src/checks/dependency-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Prompt injection and tool poisoning detection
3
+ * 提示注入和工具投毒偵測
4
+ *
5
+ * Scans skill instructions for patterns that indicate:
6
+ * 1. Prompt injection — hidden directives that override system prompts
7
+ * 2. Tool poisoning — redefining tools or escalating privileges
8
+ * 3. Hidden Unicode — zero-width chars, RTL overrides, homoglyph attacks
9
+ * 4. Encoded payloads — Base64 or hex-encoded suspicious content
10
+ */
11
+ import type { CheckResult } from '../types.js';
12
+ export declare function checkInstructions(instructions: string): CheckResult;
13
+ //# sourceMappingURL=instruction-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instruction-check.d.ts","sourceRoot":"","sources":["../../src/checks/instruction-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,aAAa,CAAC;AAoG7D,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAiEnE"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Prompt injection and tool poisoning detection
3
+ * 提示注入和工具投毒偵測
4
+ *
5
+ * Scans skill instructions for patterns that indicate:
6
+ * 1. Prompt injection — hidden directives that override system prompts
7
+ * 2. Tool poisoning — redefining tools or escalating privileges
8
+ * 3. Hidden Unicode — zero-width chars, RTL overrides, homoglyph attacks
9
+ * 4. Encoded payloads — Base64 or hex-encoded suspicious content
10
+ */
11
+ const PATTERNS = [
12
+ // Prompt injection patterns
13
+ {
14
+ id: 'pi-ignore-previous',
15
+ title: 'Prompt injection: ignore previous instructions',
16
+ regex: /\b(ignore|disregard|forget|override)\b.{0,30}\b(previous|above|prior|earlier|system)\b.{0,20}\b(instructions?|prompt|rules?|context)\b/i,
17
+ severity: 'critical',
18
+ category: 'prompt-injection',
19
+ },
20
+ {
21
+ id: 'pi-you-are-now',
22
+ title: 'Prompt injection: identity override',
23
+ regex: /\b(you are now|act as|pretend to be|assume the role|from now on you)\b/i,
24
+ severity: 'high',
25
+ category: 'prompt-injection',
26
+ },
27
+ {
28
+ id: 'pi-system-prompt',
29
+ title: 'Prompt injection: system prompt manipulation',
30
+ regex: /\b(system prompt|system message|system instruction|<\|system\|>|<<SYS>>)\b/i,
31
+ severity: 'critical',
32
+ category: 'prompt-injection',
33
+ },
34
+ {
35
+ id: 'pi-do-anything',
36
+ title: 'Prompt injection: jailbreak pattern',
37
+ regex: /\b(DAN|do anything now|jailbreak|bypass safety|ignore safety|no restrictions)\b/i,
38
+ severity: 'critical',
39
+ category: 'prompt-injection',
40
+ },
41
+ {
42
+ id: 'pi-hidden-text',
43
+ title: 'Hidden text via HTML/markdown comments',
44
+ regex: /<!--[\s\S]*?(ignore|override|system|inject|bypass)[\s\S]*?-->/i,
45
+ severity: 'high',
46
+ category: 'prompt-injection',
47
+ },
48
+ // Tool poisoning patterns
49
+ {
50
+ id: 'tp-sudo-escalation',
51
+ title: 'Privilege escalation via sudo/admin',
52
+ regex: /\b(sudo\s|as\s+root|run\s+as\s+admin|--privileged|chmod\s+777|chmod\s+u\+s)\b/i,
53
+ severity: 'high',
54
+ category: 'tool-poisoning',
55
+ },
56
+ {
57
+ id: 'tp-reverse-shell',
58
+ title: 'Reverse shell pattern detected',
59
+ regex: /\b(nc\s+-[elp]|ncat\s+-|bash\s+-i\s+>&|\/dev\/tcp\/|mkfifo|socat\s.*exec)/i,
60
+ severity: 'critical',
61
+ category: 'tool-poisoning',
62
+ },
63
+ {
64
+ id: 'tp-curl-pipe-bash',
65
+ title: 'Remote code execution via curl|bash',
66
+ regex: /\b(curl|wget)\s+.*\|\s*(bash|sh|zsh|python|node|perl)/i,
67
+ severity: 'high',
68
+ category: 'tool-poisoning',
69
+ },
70
+ {
71
+ id: 'tp-env-exfil',
72
+ title: 'Environment variable exfiltration',
73
+ regex: /\b(printenv|env\b|set\b).*\|\s*(curl|wget|nc\b)|curl.*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/i,
74
+ severity: 'critical',
75
+ category: 'tool-poisoning',
76
+ },
77
+ {
78
+ id: 'tp-file-exfil',
79
+ title: 'Sensitive file access pattern',
80
+ regex: /\b(cat|less|more|head|tail)\s+.*(\/etc\/shadow|\/etc\/passwd|~\/\.ssh|\.env|credentials|\.aws\/)/i,
81
+ severity: 'high',
82
+ category: 'tool-poisoning',
83
+ },
84
+ {
85
+ id: 'tp-rm-destructive',
86
+ title: 'Destructive file operations',
87
+ regex: /\brm\s+(-rf?|--recursive).*\s+(\/|~|\$HOME|\$\(pwd\))\b/i,
88
+ severity: 'high',
89
+ category: 'tool-poisoning',
90
+ },
91
+ ];
92
+ /** Zero-width and invisible Unicode characters */
93
+ const HIDDEN_UNICODE_RE = /[\u200B\u200C\u200D\u200E\u200F\u202A-\u202E\u2060\u2061\u2062\u2063\u2064\uFEFF\u00AD]/;
94
+ /** Base64-encoded suspicious keywords */
95
+ const BASE64_BLOCK_RE = /[A-Za-z0-9+/]{40,}={0,2}/g;
96
+ const SUSPICIOUS_DECODED = /(eval|exec|system|import\s+os|subprocess|child_process|require\s*\(|__import__|curl|wget)/i;
97
+ export function checkInstructions(instructions) {
98
+ const findings = [];
99
+ // Pattern matching
100
+ for (const pattern of PATTERNS) {
101
+ const match = pattern.regex.exec(instructions);
102
+ if (match) {
103
+ const lineNum = instructions.substring(0, match.index).split('\n').length;
104
+ findings.push({
105
+ id: pattern.id,
106
+ title: pattern.title,
107
+ description: `Detected near line ${lineNum}: "${match[0].substring(0, 80)}"`,
108
+ severity: pattern.severity,
109
+ category: pattern.category,
110
+ location: `SKILL.md:${lineNum}`,
111
+ });
112
+ }
113
+ }
114
+ // Hidden Unicode check
115
+ const unicodeMatch = HIDDEN_UNICODE_RE.exec(instructions);
116
+ if (unicodeMatch) {
117
+ const lineNum = instructions.substring(0, unicodeMatch.index).split('\n').length;
118
+ const charCode = unicodeMatch[0]?.codePointAt(0) ?? 0;
119
+ findings.push({
120
+ id: 'hidden-unicode',
121
+ title: 'Hidden Unicode characters detected',
122
+ description: `Found invisible character U+${charCode.toString(16).toUpperCase().padStart(4, '0')} at line ${lineNum}. This may be used to hide malicious instructions.`,
123
+ severity: 'high',
124
+ category: 'prompt-injection',
125
+ location: `SKILL.md:${lineNum}`,
126
+ });
127
+ }
128
+ // Base64-encoded suspicious content
129
+ const b64Matches = instructions.match(BASE64_BLOCK_RE);
130
+ if (b64Matches) {
131
+ for (const b64 of b64Matches) {
132
+ try {
133
+ const decoded = Buffer.from(b64, 'base64').toString('utf-8');
134
+ if (SUSPICIOUS_DECODED.test(decoded)) {
135
+ findings.push({
136
+ id: 'encoded-payload',
137
+ title: 'Base64-encoded suspicious payload',
138
+ description: `Decoded content contains executable patterns: "${decoded.substring(0, 60)}..."`,
139
+ severity: 'critical',
140
+ category: 'prompt-injection',
141
+ });
142
+ break; // One finding is enough
143
+ }
144
+ }
145
+ catch {
146
+ // Not valid base64, skip
147
+ }
148
+ }
149
+ }
150
+ const hasCritical = findings.some((f) => f.severity === 'critical');
151
+ const hasHigh = findings.some((f) => f.severity === 'high');
152
+ const status = hasCritical ? 'fail' : hasHigh ? 'warn' : findings.length > 0 ? 'warn' : 'pass';
153
+ const label = findings.length === 0
154
+ ? 'Prompt Safety: No injection patterns detected'
155
+ : `Prompt Safety: ${findings.length} suspicious pattern(s) detected`;
156
+ return { status, label, findings };
157
+ }
158
+ //# sourceMappingURL=instruction-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instruction-check.js","sourceRoot":"","sources":["../../src/checks/instruction-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Manifest structure validation
3
+ * 清單結構驗證
4
+ */
5
+ import type { SkillManifest, CheckResult } from '../types.js';
6
+ export declare function checkManifest(manifest: SkillManifest | null): CheckResult;
7
+ //# sourceMappingURL=manifest-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-check.d.ts","sourceRoot":"","sources":["../../src/checks/manifest-check.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgB,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,GAAG,WAAW,CA8EzE"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Manifest structure validation
3
+ * 清單結構驗證
4
+ */
5
+ export function checkManifest(manifest) {
6
+ const findings = [];
7
+ if (!manifest) {
8
+ findings.push({
9
+ id: 'manifest-missing',
10
+ title: 'SKILL.md not found',
11
+ description: 'No SKILL.md file found in the skill directory. This is required for all valid skills.',
12
+ severity: 'critical',
13
+ category: 'manifest',
14
+ });
15
+ return { status: 'fail', label: 'Manifest: SKILL.md not found', findings };
16
+ }
17
+ if (!manifest.name || manifest.name.trim() === '') {
18
+ findings.push({
19
+ id: 'manifest-no-name',
20
+ title: 'Missing skill name',
21
+ description: 'SKILL.md frontmatter must include a "name" field.',
22
+ severity: 'high',
23
+ category: 'manifest',
24
+ });
25
+ }
26
+ if (!manifest.description || manifest.description.trim() === '') {
27
+ findings.push({
28
+ id: 'manifest-no-description',
29
+ title: 'Missing skill description',
30
+ description: 'SKILL.md frontmatter should include a "description" field.',
31
+ severity: 'medium',
32
+ category: 'manifest',
33
+ });
34
+ }
35
+ if (!manifest.license) {
36
+ findings.push({
37
+ id: 'manifest-no-license',
38
+ title: 'No license declared',
39
+ description: 'Skill does not declare a license. Consider adding one for trust and legal clarity.',
40
+ severity: 'low',
41
+ category: 'manifest',
42
+ });
43
+ }
44
+ if (manifest.instructions.trim().length < 50) {
45
+ findings.push({
46
+ id: 'manifest-short-instructions',
47
+ title: 'Suspiciously short instructions',
48
+ description: `Skill instructions are only ${manifest.instructions.trim().length} characters. This may indicate an incomplete or placeholder skill.`,
49
+ severity: 'medium',
50
+ category: 'manifest',
51
+ });
52
+ }
53
+ if (manifest.metadata?.version) {
54
+ const semverRe = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
55
+ if (!semverRe.test(manifest.metadata.version)) {
56
+ findings.push({
57
+ id: 'manifest-bad-version',
58
+ title: 'Invalid version format',
59
+ description: `Version "${manifest.metadata.version}" is not valid semver (expected X.Y.Z).`,
60
+ severity: 'low',
61
+ category: 'manifest',
62
+ });
63
+ }
64
+ }
65
+ const status = findings.some((f) => f.severity === 'critical' || f.severity === 'high')
66
+ ? 'fail'
67
+ : findings.length > 0
68
+ ? 'warn'
69
+ : 'pass';
70
+ const label = status === 'pass'
71
+ ? 'Manifest: Valid SKILL.md with all required fields'
72
+ : `Manifest: ${findings.length} issue(s) found`;
73
+ return { status, label, findings };
74
+ }
75
+ //# sourceMappingURL=manifest-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-check.js","sourceRoot":"","sources":["../../src/checks/manifest-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Permission scope analysis
3
+ * 權限範圍分析
4
+ *
5
+ * Analyzes what tools and permissions a skill requires based on its instructions.
6
+ * 根據技能指令分析其需要的工具和權限。
7
+ */
8
+ import type { SkillManifest, CheckResult } from '../types.js';
9
+ export declare function checkPermissions(manifest: SkillManifest): CheckResult;
10
+ //# sourceMappingURL=permission-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-check.d.ts","sourceRoot":"","sources":["../../src/checks/permission-check.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgB,WAAW,EAAE,MAAM,aAAa,CAAC;AAmB5E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAoDrE"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Permission scope analysis
3
+ * 權限範圍分析
4
+ *
5
+ * Analyzes what tools and permissions a skill requires based on its instructions.
6
+ * 根據技能指令分析其需要的工具和權限。
7
+ */
8
+ const TOOL_PATTERNS = [
9
+ { name: 'Bash/Shell', regex: /\b(bash|shell|terminal|command line|execute.*command|run.*command)\b/i, risk: 'high', reason: 'Can execute arbitrary system commands' },
10
+ { name: 'File Write', regex: /\b(write.*file|create.*file|save.*to.*disk|overwrite)\b/i, risk: 'medium', reason: 'Can modify files on disk' },
11
+ { name: 'File Read', regex: /\b(read.*file|cat\s|open.*file|load.*from)\b/i, risk: 'low', reason: 'Can read files from disk' },
12
+ { name: 'Network/HTTP', regex: /\b(fetch|http request|api call|curl|wget|download|upload)\b/i, risk: 'medium', reason: 'Can make network requests' },
13
+ { name: 'Browser', regex: /\b(browser|open.*url|navigate.*to|web.*scrape|playwright|puppeteer)\b/i, risk: 'medium', reason: 'Can open URLs and interact with web pages' },
14
+ { name: 'Database', regex: /\b(database|sql|query|insert|update|delete.*from|mongodb|postgres|mysql)\b/i, risk: 'high', reason: 'Can access and modify database contents' },
15
+ { name: 'Credentials', regex: /\b(api[_\s]?key|token|password|secret|credential|auth)\b/i, risk: 'medium', reason: 'Handles sensitive credentials' },
16
+ ];
17
+ export function checkPermissions(manifest) {
18
+ const findings = [];
19
+ const instructions = manifest.instructions;
20
+ const detectedTools = [];
21
+ for (const pattern of TOOL_PATTERNS) {
22
+ if (pattern.regex.test(instructions)) {
23
+ detectedTools.push({ name: pattern.name, risk: pattern.risk });
24
+ if (pattern.risk === 'high') {
25
+ findings.push({
26
+ id: `perm-${pattern.name.toLowerCase().replace(/[^a-z]/g, '-')}`,
27
+ title: `Skill uses ${pattern.name} (${pattern.risk} risk)`,
28
+ description: pattern.reason,
29
+ severity: 'medium',
30
+ category: 'permission',
31
+ });
32
+ }
33
+ }
34
+ }
35
+ // Check for command-dispatch tools (can bypass model safety)
36
+ if (manifest.commandDispatch === 'tool') {
37
+ findings.push({
38
+ id: 'perm-command-dispatch',
39
+ title: 'Skill uses tool dispatch mode',
40
+ description: 'This skill dispatches directly to a tool, bypassing the AI model. Verify the dispatched tool is safe.',
41
+ severity: 'medium',
42
+ category: 'permission',
43
+ });
44
+ }
45
+ // Check if model invocation is disabled (unusual)
46
+ if (manifest.disableModelInvocation) {
47
+ findings.push({
48
+ id: 'perm-no-model',
49
+ title: 'Model invocation disabled',
50
+ description: 'This skill is excluded from model prompts. It can only be invoked via slash command.',
51
+ severity: 'low',
52
+ category: 'permission',
53
+ });
54
+ }
55
+ const highRiskCount = detectedTools.filter((t) => t.risk === 'high').length;
56
+ const status = highRiskCount > 0 ? 'warn' : 'pass';
57
+ const toolNames = detectedTools.map((t) => t.name).join(', ');
58
+ const label = detectedTools.length > 0
59
+ ? `Permissions: Uses ${toolNames}`
60
+ : 'Permissions: No special tool usage detected';
61
+ return { status, label, findings };
62
+ }
63
+ //# sourceMappingURL=permission-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-check.js","sourceRoot":"","sources":["../../src/checks/permission-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Panguard Skill Auditor - Main entry
3
+ * Panguard 技能審計器 - 主入口
4
+ *
5
+ * Security auditor for OpenClaw/AgentSkills SKILL.md files.
6
+ * Checks manifest validity, prompt injection, code security,
7
+ * dependencies, and permissions.
8
+ *
9
+ * @module @panguard-ai/panguard-skill-auditor
10
+ */
11
+ import type { AuditReport } from './types.js';
12
+ export type { AuditReport, AuditFinding, CheckResult, SkillManifest } from './types.js';
13
+ export { parseSkillManifest } from './manifest-parser.js';
14
+ /**
15
+ * Audit a skill directory for security issues.
16
+ * 審計技能目錄的安全問題。
17
+ *
18
+ * @param skillDir - Path to the skill directory containing SKILL.md
19
+ * @returns Complete audit report
20
+ */
21
+ export declare function auditSkill(skillDir: string): Promise<AuditReport>;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAE3D,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0CvE"}
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Panguard Skill Auditor - Main entry
3
+ * Panguard 技能審計器 - 主入口
4
+ *
5
+ * Security auditor for OpenClaw/AgentSkills SKILL.md files.
6
+ * Checks manifest validity, prompt injection, code security,
7
+ * dependencies, and permissions.
8
+ *
9
+ * @module @panguard-ai/panguard-skill-auditor
10
+ */
11
+ import { parseSkillManifest } from './manifest-parser.js';
12
+ import { checkManifest } from './checks/manifest-check.js';
13
+ import { checkInstructions } from './checks/instruction-check.js';
14
+ import { checkCode } from './checks/code-check.js';
15
+ import { checkDependencies } from './checks/dependency-check.js';
16
+ import { checkPermissions } from './checks/permission-check.js';
17
+ import { calculateRiskScore } from './risk-scorer.js';
18
+ export { parseSkillManifest } from './manifest-parser.js';
19
+ /**
20
+ * Audit a skill directory for security issues.
21
+ * 審計技能目錄的安全問題。
22
+ *
23
+ * @param skillDir - Path to the skill directory containing SKILL.md
24
+ * @returns Complete audit report
25
+ */
26
+ export async function auditSkill(skillDir) {
27
+ const startTime = Date.now();
28
+ // 1. Parse manifest
29
+ const manifest = await parseSkillManifest(skillDir);
30
+ // 2. Run all checks
31
+ const checks = [];
32
+ // Manifest validation
33
+ checks.push(checkManifest(manifest));
34
+ if (manifest) {
35
+ // Prompt injection + tool poisoning
36
+ checks.push(checkInstructions(manifest.instructions));
37
+ // Dependencies
38
+ checks.push(checkDependencies(manifest));
39
+ // Permissions
40
+ checks.push(checkPermissions(manifest));
41
+ }
42
+ // Code security (SAST + secrets) — always run on directory
43
+ checks.push(await checkCode(skillDir));
44
+ // 3. Aggregate findings
45
+ const allFindings = checks.flatMap((c) => c.findings);
46
+ // 4. Calculate risk score
47
+ const { score, level } = calculateRiskScore(allFindings);
48
+ return {
49
+ skillPath: skillDir,
50
+ manifest,
51
+ riskScore: score,
52
+ riskLevel: level,
53
+ checks,
54
+ findings: allFindings,
55
+ auditedAt: new Date().toISOString(),
56
+ durationMs: Date.now() - startTime,
57
+ };
58
+ }
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAItD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,oBAAoB;IACpB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAEpD,oBAAoB;IACpB,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,sBAAsB;IACtB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IAErC,IAAI,QAAQ,EAAE,CAAC;QACb,oCAAoC;QACpC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QAEtD,eAAe;QACf,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEzC,cAAc;QACd,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,2DAA2D;IAC3D,MAAM,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEvC,wBAAwB;IACxB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAEtD,0BAA0B;IAC1B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAEzD,OAAO;QACL,SAAS,EAAE,QAAQ;QACnB,QAAQ;QACR,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE,KAAK;QAChB,MAAM;QACN,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SKILL.md manifest parser
3
+ * SKILL.md 清單解析器
4
+ *
5
+ * Parses YAML frontmatter from SKILL.md files following the AgentSkills spec.
6
+ * 解析遵循 AgentSkills 規範的 SKILL.md 檔案中的 YAML frontmatter。
7
+ *
8
+ * @module @panguard-ai/panguard-skill-auditor/manifest-parser
9
+ */
10
+ import type { SkillManifest } from './types.js';
11
+ /**
12
+ * Parse a SKILL.md file and extract manifest + instructions.
13
+ * 解析 SKILL.md 檔案並擷取清單和指令。
14
+ */
15
+ export declare function parseSkillManifest(skillDir: string): Promise<SkillManifest | null>;
16
+ //# sourceMappingURL=manifest-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-parser.d.ts","sourceRoot":"","sources":["../src/manifest-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAC;AAI/D;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAyDxF"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * SKILL.md manifest parser
3
+ * SKILL.md 清單解析器
4
+ *
5
+ * Parses YAML frontmatter from SKILL.md files following the AgentSkills spec.
6
+ * 解析遵循 AgentSkills 規範的 SKILL.md 檔案中的 YAML frontmatter。
7
+ *
8
+ * @module @panguard-ai/panguard-skill-auditor/manifest-parser
9
+ */
10
+ import { promises as fs } from 'node:fs';
11
+ import path from 'node:path';
12
+ import yaml from 'js-yaml';
13
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
14
+ /**
15
+ * Parse a SKILL.md file and extract manifest + instructions.
16
+ * 解析 SKILL.md 檔案並擷取清單和指令。
17
+ */
18
+ export async function parseSkillManifest(skillDir) {
19
+ const skillPath = path.join(skillDir, 'SKILL.md');
20
+ let content;
21
+ try {
22
+ content = await fs.readFile(skillPath, 'utf-8');
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ const match = FRONTMATTER_RE.exec(content);
28
+ if (!match) {
29
+ // No frontmatter — treat entire content as instructions
30
+ return {
31
+ name: path.basename(skillDir),
32
+ description: '',
33
+ instructions: content,
34
+ };
35
+ }
36
+ const [, frontmatterRaw, instructions] = match;
37
+ let parsed;
38
+ try {
39
+ parsed = yaml.load(frontmatterRaw ?? '') ?? {};
40
+ }
41
+ catch {
42
+ return {
43
+ name: path.basename(skillDir),
44
+ description: '',
45
+ instructions: instructions ?? content,
46
+ };
47
+ }
48
+ // Parse metadata — can be a JSON string or an object
49
+ let metadata;
50
+ if (typeof parsed['metadata'] === 'string') {
51
+ try {
52
+ metadata = JSON.parse(parsed['metadata']);
53
+ }
54
+ catch {
55
+ metadata = undefined;
56
+ }
57
+ }
58
+ else if (typeof parsed['metadata'] === 'object' && parsed['metadata'] !== null) {
59
+ metadata = parsed['metadata'];
60
+ }
61
+ return {
62
+ name: parsed['name'] ?? path.basename(skillDir),
63
+ description: parsed['description'] ?? '',
64
+ license: parsed['license'],
65
+ homepage: parsed['homepage'],
66
+ userInvocable: parsed['user-invocable'],
67
+ disableModelInvocation: parsed['disable-model-invocation'],
68
+ commandDispatch: parsed['command-dispatch'],
69
+ commandTool: parsed['command-tool'],
70
+ metadata,
71
+ instructions: instructions ?? '',
72
+ };
73
+ }
74
+ //# sourceMappingURL=manifest-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-parser.js","sourceRoot":"","sources":["../src/manifest-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,MAAM,cAAc,GAAG,6CAA6C,CAAC;AAErE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAElD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,wDAAwD;QACxD,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC7B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,OAAO;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,EAAE,cAAc,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC;IAE/C,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAI,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAA6B,IAAI,EAAE,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC7B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,YAAY,IAAI,OAAO;SACtC,CAAC;IACJ,CAAC;IAED,qDAAqD;IACrD,IAAI,QAAmC,CAAC;IACxC,IAAI,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAkB,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,SAAS,CAAC;QACvB,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;QACjF,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAkB,CAAC;IACjD,CAAC;IAED,OAAO;QACL,IAAI,EAAG,MAAM,CAAC,MAAM,CAAY,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC3D,WAAW,EAAG,MAAM,CAAC,aAAa,CAAY,IAAI,EAAE;QACpD,OAAO,EAAE,MAAM,CAAC,SAAS,CAAuB;QAChD,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAuB;QAClD,aAAa,EAAE,MAAM,CAAC,gBAAgB,CAAwB;QAC9D,sBAAsB,EAAE,MAAM,CAAC,0BAA0B,CAAwB;QACjF,eAAe,EAAE,MAAM,CAAC,kBAAkB,CAAuB;QACjE,WAAW,EAAE,MAAM,CAAC,cAAc,CAAuB;QACzD,QAAQ;QACR,YAAY,EAAE,YAAY,IAAI,EAAE;KACjC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Risk scoring engine
3
+ * 風險評分引擎
4
+ *
5
+ * Calculates a 0-100 risk score from audit findings.
6
+ * 根據審計發現計算 0-100 風險評分。
7
+ */
8
+ import type { AuditFinding, AuditReport } from './types.js';
9
+ /**
10
+ * Calculate risk score (0-100) from findings.
11
+ * Higher score = higher risk.
12
+ */
13
+ export declare function calculateRiskScore(findings: AuditFinding[]): {
14
+ score: number;
15
+ level: AuditReport['riskLevel'];
16
+ };
17
+ //# sourceMappingURL=risk-scorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-scorer.d.ts","sourceRoot":"","sources":["../src/risk-scorer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS5D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,CAAA;CAAE,CAiB/G"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Risk scoring engine
3
+ * 風險評分引擎
4
+ *
5
+ * Calculates a 0-100 risk score from audit findings.
6
+ * 根據審計發現計算 0-100 風險評分。
7
+ */
8
+ const SEVERITY_WEIGHTS = {
9
+ critical: 25,
10
+ high: 15,
11
+ medium: 5,
12
+ low: 1,
13
+ };
14
+ /**
15
+ * Calculate risk score (0-100) from findings.
16
+ * Higher score = higher risk.
17
+ */
18
+ export function calculateRiskScore(findings) {
19
+ let rawScore = 0;
20
+ for (const finding of findings) {
21
+ rawScore += SEVERITY_WEIGHTS[finding.severity] ?? 0;
22
+ }
23
+ // Cap at 100
24
+ const score = Math.min(100, rawScore);
25
+ let level;
26
+ if (score >= 70)
27
+ level = 'CRITICAL';
28
+ else if (score >= 40)
29
+ level = 'HIGH';
30
+ else if (score >= 15)
31
+ level = 'MEDIUM';
32
+ else
33
+ level = 'LOW';
34
+ return { score, level };
35
+ }
36
+ //# sourceMappingURL=risk-scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-scorer.js","sourceRoot":"","sources":["../src/risk-scorer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,gBAAgB,GAA2B;IAC/C,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;CACP,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAwB;IACzD,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,QAAQ,IAAI,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,aAAa;IACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAEtC,IAAI,KAA+B,CAAC;IACpC,IAAI,KAAK,IAAI,EAAE;QAAE,KAAK,GAAG,UAAU,CAAC;SAC/B,IAAI,KAAK,IAAI,EAAE;QAAE,KAAK,GAAG,MAAM,CAAC;SAChC,IAAI,KAAK,IAAI,EAAE;QAAE,KAAK,GAAG,QAAQ,CAAC;;QAClC,KAAK,GAAG,KAAK,CAAC;IAEnB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Panguard Skill Auditor - Type definitions
3
+ * Panguard 技能審計器 - 型別定義
4
+ *
5
+ * @module @panguard-ai/panguard-skill-auditor/types
6
+ */
7
+ import type { Severity } from '@panguard-ai/core';
8
+ /** Parsed SKILL.md manifest */
9
+ export interface SkillManifest {
10
+ name: string;
11
+ description: string;
12
+ license?: string;
13
+ homepage?: string;
14
+ userInvocable?: boolean;
15
+ disableModelInvocation?: boolean;
16
+ commandDispatch?: string;
17
+ commandTool?: string;
18
+ metadata?: SkillMetadata;
19
+ /** Raw instruction body (after frontmatter) */
20
+ instructions: string;
21
+ }
22
+ export interface SkillMetadata {
23
+ author?: string;
24
+ version?: string;
25
+ tags?: string[];
26
+ triggers?: string[];
27
+ openclaw?: {
28
+ requires?: {
29
+ bins?: string[];
30
+ env?: string[];
31
+ config?: string[];
32
+ };
33
+ primaryEnv?: string;
34
+ os?: string[];
35
+ always?: boolean;
36
+ homepage?: string;
37
+ };
38
+ [key: string]: unknown;
39
+ }
40
+ /** Single audit finding */
41
+ export interface AuditFinding {
42
+ id: string;
43
+ title: string;
44
+ description: string;
45
+ severity: Severity;
46
+ category: 'manifest' | 'prompt-injection' | 'tool-poisoning' | 'code' | 'secrets' | 'dependency' | 'permission';
47
+ location?: string;
48
+ }
49
+ /** Result of a single check category */
50
+ export interface CheckResult {
51
+ status: 'pass' | 'warn' | 'fail' | 'info';
52
+ label: string;
53
+ findings: AuditFinding[];
54
+ }
55
+ /** Complete audit report */
56
+ export interface AuditReport {
57
+ skillPath: string;
58
+ manifest: SkillManifest | null;
59
+ riskScore: number;
60
+ riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
61
+ checks: CheckResult[];
62
+ findings: AuditFinding[];
63
+ auditedAt: string;
64
+ durationMs: number;
65
+ }
66
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE;YACT,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;YAChB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;SACnB,CAAC;QACF,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;QACd,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,2BAA2B;AAC3B,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,UAAU,GAAG,kBAAkB,GAAG,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,CAAC;IAChH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IAClD,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Panguard Skill Auditor - Type definitions
3
+ * Panguard 技能審計器 - 型別定義
4
+ *
5
+ * @module @panguard-ai/panguard-skill-auditor/types
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@panguard-ai/panguard-skill-auditor",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Security auditor for OpenClaw/AgentSkills SKILL.md files",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "package.json",
14
+ "README.md"
15
+ ],
16
+ "dependencies": {
17
+ "js-yaml": "^4.1.0",
18
+ "@panguard-ai/core": "0.3.1",
19
+ "@panguard-ai/panguard-scan": "0.2.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/js-yaml": "^4.0.9",
23
+ "@types/node": "^22.14.0",
24
+ "typescript": "~5.7.3",
25
+ "vitest": "^3.0.0"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc --build",
29
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "vitest run",
32
+ "dev": "tsc --build --watch"
33
+ }
34
+ }