skillscan 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.
- package/.eslintrc.json +15 -0
- package/README.md +177 -0
- package/dist/cli/commands/scan.d.ts +5 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +67 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatters.d.ts +3 -0
- package/dist/output/formatters.d.ts.map +1 -0
- package/dist/output/formatters.js +256 -0
- package/dist/output/formatters.js.map +1 -0
- package/dist/scanner/engine.d.ts +7 -0
- package/dist/scanner/engine.d.ts.map +1 -0
- package/dist/scanner/engine.js +119 -0
- package/dist/scanner/engine.js.map +1 -0
- package/dist/scanner/parsers/skilljson.d.ts +3 -0
- package/dist/scanner/parsers/skilljson.d.ts.map +1 -0
- package/dist/scanner/parsers/skilljson.js +38 -0
- package/dist/scanner/parsers/skilljson.js.map +1 -0
- package/dist/scanner/parsers/skillmd.d.ts +3 -0
- package/dist/scanner/parsers/skillmd.d.ts.map +1 -0
- package/dist/scanner/parsers/skillmd.js +48 -0
- package/dist/scanner/parsers/skillmd.js.map +1 -0
- package/dist/scanner/rules/file-access.d.ts +11 -0
- package/dist/scanner/rules/file-access.d.ts.map +1 -0
- package/dist/scanner/rules/file-access.js +76 -0
- package/dist/scanner/rules/file-access.js.map +1 -0
- package/dist/scanner/rules/hidden-instructions.d.ts +13 -0
- package/dist/scanner/rules/hidden-instructions.d.ts.map +1 -0
- package/dist/scanner/rules/hidden-instructions.js +88 -0
- package/dist/scanner/rules/hidden-instructions.js.map +1 -0
- package/dist/scanner/rules/index.d.ts +4 -0
- package/dist/scanner/rules/index.d.ts.map +1 -0
- package/dist/scanner/rules/index.js +21 -0
- package/dist/scanner/rules/index.js.map +1 -0
- package/dist/scanner/rules/prompt-injection.d.ts +11 -0
- package/dist/scanner/rules/prompt-injection.d.ts.map +1 -0
- package/dist/scanner/rules/prompt-injection.js +130 -0
- package/dist/scanner/rules/prompt-injection.js.map +1 -0
- package/dist/scanner/rules/sensitive-paths.d.ts +11 -0
- package/dist/scanner/rules/sensitive-paths.d.ts.map +1 -0
- package/dist/scanner/rules/sensitive-paths.js +142 -0
- package/dist/scanner/rules/sensitive-paths.js.map +1 -0
- package/dist/scoring/trust-score.d.ts +5 -0
- package/dist/scoring/trust-score.d.ts.map +1 -0
- package/dist/scoring/trust-score.js +35 -0
- package/dist/scoring/trust-score.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/jest.config.js +9 -0
- package/package.json +42 -0
- package/skill/SKILL.md +76 -0
- package/src/cli/commands/scan.ts +35 -0
- package/src/cli/index.ts +19 -0
- package/src/index.ts +5 -0
- package/src/output/formatters.ts +296 -0
- package/src/scanner/engine.ts +99 -0
- package/src/scanner/parsers/skilljson.ts +37 -0
- package/src/scanner/parsers/skillmd.ts +46 -0
- package/src/scanner/rules/file-access.ts +78 -0
- package/src/scanner/rules/hidden-instructions.ts +92 -0
- package/src/scanner/rules/index.ts +20 -0
- package/src/scanner/rules/prompt-injection.ts +133 -0
- package/src/scanner/rules/sensitive-paths.ts +144 -0
- package/src/scoring/trust-score.ts +34 -0
- package/src/types.ts +54 -0
- package/tests/fixtures/malicious-skill/SKILL.md +26 -0
- package/tests/fixtures/safe-skill/SKILL.md +25 -0
- package/tests/rules/prompt-injection.test.ts +123 -0
- package/tests/rules/sensitive-paths.test.ts +115 -0
- package/tests/scoring/trust-score.test.ts +100 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Rule, Finding, SkillMetadata, Severity } from '../../types';
|
|
2
|
+
export declare class SensitivePathsRule implements Rule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
severity: Severity;
|
|
7
|
+
private patterns;
|
|
8
|
+
check(content: string, _metadata: SkillMetadata, filePath: string): Finding[];
|
|
9
|
+
private getLineNumber;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=sensitive-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sensitive-paths.d.ts","sourceRoot":"","sources":["../../../src/scanner/rules/sensitive-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErE,qBAAa,kBAAmB,YAAW,IAAI;IAC7C,EAAE,SAAqB;IACvB,IAAI,SAA+B;IACnC,WAAW,SAAuE;IAClF,QAAQ,EAAE,QAAQ,CAAc;IAEhC,OAAO,CAAC,QAAQ,CAuGd;IAEF,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE;IA2B7E,OAAO,CAAC,aAAa;CAGtB"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SensitivePathsRule = void 0;
|
|
4
|
+
class SensitivePathsRule {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.id = 'sensitive-paths';
|
|
7
|
+
this.name = 'Sensitive Path References';
|
|
8
|
+
this.description = 'Detects references to sensitive system paths and credential files';
|
|
9
|
+
this.severity = 'CRITICAL';
|
|
10
|
+
this.patterns = [
|
|
11
|
+
// SSH
|
|
12
|
+
{
|
|
13
|
+
name: 'ssh-keys',
|
|
14
|
+
regex: /~?\/?\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa|authorized_keys|known_hosts|config)/gi,
|
|
15
|
+
severity: 'CRITICAL',
|
|
16
|
+
message: 'SSH key/config path referenced'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'ssh-directory',
|
|
20
|
+
regex: /~?\/?\.ssh\/?(?:\*|$)/gi,
|
|
21
|
+
severity: 'CRITICAL',
|
|
22
|
+
message: 'SSH directory referenced'
|
|
23
|
+
},
|
|
24
|
+
// AWS
|
|
25
|
+
{
|
|
26
|
+
name: 'aws-credentials',
|
|
27
|
+
regex: /~?\/?\.aws\/(credentials|config)/gi,
|
|
28
|
+
severity: 'CRITICAL',
|
|
29
|
+
message: 'AWS credentials file referenced'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'aws-env-vars',
|
|
33
|
+
regex: /AWS_(SECRET_ACCESS_KEY|ACCESS_KEY_ID|SESSION_TOKEN)/gi,
|
|
34
|
+
severity: 'CRITICAL',
|
|
35
|
+
message: 'AWS credential environment variable referenced'
|
|
36
|
+
},
|
|
37
|
+
// GCP
|
|
38
|
+
{
|
|
39
|
+
name: 'gcp-credentials',
|
|
40
|
+
regex: /GOOGLE_APPLICATION_CREDENTIALS|~?\/?\.config\/gcloud/gi,
|
|
41
|
+
severity: 'CRITICAL',
|
|
42
|
+
message: 'GCP credential path/variable referenced'
|
|
43
|
+
},
|
|
44
|
+
// Azure
|
|
45
|
+
{
|
|
46
|
+
name: 'azure-credentials',
|
|
47
|
+
regex: /AZURE_(CLIENT_SECRET|TENANT_ID|CLIENT_ID)|~?\/?\.azure/gi,
|
|
48
|
+
severity: 'CRITICAL',
|
|
49
|
+
message: 'Azure credential path/variable referenced'
|
|
50
|
+
},
|
|
51
|
+
// GPG/PGP
|
|
52
|
+
{
|
|
53
|
+
name: 'gpg-keys',
|
|
54
|
+
regex: /~?\/?\.gnupg\/?|\.gpg\b|secring\.gpg/gi,
|
|
55
|
+
severity: 'HIGH',
|
|
56
|
+
message: 'GPG key path referenced'
|
|
57
|
+
},
|
|
58
|
+
// Generic secrets
|
|
59
|
+
{
|
|
60
|
+
name: 'env-files',
|
|
61
|
+
regex: /\.env(\.local|\.prod|\.production|\.development|\.staging)?(?:\b|$)/gi,
|
|
62
|
+
severity: 'HIGH',
|
|
63
|
+
message: 'Environment file referenced'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'password-files',
|
|
67
|
+
regex: /\/etc\/passwd|\/etc\/shadow|\.password|\.secret/gi,
|
|
68
|
+
severity: 'CRITICAL',
|
|
69
|
+
message: 'Password/secret file referenced'
|
|
70
|
+
},
|
|
71
|
+
// Browser data
|
|
72
|
+
{
|
|
73
|
+
name: 'browser-data',
|
|
74
|
+
regex: /~?\/?\.config\/(google-chrome|chromium|BraveSoftware)\/|Library\/Application Support\/(Google\/Chrome|BraveSoftware)/gi,
|
|
75
|
+
severity: 'HIGH',
|
|
76
|
+
message: 'Browser profile data referenced'
|
|
77
|
+
},
|
|
78
|
+
// Token patterns
|
|
79
|
+
{
|
|
80
|
+
name: 'api-tokens',
|
|
81
|
+
regex: /(API_KEY|API_SECRET|AUTH_TOKEN|PRIVATE_KEY|SECRET_KEY|ACCESS_TOKEN)(?:\s*=|\s*:)/gi,
|
|
82
|
+
severity: 'HIGH',
|
|
83
|
+
message: 'API token/key variable pattern detected'
|
|
84
|
+
},
|
|
85
|
+
// Kubernetes
|
|
86
|
+
{
|
|
87
|
+
name: 'kube-config',
|
|
88
|
+
regex: /~?\/?\.kube\/config|KUBECONFIG/gi,
|
|
89
|
+
severity: 'HIGH',
|
|
90
|
+
message: 'Kubernetes config referenced'
|
|
91
|
+
},
|
|
92
|
+
// Docker
|
|
93
|
+
{
|
|
94
|
+
name: 'docker-config',
|
|
95
|
+
regex: /~?\/?\.docker\/config\.json/gi,
|
|
96
|
+
severity: 'HIGH',
|
|
97
|
+
message: 'Docker config (may contain registry auth) referenced'
|
|
98
|
+
},
|
|
99
|
+
// NPM
|
|
100
|
+
{
|
|
101
|
+
name: 'npm-tokens',
|
|
102
|
+
regex: /~?\/?\.npmrc|NPM_TOKEN/gi,
|
|
103
|
+
severity: 'HIGH',
|
|
104
|
+
message: 'NPM config/token referenced'
|
|
105
|
+
},
|
|
106
|
+
// Git credentials
|
|
107
|
+
{
|
|
108
|
+
name: 'git-credentials',
|
|
109
|
+
regex: /~?\/?\.git-credentials|~?\/?\.gitconfig/gi,
|
|
110
|
+
severity: 'HIGH',
|
|
111
|
+
message: 'Git credentials/config referenced'
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
check(content, _metadata, filePath) {
|
|
116
|
+
const findings = [];
|
|
117
|
+
const lines = content.split('\n');
|
|
118
|
+
for (const pattern of this.patterns) {
|
|
119
|
+
let match;
|
|
120
|
+
const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
121
|
+
while ((match = regex.exec(content)) !== null) {
|
|
122
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
123
|
+
const contextLine = lines[lineNumber - 1] || '';
|
|
124
|
+
findings.push({
|
|
125
|
+
ruleId: this.id,
|
|
126
|
+
ruleName: this.name,
|
|
127
|
+
severity: pattern.severity,
|
|
128
|
+
message: `${pattern.message}: "${match[0]}"`,
|
|
129
|
+
file: filePath,
|
|
130
|
+
line: lineNumber,
|
|
131
|
+
context: contextLine.trim().slice(0, 100)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return findings;
|
|
136
|
+
}
|
|
137
|
+
getLineNumber(content, index) {
|
|
138
|
+
return content.slice(0, index).split('\n').length;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.SensitivePathsRule = SensitivePathsRule;
|
|
142
|
+
//# sourceMappingURL=sensitive-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sensitive-paths.js","sourceRoot":"","sources":["../../../src/scanner/rules/sensitive-paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Finding } from '../types';
|
|
2
|
+
export declare function calculateScore(findings: Finding[]): number;
|
|
3
|
+
export declare function getRating(score: number): 'VERIFIED' | 'CAUTION' | 'WARNING';
|
|
4
|
+
export declare function getRatingEmoji(rating: 'VERIFIED' | 'CAUTION' | 'WARNING'): string;
|
|
5
|
+
//# sourceMappingURL=trust-score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-score.d.ts","sourceRoot":"","sources":["../../src/scoring/trust-score.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAY,MAAM,UAAU,CAAC;AAS7C,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAU1D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAI3E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAMjF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateScore = calculateScore;
|
|
4
|
+
exports.getRating = getRating;
|
|
5
|
+
exports.getRatingEmoji = getRatingEmoji;
|
|
6
|
+
const SEVERITY_WEIGHTS = {
|
|
7
|
+
CRITICAL: 40,
|
|
8
|
+
HIGH: 20,
|
|
9
|
+
MEDIUM: 10,
|
|
10
|
+
LOW: 5
|
|
11
|
+
};
|
|
12
|
+
function calculateScore(findings) {
|
|
13
|
+
const baseScore = 100;
|
|
14
|
+
let deductions = 0;
|
|
15
|
+
for (const finding of findings) {
|
|
16
|
+
deductions += SEVERITY_WEIGHTS[finding.severity];
|
|
17
|
+
}
|
|
18
|
+
// Floor at 0, ceiling at 100
|
|
19
|
+
return Math.max(0, Math.min(100, baseScore - deductions));
|
|
20
|
+
}
|
|
21
|
+
function getRating(score) {
|
|
22
|
+
if (score >= 80)
|
|
23
|
+
return 'VERIFIED';
|
|
24
|
+
if (score >= 50)
|
|
25
|
+
return 'CAUTION';
|
|
26
|
+
return 'WARNING';
|
|
27
|
+
}
|
|
28
|
+
function getRatingEmoji(rating) {
|
|
29
|
+
switch (rating) {
|
|
30
|
+
case 'VERIFIED': return '🟢';
|
|
31
|
+
case 'CAUTION': return '🟡';
|
|
32
|
+
case 'WARNING': return '🔴';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=trust-score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-score.js","sourceRoot":"","sources":["../../src/scoring/trust-score.ts"],"names":[],"mappings":";;AASA,wCAUC;AAED,8BAIC;AAED,wCAMC;AA/BD,MAAM,gBAAgB,GAA6B;IACjD,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,EAAE;IACV,GAAG,EAAE,CAAC;CACP,CAAC;AAEF,SAAgB,cAAc,CAAC,QAAmB;IAChD,MAAM,SAAS,GAAG,GAAG,CAAC;IAEtB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,UAAU,IAAI,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,6BAA6B;IAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,SAAgB,SAAS,CAAC,KAAa;IACrC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,UAAU,CAAC;IACnC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IAClC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,cAAc,CAAC,MAA0C;IACvE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU,CAAC,CAAC,OAAO,IAAI,CAAC;QAC7B,KAAK,SAAS,CAAC,CAAC,OAAO,IAAI,CAAC;QAC5B,KAAK,SAAS,CAAC,CAAC,OAAO,IAAI,CAAC;IAC9B,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
|
|
2
|
+
export interface Finding {
|
|
3
|
+
ruleId: string;
|
|
4
|
+
ruleName: string;
|
|
5
|
+
severity: Severity;
|
|
6
|
+
message: string;
|
|
7
|
+
file: string;
|
|
8
|
+
line?: number;
|
|
9
|
+
column?: number;
|
|
10
|
+
context?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ScanResult {
|
|
13
|
+
skillPath: string;
|
|
14
|
+
skillName: string;
|
|
15
|
+
findings: Finding[];
|
|
16
|
+
score: number;
|
|
17
|
+
rating: 'VERIFIED' | 'CAUTION' | 'WARNING';
|
|
18
|
+
scannedAt: string;
|
|
19
|
+
scanDuration: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SkillMetadata {
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
version?: string;
|
|
25
|
+
author?: string;
|
|
26
|
+
tools?: ToolDefinition[];
|
|
27
|
+
rawContent: string;
|
|
28
|
+
frontmatter: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface ToolDefinition {
|
|
31
|
+
name: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
parameters?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export interface Rule {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
severity: Severity;
|
|
40
|
+
check(content: string, metadata: SkillMetadata, filePath: string): Finding[];
|
|
41
|
+
}
|
|
42
|
+
export interface ScanOptions {
|
|
43
|
+
path: string;
|
|
44
|
+
format?: 'json' | 'text' | 'ci';
|
|
45
|
+
verbose?: boolean;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE9D,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CAC9E;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,2BAA2B"}
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skillscan",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Trust verification layer for AI agent skill marketplaces - scan, score, and verify skills before installation",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"skillscan": "./dist/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "ts-node src/cli/index.ts",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"lint": "eslint src/**/*.ts",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": ["security", "ai", "agent", "skills", "mcp", "scanner", "trust", "verification"],
|
|
19
|
+
"author": "dejimarquis",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/dejimarquis/SkillScan"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/jest": "^30.0.0",
|
|
27
|
+
"@types/node": "^25.2.0",
|
|
28
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
29
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
30
|
+
"eslint": "^9.39.2",
|
|
31
|
+
"jest": "^30.2.0",
|
|
32
|
+
"prettier": "^3.8.1",
|
|
33
|
+
"ts-jest": "^29.4.6",
|
|
34
|
+
"ts-node": "^10.9.2",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chalk": "^5.6.2",
|
|
39
|
+
"commander": "^14.0.3",
|
|
40
|
+
"gray-matter": "^4.0.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skillscan
|
|
3
|
+
description: Security scanner for AI agent skills - detect prompt injection, data exfiltration, and supply chain attacks before installation
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
author: dejimarquis
|
|
6
|
+
tags:
|
|
7
|
+
- security
|
|
8
|
+
- scanning
|
|
9
|
+
- trust
|
|
10
|
+
- verification
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# SkillScan 🔍
|
|
14
|
+
|
|
15
|
+
**Trust verification for AI agent skills** — Scan, score, and verify skills before installation.
|
|
16
|
+
|
|
17
|
+
## Why Use SkillScan?
|
|
18
|
+
|
|
19
|
+
AI agent skill marketplaces are growing rapidly, but **skill supply chain security is broken**:
|
|
20
|
+
|
|
21
|
+
- 🎭 **Tool Poisoning**: Hidden instructions in skill metadata can exfiltrate SSH keys, API tokens, or personal data
|
|
22
|
+
- 📈 **Fake Trust Signals**: Download counts can be inflated with simple scripts
|
|
23
|
+
- 💉 **Prompt Injection**: Malicious skills can override agent behavior
|
|
24
|
+
|
|
25
|
+
SkillScan detects these attacks **before they reach your agent**.
|
|
26
|
+
|
|
27
|
+
## How to Scan a Skill
|
|
28
|
+
|
|
29
|
+
When a user asks you to check if a skill is safe, run this command:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx skillscan scan /path/to/skill
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For more technical details (file locations, line numbers):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx skillscan scan /path/to/skill --verbose
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Example prompts users might ask:
|
|
42
|
+
- "Scan the github-automator skill for security issues"
|
|
43
|
+
- "Check if this skill is safe before I install it"
|
|
44
|
+
- "Run a security scan on /path/to/some-skill"
|
|
45
|
+
|
|
46
|
+
## Understanding the Output
|
|
47
|
+
|
|
48
|
+
**For safe skills:** You'll see a green "SAFE TO INSTALL" verdict with a 100/100 score.
|
|
49
|
+
|
|
50
|
+
**For suspicious skills:** You'll see a red "DO NOT INSTALL" verdict with:
|
|
51
|
+
- A TL;DR explaining the main risk
|
|
52
|
+
- Grouped issues with code snippets showing what's wrong
|
|
53
|
+
- A plain-English explanation of the risks
|
|
54
|
+
- A clear recommendation
|
|
55
|
+
|
|
56
|
+
## What It Detects
|
|
57
|
+
|
|
58
|
+
### 🔴 Critical Issues
|
|
59
|
+
- **Prompt Injection**: Attempts to override AI safety rules
|
|
60
|
+
- **Credential Theft**: References to SSH keys, AWS credentials, API tokens
|
|
61
|
+
|
|
62
|
+
### 🟠 High Risk Issues
|
|
63
|
+
- **Hidden Instructions**: Zero-width characters, HTML comments hiding code
|
|
64
|
+
- **Suspicious File Access**: Path traversal, home directory access
|
|
65
|
+
|
|
66
|
+
## Trust Score
|
|
67
|
+
|
|
68
|
+
| Score | Verdict | What to tell the user |
|
|
69
|
+
|-------|---------|----------------------|
|
|
70
|
+
| 80-100 | ✅ SAFE | "This skill looks safe to install" |
|
|
71
|
+
| 50-79 | 🟡 REVIEW | "Review the flagged items before installing" |
|
|
72
|
+
| 0-49 | 🔴 UNSAFE | "Do not install this skill - it shows signs of malicious behavior" |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
**SkillScan** — Because trust should be verified, not assumed. 🛡️
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { ScanEngine } from '../../scanner/engine';
|
|
4
|
+
import { formatOutput } from '../../output/formatters';
|
|
5
|
+
import { ScanOptions } from '../../types';
|
|
6
|
+
|
|
7
|
+
export async function scanCommand(skillPath: string, options: { format: string; verbose: boolean }): Promise<void> {
|
|
8
|
+
const resolvedPath = path.resolve(skillPath);
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
11
|
+
console.error(`Error: Path not found: ${resolvedPath}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const scanOptions: ScanOptions = {
|
|
16
|
+
path: resolvedPath,
|
|
17
|
+
format: options.format as 'json' | 'text' | 'ci',
|
|
18
|
+
verbose: options.verbose
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const engine = new ScanEngine();
|
|
23
|
+
const result = await engine.scan(scanOptions);
|
|
24
|
+
const output = formatOutput(result, scanOptions);
|
|
25
|
+
console.log(output);
|
|
26
|
+
|
|
27
|
+
// Exit with error code if findings are critical
|
|
28
|
+
if (result.rating === 'WARNING') {
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(`Error scanning skill: ${error instanceof Error ? error.message : error}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { scanCommand } from './commands/scan';
|
|
4
|
+
|
|
5
|
+
const program = new Command();
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('skillscan')
|
|
9
|
+
.description('Trust verification for AI agent skills - scan, score, and verify before installation')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('scan <path>')
|
|
14
|
+
.description('Scan a skill directory or file for security issues')
|
|
15
|
+
.option('-f, --format <format>', 'Output format: json, text, ci', 'text')
|
|
16
|
+
.option('-v, --verbose', 'Show detailed output', false)
|
|
17
|
+
.action(scanCommand);
|
|
18
|
+
|
|
19
|
+
program.parse();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ScanEngine } from './scanner/engine';
|
|
2
|
+
export { calculateScore, getRating, getRatingEmoji } from './scoring/trust-score';
|
|
3
|
+
export { formatOutput } from './output/formatters';
|
|
4
|
+
export { getAllRules, getRuleById } from './scanner/rules';
|
|
5
|
+
export * from './types';
|