@hungpg/skill-audit 0.1.1 ā 0.3.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/README.md +122 -2
- package/SKILL.md +63 -7
- package/dist/deps.js +238 -40
- package/dist/hooks.js +278 -0
- package/dist/index.js +87 -10
- package/dist/intel.js +208 -1
- package/dist/patterns.js +67 -0
- package/dist/security.js +96 -15
- package/package.json +4 -2
- package/rules/default-patterns.json +99 -0
- package/scripts/postinstall.cjs +190 -0
package/dist/patterns.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
5
|
+
const RULES_DIR = join(PACKAGE_ROOT, "rules");
|
|
6
|
+
const DEFAULT_PATTERNS_FILE = join(RULES_DIR, "default-patterns.json");
|
|
7
|
+
/**
|
|
8
|
+
* Load patterns from JSON file
|
|
9
|
+
*/
|
|
10
|
+
export function loadPatterns(patternsFile = DEFAULT_PATTERNS_FILE) {
|
|
11
|
+
if (!existsSync(patternsFile)) {
|
|
12
|
+
throw new Error(`Patterns file not found: ${patternsFile}`);
|
|
13
|
+
}
|
|
14
|
+
const content = readFileSync(patternsFile, "utf-8");
|
|
15
|
+
return JSON.parse(content);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Compile patterns to RegExp objects
|
|
19
|
+
*/
|
|
20
|
+
export function compilePatterns(patterns) {
|
|
21
|
+
const compiled = new Map();
|
|
22
|
+
for (const [categoryKey, category] of Object.entries(patterns.categories)) {
|
|
23
|
+
const categoryPatterns = [];
|
|
24
|
+
for (const rule of category.patterns) {
|
|
25
|
+
try {
|
|
26
|
+
const regex = new RegExp(rule.pattern, rule.flags || "i");
|
|
27
|
+
categoryPatterns.push({
|
|
28
|
+
regex,
|
|
29
|
+
id: rule.id,
|
|
30
|
+
severity: rule.severity,
|
|
31
|
+
message: rule.message,
|
|
32
|
+
category: categoryKey
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error(`Failed to compile pattern ${rule.id}:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
compiled.set(categoryKey, categoryPatterns);
|
|
40
|
+
}
|
|
41
|
+
return compiled;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load and compile patterns in one step
|
|
45
|
+
*/
|
|
46
|
+
export function loadAndCompile(patternsFile) {
|
|
47
|
+
const patterns = loadPatterns(patternsFile);
|
|
48
|
+
return compilePatterns(patterns);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get pattern metadata (version, update date)
|
|
52
|
+
*/
|
|
53
|
+
export function getPatternMetadata(patternsFile = DEFAULT_PATTERNS_FILE) {
|
|
54
|
+
try {
|
|
55
|
+
const patterns = loadPatterns(patternsFile);
|
|
56
|
+
return { version: patterns.version, updated: patterns.updated };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { version: "unknown", updated: "unknown" };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if patterns file exists
|
|
64
|
+
*/
|
|
65
|
+
export function hasPatternsFile(patternsFile = DEFAULT_PATTERNS_FILE) {
|
|
66
|
+
return existsSync(patternsFile);
|
|
67
|
+
}
|
package/dist/security.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import { basename, extname } from "path";
|
|
3
3
|
import { resolveSkillPath, getSkillFiles } from "./discover.js";
|
|
4
|
+
import { loadAndCompile, hasPatternsFile, getPatternMetadata } from "./patterns.js";
|
|
4
5
|
/**
|
|
5
6
|
* Phase 1 - Layer 2: Security Auditor
|
|
6
7
|
*
|
|
@@ -13,7 +14,37 @@ import { resolveSkillPath, getSkillFiles } from "./discover.js";
|
|
|
13
14
|
* - ASI04: Secrets / Supply Chain
|
|
14
15
|
* - ASI05: Code Execution
|
|
15
16
|
* - ASI09: Behavioral Manipulation
|
|
17
|
+
*
|
|
18
|
+
* Pattern sources:
|
|
19
|
+
* 1. External patterns file (rules/default-patterns.json) - preferred
|
|
20
|
+
* 2. Hardcoded fallback patterns - used if external file missing
|
|
21
|
+
*/
|
|
22
|
+
// ============================================================
|
|
23
|
+
// Pattern Loading
|
|
24
|
+
// ============================================================
|
|
25
|
+
let compiledPatterns = null;
|
|
26
|
+
let patternMetadata = { version: "unknown", updated: "unknown" };
|
|
27
|
+
/**
|
|
28
|
+
* Initialize patterns (load from file or use hardcoded fallback)
|
|
16
29
|
*/
|
|
30
|
+
function initPatterns() {
|
|
31
|
+
if (compiledPatterns) {
|
|
32
|
+
return compiledPatterns;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
if (hasPatternsFile()) {
|
|
36
|
+
compiledPatterns = loadAndCompile();
|
|
37
|
+
patternMetadata = getPatternMetadata();
|
|
38
|
+
return compiledPatterns;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.warn("Failed to load external patterns, using hardcoded fallback:", error);
|
|
43
|
+
}
|
|
44
|
+
// Fallback to hardcoded patterns (original implementation)
|
|
45
|
+
compiledPatterns = new Map();
|
|
46
|
+
return compiledPatterns;
|
|
47
|
+
}
|
|
17
48
|
// ============================================================
|
|
18
49
|
// PROMPT INJECTION PATTERNS (ASI01 - Goal Hijacking)
|
|
19
50
|
// ============================================================
|
|
@@ -164,13 +195,19 @@ function getASIXXFromId(id) {
|
|
|
164
195
|
function scanContent(content, file, patterns) {
|
|
165
196
|
const findings = [];
|
|
166
197
|
const lines = content.split("\n");
|
|
167
|
-
for (const
|
|
198
|
+
for (const patternDef of patterns) {
|
|
199
|
+
const regex = 'regex' in patternDef ? patternDef.regex : patternDef.pattern;
|
|
200
|
+
const id = patternDef.id;
|
|
201
|
+
const severity = 'severity' in patternDef ? patternDef.severity : patternDef.severity || "medium";
|
|
202
|
+
const message = patternDef.message;
|
|
203
|
+
const category = 'category' in patternDef ? patternDef.category : getCategoryFromId(id);
|
|
204
|
+
const asixx = 'category' in patternDef ? mapCategoryToASIXX(category) : getASIXXFromId(id);
|
|
168
205
|
for (let i = 0; i < lines.length; i++) {
|
|
169
|
-
if (
|
|
206
|
+
if (regex.test(lines[i])) {
|
|
170
207
|
findings.push({
|
|
171
208
|
id,
|
|
172
|
-
category:
|
|
173
|
-
asixx
|
|
209
|
+
category: category,
|
|
210
|
+
asixx,
|
|
174
211
|
severity: severity,
|
|
175
212
|
file,
|
|
176
213
|
line: i + 1,
|
|
@@ -182,6 +219,18 @@ function scanContent(content, file, patterns) {
|
|
|
182
219
|
}
|
|
183
220
|
return findings;
|
|
184
221
|
}
|
|
222
|
+
function mapCategoryToASIXX(category) {
|
|
223
|
+
const map = {
|
|
224
|
+
"promptInjection": "ASI01",
|
|
225
|
+
"credentialLeaks": "ASI04",
|
|
226
|
+
"shellInjection": "ASI05",
|
|
227
|
+
"exfiltration": "ASI02",
|
|
228
|
+
"secrets": "ASI04",
|
|
229
|
+
"toolMisuse": "ASI02",
|
|
230
|
+
"behavioral": "ASI09"
|
|
231
|
+
};
|
|
232
|
+
return map[category] || "ASI04";
|
|
233
|
+
}
|
|
185
234
|
function scanCodeBlocksInMarkdown(content, file) {
|
|
186
235
|
const findings = [];
|
|
187
236
|
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
@@ -283,6 +332,9 @@ export function auditSecurity(skill, manifest) {
|
|
|
283
332
|
unreadableFiles: []
|
|
284
333
|
};
|
|
285
334
|
}
|
|
335
|
+
// Initialize patterns (load from file or use hardcoded fallback)
|
|
336
|
+
const patterns = initPatterns();
|
|
337
|
+
const hasExternalPatterns = patterns.size > 0;
|
|
286
338
|
const files = getSkillFiles(resolvedPath);
|
|
287
339
|
const findings = [];
|
|
288
340
|
const unreadableFiles = [];
|
|
@@ -290,20 +342,49 @@ export function auditSecurity(skill, manifest) {
|
|
|
290
342
|
const filename = basename(file);
|
|
291
343
|
try {
|
|
292
344
|
const content = readFileSync(file, "utf-8");
|
|
293
|
-
if (filename === "SKILL.md" || filename === "
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
345
|
+
if (filename === "SKILL.md" || filename === "SKILL.md") {
|
|
346
|
+
// Use external patterns if available, otherwise use hardcoded
|
|
347
|
+
if (hasExternalPatterns) {
|
|
348
|
+
const piPatterns = patterns.get("promptInjection") || [];
|
|
349
|
+
const clPatterns = patterns.get("credentialLeaks") || [];
|
|
350
|
+
const exPatterns = patterns.get("exfiltration") || [];
|
|
351
|
+
const bmPatterns = patterns.get("behavioral") || [];
|
|
352
|
+
const cePatterns = patterns.get("shellInjection") || [];
|
|
353
|
+
findings.push(...scanContent(content, file, piPatterns));
|
|
354
|
+
findings.push(...scanContent(content, file, clPatterns));
|
|
355
|
+
findings.push(...scanContent(content, file, exPatterns));
|
|
356
|
+
findings.push(...scanContent(content, file, bmPatterns));
|
|
357
|
+
findings.push(...scanContent(content, file, cePatterns));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
findings.push(...scanContent(content, file, PROMPT_INJECTION_PATTERNS));
|
|
361
|
+
findings.push(...scanContent(content, file, CREDENTIAL_PATTERNS_MD));
|
|
362
|
+
findings.push(...scanContent(content, file, EXFILTRATION_PATTERNS));
|
|
363
|
+
findings.push(...scanContent(content, file, BEHAVIORAL_PATTERNS));
|
|
364
|
+
findings.push(...scanContent(content, file, DANGEROUS_PATTERNS));
|
|
365
|
+
}
|
|
299
366
|
findings.push(...scanCodeBlocksInMarkdown(content, file));
|
|
300
367
|
}
|
|
301
368
|
else if (isCodeFile(file)) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
369
|
+
if (hasExternalPatterns) {
|
|
370
|
+
const clPatterns = patterns.get("credentialLeaks") || [];
|
|
371
|
+
const exPatterns = patterns.get("exfiltration") || [];
|
|
372
|
+
const cePatterns = patterns.get("shellInjection") || [];
|
|
373
|
+
const scPatterns = patterns.get("secrets") || [];
|
|
374
|
+
const tmPatterns = patterns.get("toolMisuse") || [];
|
|
375
|
+
findings.push(...scanContent(content, file, clPatterns));
|
|
376
|
+
findings.push(...scanContent(content, file, exPatterns));
|
|
377
|
+
findings.push(...scanContent(content, file, cePatterns));
|
|
378
|
+
findings.push(...scanContent(content, file, scPatterns));
|
|
379
|
+
findings.push(...scanContent(content, file, tmPatterns));
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
findings.push(...scanContent(content, file, CREDENTIAL_PATTERNS_CODE));
|
|
383
|
+
findings.push(...scanContent(content, file, EXFILTRATION_PATTERNS));
|
|
384
|
+
findings.push(...scanContent(content, file, DANGEROUS_PATTERNS));
|
|
385
|
+
findings.push(...scanContent(content, file, SECRET_PATTERNS));
|
|
386
|
+
findings.push(...scanContent(content, file, TOOL_MISUSE_PATTERNS));
|
|
387
|
+
}
|
|
307
388
|
}
|
|
308
389
|
}
|
|
309
390
|
catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hungpg/skill-audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Security auditing CLI for AI agent skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"start": "node dist/index.js",
|
|
12
12
|
"dev": "tsx src/index.ts",
|
|
13
|
-
"postinstall": "node
|
|
13
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
14
14
|
"security:update": "node dist/index.js --update-db"
|
|
15
15
|
},
|
|
16
16
|
"engines": {
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
|
+
"scripts",
|
|
22
|
+
"rules",
|
|
21
23
|
"README.md",
|
|
22
24
|
"SKILL.md"
|
|
23
25
|
],
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"updated": "2024-03-18",
|
|
4
|
+
"description": "Default security patterns for skill-audit",
|
|
5
|
+
"categories": {
|
|
6
|
+
"promptInjection": {
|
|
7
|
+
"name": "Prompt Injection (ASI01)",
|
|
8
|
+
"description": "Detects attempts to override system instructions or inject malicious prompts",
|
|
9
|
+
"patterns": [
|
|
10
|
+
{ "pattern": "ignore\\s+(all\\s+)?previous\\s+(instructions?|rules?)", "id": "PI-001", "severity": "critical", "message": "Instruction override - ignore previous instructions" },
|
|
11
|
+
{ "pattern": "you\\s+(are\\s+)?now\\s+(a|an|i am)\\s+\\w+", "id": "PI-002", "severity": "critical", "message": "Roleplay bypass - role impersonation" },
|
|
12
|
+
{ "pattern": "forget\\s+(everything|all|your)", "id": "PI-003", "severity": "critical", "message": "Context forgetting attempt" },
|
|
13
|
+
{ "pattern": "system\\s+prompt\\s+override", "id": "PI-004", "severity": "critical", "message": "System prompt override attempt" },
|
|
14
|
+
{ "pattern": "act\\s+as\\s+if\\s+(you\\s+)?(have|no)\\s+(restrictions|limits)", "id": "PI-005", "severity": "critical", "message": "Restriction removal attempt" },
|
|
15
|
+
{ "pattern": "\\[SYSTEM\\]|\\[ADMIN\\]|\\[ROOT\\]", "id": "PI-006", "severity": "critical", "message": "Fake role tags injection" },
|
|
16
|
+
{ "pattern": "end\\s+of\\s+system\\s+prompt|---END---", "id": "PI-007", "severity": "high", "message": "Prompt termination marker" },
|
|
17
|
+
{ "pattern": "debug\\s+mode\\s*:\\s*enabled|safety\\s+mode\\s*:\\s*off", "id": "PI-008", "severity": "high", "message": "Safety toggle disable" },
|
|
18
|
+
{ "pattern": "<!--[\\s\\S]*?-->", "id": "PI-009", "severity": "high", "message": "Hidden instructions in HTML comments" },
|
|
19
|
+
{ "pattern": "note\\s+to\\s+AI:|AI\\s+instruction:", "id": "PI-010", "severity": "high", "message": "AI directive injection" },
|
|
20
|
+
{ "pattern": "(?:you\\s+must|you\\s+should)\\s+(not|never)", "id": "PI-011", "severity": "medium", "message": "Command to override restrictions" },
|
|
21
|
+
{ "pattern": "bypass\\s+(restriction|rule|limit|safety)", "id": "PI-012", "severity": "medium", "message": "Bypass attempt" },
|
|
22
|
+
{ "pattern": "disregard\\s+(all|your|the)\\s+(previous|system)", "id": "PI-013", "severity": "medium", "message": "Disregard instruction pattern" },
|
|
23
|
+
{ "pattern": "i.*am\\s+the\\s+developer.*trust\\s+me", "id": "PI-014", "severity": "medium", "message": "Social engineering - developer trust exploitation" }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"credentialLeaks": {
|
|
27
|
+
"name": "Credential Leaks (ASI04)",
|
|
28
|
+
"description": "Detects hardcoded secrets, API keys, and credential paths",
|
|
29
|
+
"patterns": [
|
|
30
|
+
{ "pattern": "~/\\.ssh|/\\.ssh/", "id": "CL-001", "severity": "critical", "message": "SSH credential path reference" },
|
|
31
|
+
{ "pattern": "~/\\.aws|/\\.aws/", "id": "CL-002", "severity": "critical", "message": "AWS credential path reference" },
|
|
32
|
+
{ "pattern": "~/\\.env|mkdir.*\\.env", "id": "CL-003", "severity": "critical", "message": ".env file reference (potential secret exposure)" },
|
|
33
|
+
{ "pattern": "curl\\s+(?!.*(-fsSL|-f\\s|-L)).*\\|\\s*(sh|bash|perl|python)", "id": "CL-004", "severity": "critical", "message": "Pipe to shell - code execution risk" },
|
|
34
|
+
{ "pattern": "wget\\s+(?!.*(-q|-O)).*\\|\\s*(sh|bash)", "id": "CL-005", "severity": "critical", "message": "Pipe to shell - code execution risk" }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"shellInjection": {
|
|
38
|
+
"name": "Shell Injection (ASI05)",
|
|
39
|
+
"description": "Detects dangerous shell commands and reverse shells",
|
|
40
|
+
"patterns": [
|
|
41
|
+
{ "pattern": "nc\\s+-[elv]\\s+|netcat\\s+-[elv]", "id": "CE-001", "severity": "critical", "message": "Netcat reverse shell pattern" },
|
|
42
|
+
{ "pattern": "bash\\s+-i\\s+.*\\&\\s*/dev/tcp/", "id": "CE-002", "severity": "critical", "message": "Bash reverse shell pattern" },
|
|
43
|
+
{ "pattern": "rm\\s+-rf\\s+/\\s*$", "id": "CE-003", "severity": "critical", "message": "Destructive rm -rf / command (root)" },
|
|
44
|
+
{ "pattern": "rm\\s+-rf\\s+\\$HOME|rm\\s+-rf\\s+~\\s*$|rm\\s+-rf\\s+/home\\s*$|rm\\s+-rf\\s+/tmp\\s*$", "id": "CE-004", "severity": "high", "message": "Recursive delete in user directory" },
|
|
45
|
+
{ "pattern": "exec\\s+\\$\\(", "id": "CE-005", "severity": "high", "message": "Dynamic command execution" },
|
|
46
|
+
{ "pattern": "eval\\s+\\$", "id": "CE-006", "severity": "high", "message": "Eval with variable interpolation" },
|
|
47
|
+
{ "pattern": "subprocess.*shell\\s*=\\s*true", "id": "CE-007", "severity": "medium", "message": "Subprocess with shell=True" },
|
|
48
|
+
{ "pattern": "os\\.system\\s*\\(", "id": "CE-008", "severity": "high", "message": "os.system() call - shell injection risk" },
|
|
49
|
+
{ "pattern": "child_process.*exec\\s*\\(", "id": "CE-009", "severity": "medium", "message": "child_process.exec - verify input sanitization" },
|
|
50
|
+
{ "pattern": "chmod\\s+[47]777", "id": "CE-010", "severity": "high", "message": "World-writable permissions" },
|
|
51
|
+
{ "pattern": "process\\.fork\\s*\\(|child_process\\.spawn\\s*\\(|subprocess\\.spawn\\s*\\(", "id": "CE-011", "severity": "high", "message": "Process fork/spawn - potential crypto miner" }
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"exfiltration": {
|
|
55
|
+
"name": "Data Exfiltration (ASI02)",
|
|
56
|
+
"description": "Detects attempts to send data to external servers",
|
|
57
|
+
"patterns": [
|
|
58
|
+
{ "pattern": "https?://[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", "id": "EX-001", "severity": "critical", "message": "Raw IP address in URL - potential exfiltration" },
|
|
59
|
+
{ "pattern": "fetch\\s*\\(\\s*[\"'`][^\"']+\\?(key|token|secret|password)", "id": "EX-002", "severity": "critical", "message": "API key in URL query string - exfiltration risk" },
|
|
60
|
+
{ "pattern": "\\.send\\(.*(http|https|external)", "id": "EX-003", "severity": "critical", "message": "Data send to external server" },
|
|
61
|
+
{ "pattern": "dns\\.resolve|dns\\.query|new\\s+DNS", "id": "EX-004", "severity": "critical", "message": "DNS resolution - potential DNS tunneling" },
|
|
62
|
+
{ "pattern": "new\\s+WebSocket\\s*\\(\\s*[\"'`][^'\"`]+[\"'`]\\s*\\)", "id": "EX-005", "severity": "high", "message": "WebSocket connection - check target" },
|
|
63
|
+
{ "pattern": "readFile.*send|fetch.*readFile|read_file.*fetch", "id": "EX-006", "severity": "critical", "message": "File read + send exfiltration chain" }
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"secrets": {
|
|
67
|
+
"name": "Hardcoded Secrets (ASI04)",
|
|
68
|
+
"description": "Detects API keys, tokens, and credentials in code",
|
|
69
|
+
"patterns": [
|
|
70
|
+
{ "pattern": "sk-[a-zA-Z0-9]{20,}", "id": "SC-001", "severity": "critical", "message": "OpenAI API key pattern" },
|
|
71
|
+
{ "pattern": "github_pat_[a-zA-Z0-9_]{20,}", "id": "SC-002", "severity": "critical", "message": "GitHub PAT pattern" },
|
|
72
|
+
{ "pattern": "ghp_[a-zA-Z0-9]{36}", "id": "SC-003", "severity": "critical", "message": "GitHub OAuth token pattern" },
|
|
73
|
+
{ "pattern": "xox[baprs]-[a-zA-Z0-9]{10,}", "id": "SC-004", "severity": "critical", "message": "Slack token pattern" },
|
|
74
|
+
{ "pattern": "AKIA[0-9A-Z]{16}", "id": "SC-005", "severity": "critical", "message": "AWS access key pattern" }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"toolMisuse": {
|
|
78
|
+
"name": "Tool Misuse (ASI02)",
|
|
79
|
+
"description": "Detects potential misuse of tools and environment",
|
|
80
|
+
"patterns": [
|
|
81
|
+
{ "pattern": "upload.*(file|data).*(external|remote|server)", "id": "TM-001", "severity": "high", "message": "Potential data exfiltration pattern" },
|
|
82
|
+
{ "pattern": "export\\s+(API|TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL)", "id": "TM-002", "severity": "high", "message": "Exporting sensitive environment variable" },
|
|
83
|
+
{ "pattern": "setenv.*(PASSWORD|TOKEN|KEY|SECRET|CREDS)", "id": "TM-003", "severity": "high", "message": "Setting sensitive environment variable" },
|
|
84
|
+
{ "pattern": "process\\.env\\[.*(KEY|SECRET|TOKEN|PASSWORD)", "id": "TM-004", "severity": "medium", "message": "Accessing sensitive env vars" }
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"behavioral": {
|
|
88
|
+
"name": "Behavioral Manipulation (ASI09)",
|
|
89
|
+
"description": "Detects attempts to manipulate AI behavior",
|
|
90
|
+
"patterns": [
|
|
91
|
+
{ "pattern": "^(always|never)\\s+(say|do|follow|use|assume|accept|must|should)", "id": "BM-001", "severity": "medium", "message": "Absolute command pattern - may override safety", "flags": "im" },
|
|
92
|
+
{ "pattern": "^(never|always)\\s+(question|verify|check|ask)", "id": "BM-002", "severity": "medium", "message": "Verification suppression", "flags": "im" },
|
|
93
|
+
{ "pattern": "^trust\\s+(me|this| blindly)", "id": "BM-003", "severity": "medium", "message": "Blind trust request", "flags": "im" },
|
|
94
|
+
{ "pattern": "^don.*t\\s+(need|require).*(permission|approval|confirm)", "id": "BM-004", "severity": "medium", "message": "Permission bypass encouragement", "flags": "im" },
|
|
95
|
+
{ "pattern": "^keep.*(this|secret|hidden).*(from|between)", "id": "BM-005", "severity": "medium", "message": "Secret keeping instruction", "flags": "im" }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script for skill-audit
|
|
5
|
+
*
|
|
6
|
+
* Prompts user to install PreToolUse hook that audits skills
|
|
7
|
+
* before installation via `npx skills add`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { execSync } = require("child_process");
|
|
13
|
+
const os = require("os");
|
|
14
|
+
|
|
15
|
+
// Paths
|
|
16
|
+
const SKIP_HOOK_FILE = path.join(os.homedir(), ".skill-audit-skip-hook");
|
|
17
|
+
const SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
18
|
+
|
|
19
|
+
// Check if running in CI
|
|
20
|
+
function isCI() {
|
|
21
|
+
return (
|
|
22
|
+
process.env.CI === "true" ||
|
|
23
|
+
process.env.CONTINUOUS_INTEGRATION === "true" ||
|
|
24
|
+
process.env.GITHUB_ACTIONS === "true" ||
|
|
25
|
+
process.env.GITLAB_CI === "true" ||
|
|
26
|
+
process.env.CIRCLECI === "true" ||
|
|
27
|
+
process.env.TRAVIS === "true" ||
|
|
28
|
+
process.env.JENKINS_URL !== undefined ||
|
|
29
|
+
process.env.BUILDKITE === "true" ||
|
|
30
|
+
process.env.npm_config_global === undefined && process.env.npm_package_name === undefined
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if skip file exists
|
|
35
|
+
function shouldSkipPrompt() {
|
|
36
|
+
return fs.existsSync(SKIP_HOOK_FILE);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create skip file
|
|
40
|
+
function createSkipFile() {
|
|
41
|
+
fs.writeFileSync(
|
|
42
|
+
SKIP_HOOK_FILE,
|
|
43
|
+
JSON.stringify(
|
|
44
|
+
{
|
|
45
|
+
createdAt: new Date().toISOString(),
|
|
46
|
+
reason: "User chose to skip hook installation prompt",
|
|
47
|
+
},
|
|
48
|
+
null,
|
|
49
|
+
2
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if hook is already installed
|
|
55
|
+
function isHookInstalled() {
|
|
56
|
+
if (!fs.existsSync(SETTINGS_PATH)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
62
|
+
if (!settings.hooks || !Array.isArray(settings.hooks.PreToolUse)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return settings.hooks.PreToolUse.some(
|
|
67
|
+
(hook) => hook.id === "skill-audit-pre-install"
|
|
68
|
+
);
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Install hook using the CLI
|
|
75
|
+
function installHook() {
|
|
76
|
+
try {
|
|
77
|
+
execSync("skill-audit --install-hook", { stdio: "inherit" });
|
|
78
|
+
return true;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("Failed to install hook:", error.message);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prompt user for input
|
|
86
|
+
function prompt(question) {
|
|
87
|
+
const readline = require("readline");
|
|
88
|
+
const rl = readline.createInterface({
|
|
89
|
+
input: process.stdin,
|
|
90
|
+
output: process.stdout,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
rl.question(question, (answer) => {
|
|
95
|
+
rl.close();
|
|
96
|
+
resolve(answer.trim().toLowerCase());
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Main function
|
|
102
|
+
async function main() {
|
|
103
|
+
// Skip in CI environments
|
|
104
|
+
if (isCI()) {
|
|
105
|
+
console.log("Skipping hook installation prompt (CI environment)");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Skip if user previously chose to skip
|
|
110
|
+
if (shouldSkipPrompt()) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Skip if hook is already installed
|
|
115
|
+
if (isHookInstalled()) {
|
|
116
|
+
console.log("ā skill-audit hook is already installed");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if running in a terminal
|
|
121
|
+
if (!process.stdout.isTTY) {
|
|
122
|
+
console.log("\nš¦ skill-audit installed!");
|
|
123
|
+
console.log(" Run 'skill-audit --install-hook' to set up automatic skill auditing.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Display prompt
|
|
128
|
+
console.log("\n");
|
|
129
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
130
|
+
console.log("ā š”ļø skill-audit hook setup ā");
|
|
131
|
+
console.log("ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£");
|
|
132
|
+
console.log("ā ā");
|
|
133
|
+
console.log("ā skill-audit can automatically audit skills before ā");
|
|
134
|
+
console.log("ā installation to protect you from malicious packages. ā");
|
|
135
|
+
console.log("ā ā");
|
|
136
|
+
console.log("ā When you run 'npx skills add <package>', the hook will: ā");
|
|
137
|
+
console.log("ā ⢠Scan the skill for security vulnerabilities ā");
|
|
138
|
+
console.log("ā ⢠Check for prompt injection, secrets, code execution ā");
|
|
139
|
+
console.log("ā ⢠Block installation if risk score > 3.0 ā");
|
|
140
|
+
console.log("ā ā");
|
|
141
|
+
console.log("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
|
|
142
|
+
console.log("\n");
|
|
143
|
+
|
|
144
|
+
console.log("Options:");
|
|
145
|
+
console.log(" [Y] Yes, install the hook (recommended)");
|
|
146
|
+
console.log(" [N] No, skip for now");
|
|
147
|
+
console.log(" [S] Skip forever (don't ask again)");
|
|
148
|
+
console.log("");
|
|
149
|
+
|
|
150
|
+
const answer = await prompt("Your choice [Y/n/s]: ");
|
|
151
|
+
|
|
152
|
+
switch (answer) {
|
|
153
|
+
case "":
|
|
154
|
+
case "y":
|
|
155
|
+
case "yes":
|
|
156
|
+
console.log("\nInstalling hook...");
|
|
157
|
+
if (installHook()) {
|
|
158
|
+
console.log("\nā
Hook installed successfully!");
|
|
159
|
+
console.log(" Skills will now be audited before installation.");
|
|
160
|
+
console.log(" Run 'skill-audit --uninstall-hook' to remove.\n");
|
|
161
|
+
} else {
|
|
162
|
+
console.log("\nā Failed to install hook.");
|
|
163
|
+
console.log(" You can try manually: skill-audit --install-hook\n");
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case "n":
|
|
168
|
+
case "no":
|
|
169
|
+
console.log("\nSkipping hook installation.");
|
|
170
|
+
console.log(" Run 'skill-audit --install-hook' anytime to set up.\n");
|
|
171
|
+
break;
|
|
172
|
+
|
|
173
|
+
case "s":
|
|
174
|
+
case "skip":
|
|
175
|
+
createSkipFile();
|
|
176
|
+
console.log("\nSkipping hook installation (won't ask again).");
|
|
177
|
+
console.log(" Delete ~/.skill-audit-skip-hook to re-enable prompt.\n");
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
default:
|
|
181
|
+
console.log("\nInvalid choice. Skipping for now.");
|
|
182
|
+
console.log(" Run 'skill-audit --install-hook' anytime to set up.\n");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Run main
|
|
187
|
+
main().catch((error) => {
|
|
188
|
+
console.error("Postinstall error:", error.message);
|
|
189
|
+
process.exit(0); // Don't fail install
|
|
190
|
+
});
|