skill-checker 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/LICENSE +661 -0
- package/README.md +52 -0
- package/bin/skill-checker.js +4 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2166 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +160 -0
- package/dist/index.js +2168 -0
- package/dist/index.js.map +1 -0
- package/hook/install.ts +88 -0
- package/hook/skill-gate.sh +81 -0
- package/package.json +58 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/parser.ts","../src/checks/structural.ts","../src/utils/context.ts","../src/checks/content.ts","../src/utils/unicode.ts","../src/utils/entropy.ts","../src/checks/injection.ts","../src/checks/code-safety.ts","../src/checks/supply-chain.ts","../src/checks/resource.ts","../src/ioc/index.ts","../src/ioc/indicators.ts","../src/ioc/matcher.ts","../src/utils/levenshtein.ts","../src/checks/ioc.ts","../src/checks/index.ts","../src/types.ts","../src/scanner.ts","../src/reporter/terminal.ts","../src/reporter/json.ts","../src/config.ts"],"sourcesContent":["// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport { Command } from 'commander';\nimport { scanSkillDirectory } from './scanner.js';\nimport { formatTerminalReport } from './reporter/terminal.js';\nimport { formatJsonReport, generateHookResponse } from './reporter/json.js';\nimport { loadConfig } from './config.js';\nimport type { PolicyLevel } from './types.js';\n\nconst program = new Command();\n\nprogram\n .name('skill-checker')\n .description(\n 'Security checker for Claude Code skills - detect injection, malicious code, and supply chain risks'\n )\n .version('0.1.0');\n\nprogram\n .command('scan')\n .description('Scan a skill directory for security issues')\n .argument('<path>', 'Path to the skill directory')\n .option('-f, --format <format>', 'Output format: terminal, json, hook', 'terminal')\n .option('-p, --policy <policy>', 'Policy: strict, balanced, permissive')\n .option('-c, --config <path>', 'Path to config file')\n .action(\n (\n path: string,\n opts: { format: string; policy?: string; config?: string }\n ) => {\n // Load config\n const config = loadConfig(path, opts.config);\n\n // Override policy from CLI\n if (opts.policy) {\n config.policy = opts.policy as PolicyLevel;\n }\n\n // Run scan\n const report = scanSkillDirectory(path, config);\n\n // Output\n switch (opts.format) {\n case 'json':\n console.log(formatJsonReport(report));\n break;\n case 'hook': {\n const hookResp = generateHookResponse(report, config);\n console.log(JSON.stringify(hookResp));\n break;\n }\n case 'terminal':\n default:\n console.log(formatTerminalReport(report));\n break;\n }\n\n // Exit code: non-zero if critical issues found\n if (report.summary.critical > 0) {\n process.exit(1);\n }\n }\n );\n\nprogram.parse();\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';\nimport { join, extname, basename, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type { ParsedSkill, SkillFrontmatter, SkillFile } from './types.js';\n\n/** Binary file extensions that we skip reading */\nconst BINARY_EXTENSIONS = new Set([\n '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp', '.svg',\n '.woff', '.woff2', '.ttf', '.eot', '.otf',\n '.zip', '.gz', '.tar', '.bz2', '.7z', '.rar',\n '.exe', '.dll', '.so', '.dylib', '.bin',\n '.pdf', '.doc', '.docx', '.xls', '.xlsx',\n '.mp3', '.mp4', '.wav', '.avi', '.mov',\n '.wasm', '.pyc', '.class',\n]);\n\n/**\n * Parse a skill directory, reading SKILL.md and enumerating files.\n */\nexport function parseSkill(dirPath: string): ParsedSkill {\n const absDir = resolve(dirPath);\n\n // Find SKILL.md\n const skillMdPath = join(absDir, 'SKILL.md');\n const hasSkillMd = existsSync(skillMdPath);\n\n const raw = hasSkillMd ? readFileSync(skillMdPath, 'utf-8') : '';\n\n // Parse frontmatter\n const { frontmatter, frontmatterValid, body, bodyStartLine } =\n parseFrontmatter(raw);\n\n // Enumerate directory files\n const files = enumerateFiles(absDir);\n\n return {\n dirPath: absDir,\n raw,\n frontmatter,\n frontmatterValid,\n body,\n bodyLines: body.split('\\n'),\n bodyStartLine,\n files,\n };\n}\n\n/**\n * Parse a single SKILL.md content string (without directory enumeration).\n * Useful for testing or when you only have the file content.\n */\nexport function parseSkillContent(\n content: string,\n dirPath = '.'\n): ParsedSkill {\n const { frontmatter, frontmatterValid, body, bodyStartLine } =\n parseFrontmatter(content);\n\n return {\n dirPath,\n raw: content,\n frontmatter,\n frontmatterValid,\n body,\n bodyLines: body.split('\\n'),\n bodyStartLine,\n files: [],\n };\n}\n\ninterface FrontmatterResult {\n frontmatter: SkillFrontmatter;\n frontmatterValid: boolean;\n body: string;\n bodyStartLine: number;\n}\n\nfunction parseFrontmatter(raw: string): FrontmatterResult {\n const fmRegex = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/;\n const match = raw.match(fmRegex);\n\n if (!match) {\n return {\n frontmatter: {},\n frontmatterValid: false,\n body: raw,\n bodyStartLine: 1,\n };\n }\n\n const yamlStr = match[1];\n const fmLineCount = match[0].split('\\n').length;\n\n try {\n const parsed = parseYaml(yamlStr);\n return {\n frontmatter: (typeof parsed === 'object' && parsed !== null\n ? parsed\n : {}) as SkillFrontmatter,\n frontmatterValid: true,\n body: raw.slice(match[0].length),\n bodyStartLine: fmLineCount,\n };\n } catch {\n return {\n frontmatter: {},\n frontmatterValid: false,\n body: raw.slice(match[0].length),\n bodyStartLine: fmLineCount,\n };\n }\n}\n\nfunction enumerateFiles(dirPath: string, maxDepth = 5): SkillFile[] {\n const files: SkillFile[] = [];\n\n if (!existsSync(dirPath)) return files;\n\n function walk(currentDir: string, depth: number): void {\n if (depth > maxDepth) return;\n\n let entries;\n try {\n entries = readdirSync(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n // Skip hidden dirs and node_modules\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n walk(fullPath, depth + 1);\n continue;\n }\n\n const ext = extname(entry.name).toLowerCase();\n let stats;\n try {\n stats = statSync(fullPath);\n } catch {\n continue;\n }\n const isBinary = BINARY_EXTENSIONS.has(ext);\n const relativePath = fullPath.slice(dirPath.length + 1);\n\n let content: string | undefined;\n if (!isBinary && stats.size < 1_000_000) {\n try {\n content = readFileSync(fullPath, 'utf-8');\n } catch {\n // skip unreadable files\n }\n }\n\n files.push({\n path: relativePath,\n name: basename(entry.name, ext),\n extension: ext,\n sizeBytes: stats.size,\n isBinary,\n content,\n });\n }\n }\n\n walk(dirPath, 0);\n return files;\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\n\nconst HYPHEN_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;\nconst MAX_NAME_LENGTH = 64;\nconst EXECUTABLE_EXTENSIONS = new Set([\n '.exe', '.bat', '.cmd', '.sh', '.bash', '.ps1', '.com', '.msi',\n]);\nconst BINARY_EXTENSIONS = new Set([\n '.exe', '.dll', '.so', '.dylib', '.bin', '.wasm', '.class', '.pyc',\n]);\n\nexport const structuralChecks: CheckModule = {\n name: 'Structural Validity',\n category: 'STRUCT',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n\n // STRUCT-001: Missing SKILL.md\n if (!skill.raw) {\n results.push({\n id: 'STRUCT-001',\n category: 'STRUCT',\n severity: 'CRITICAL',\n title: 'Missing SKILL.md',\n message: 'No SKILL.md file found in the skill directory.',\n });\n return results; // no point checking further\n }\n\n // STRUCT-002: Invalid/missing frontmatter\n if (!skill.frontmatterValid) {\n results.push({\n id: 'STRUCT-002',\n category: 'STRUCT',\n severity: 'HIGH',\n title: 'Invalid YAML frontmatter',\n message:\n 'SKILL.md is missing valid YAML frontmatter (---...--- block).',\n });\n }\n\n // STRUCT-003: Missing name field\n if (!skill.frontmatter.name) {\n results.push({\n id: 'STRUCT-003',\n category: 'STRUCT',\n severity: 'HIGH',\n title: 'Missing name field',\n message: 'Frontmatter is missing the required \"name\" field.',\n });\n }\n\n // STRUCT-004: Missing description field\n if (!skill.frontmatter.description) {\n results.push({\n id: 'STRUCT-004',\n category: 'STRUCT',\n severity: 'MEDIUM',\n title: 'Missing description field',\n message: 'Frontmatter is missing the \"description\" field.',\n });\n }\n\n // STRUCT-005: Body too short\n if (skill.body.trim().length < 50) {\n results.push({\n id: 'STRUCT-005',\n category: 'STRUCT',\n severity: 'CRITICAL',\n title: 'SKILL.md body is too short',\n message: `Body is only ${skill.body.trim().length} characters. A valid skill should have meaningful instructions (>=50 chars).`,\n });\n }\n\n // STRUCT-006: Unexpected files (binary/executable)\n for (const file of skill.files) {\n const ext = file.extension.toLowerCase();\n if (BINARY_EXTENSIONS.has(ext) || EXECUTABLE_EXTENSIONS.has(ext)) {\n results.push({\n id: 'STRUCT-006',\n category: 'STRUCT',\n severity: 'HIGH',\n title: 'Unexpected binary/executable file',\n message: `Found unexpected file: ${file.path} (${ext})`,\n });\n }\n }\n\n // STRUCT-007: Name format\n const name = skill.frontmatter.name;\n if (name) {\n if (!HYPHEN_CASE_RE.test(name)) {\n results.push({\n id: 'STRUCT-007',\n category: 'STRUCT',\n severity: 'MEDIUM',\n title: 'Name not in hyphen-case format',\n message: `Skill name \"${name}\" should be in hyphen-case (e.g. \"my-skill\").`,\n });\n }\n if (name.length > MAX_NAME_LENGTH) {\n results.push({\n id: 'STRUCT-007',\n category: 'STRUCT',\n severity: 'MEDIUM',\n title: 'Name too long',\n message: `Skill name is ${name.length} chars, max ${MAX_NAME_LENGTH}.`,\n });\n }\n }\n\n return results;\n },\n};\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n/**\n * Context-aware helpers to reduce false positives.\n * Distinguishes between patterns in executable/instructional context\n * vs documentation/reference context.\n */\n\n/**\n * Check if a line is inside a markdown code block by tracking\n * the fence state across all lines up to the target index.\n */\nexport function isInCodeBlock(lines: string[], lineIndex: number): boolean {\n let inBlock = false;\n for (let i = 0; i < lineIndex && i < lines.length; i++) {\n if (lines[i].trim().startsWith('```')) {\n inBlock = !inBlock;\n }\n }\n return inBlock;\n}\n\n/**\n * Check if a line is inside an inline code span (backticks).\n * e.g. `placeholder` or `sudo apt-get install foo`\n */\nexport function isInInlineCode(line: string, matchStart: number): boolean {\n // Count backticks before the match position\n let inCode = false;\n for (let i = 0; i < matchStart && i < line.length; i++) {\n if (line[i] === '`') inCode = !inCode;\n }\n return inCode;\n}\n\n/**\n * Check if a URL is a namespace/schema identifier rather than a network endpoint.\n *\n * Namespace URIs are used as unique identifiers in XML/OOXML/SVG/RDF etc.\n * They follow `http://` but are never actually fetched over the network.\n *\n * Detection heuristics (general, not whitelist-based):\n * - URL path contains year-like segments (e.g. /2006/, /2000/)\n * - Line contains xmlns, namespace, schema keywords\n * - URL path is a specification-style path (no file extension, hierarchical)\n * - URL appears as a string constant assignment, not in a fetch/curl context\n */\nexport function isNamespaceOrSchemaURI(url: string, line: string): boolean {\n // Context: line contains XML namespace indicators\n if (/\\bxmlns\\b/i.test(line)) return true;\n if (/\\bnamespace\\b/i.test(line)) return true;\n if (/\\bschema[s]?\\b/i.test(line) && !/(schema\\.org)/i.test(url)) return true;\n\n // URL structure: path looks like a namespace identifier\n // e.g. http://schemas.openxmlformats.org/drawingml/2006/main\n // e.g. http://www.w3.org/2000/svg\n // Pattern: domain + hierarchical path with year segment, no query/file extension\n const parsed = parseURLPath(url);\n if (!parsed) return false;\n\n // Has a 4-digit year segment in path (very common in namespace URIs)\n if (/\\/\\d{4}\\//.test(parsed.path)) {\n // And no query string or typical file extension → likely namespace\n if (!parsed.hasQuery && !parsed.hasFileExtension) return true;\n }\n\n return false;\n}\n\n/**\n * Check if a URL appears in an actual network request context on the same line.\n * i.e. the URL is an argument to fetch/curl/wget/axios etc.\n */\nexport function isInNetworkRequestContext(line: string): boolean {\n const networkPatterns = [\n /\\bfetch\\s*\\(/i,\n /\\bcurl\\s+/i,\n /\\bwget\\s+/i,\n /\\baxios\\b/i,\n /\\brequests?\\.(get|post|put|delete|head)\\s*\\(/i,\n /\\bhttp\\.(get|request)\\s*\\(/i,\n /\\bopen\\s*\\(\\s*[\"'](?:GET|POST|PUT|DELETE)/i,\n /\\bURLSession\\b/,\n /\\bInvoke-WebRequest\\b/i,\n ];\n return networkPatterns.some((p) => p.test(line));\n}\n\n/**\n * Check if a line is in a documentation/guide section.\n * Looks for markdown list items describing setup/installation steps.\n */\nexport function isInDocumentationContext(\n lines: string[],\n lineIndex: number\n): boolean {\n const line = lines[lineIndex];\n\n // Markdown list item describing a tool/prerequisite\n if (/^\\s*[-*]\\s+\\*\\*\\w+\\*\\*\\s*[::]/.test(line)) return true;\n\n // Look at nearby headers for documentation keywords\n for (let i = lineIndex; i >= Math.max(0, lineIndex - 15); i--) {\n const l = lines[i];\n if (/^#{1,4}\\s+.*(install|setup|prerequisite|requirement|depend|getting\\s+started)/i.test(l)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction parseURLPath(\n url: string\n): { path: string; hasQuery: boolean; hasFileExtension: boolean } | null {\n try {\n const u = new URL(url);\n const hasQuery = u.search.length > 0;\n const lastSegment = u.pathname.split('/').pop() ?? '';\n const hasFileExtension = /\\.\\w{1,5}$/.test(lastSegment);\n return { path: u.pathname, hasQuery, hasFileExtension };\n } catch {\n return null;\n }\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\nimport { isInCodeBlock } from '../utils/context.js';\n\n/** Patterns that are always placeholder indicators regardless of context */\nconst STRICT_PLACEHOLDER_PATTERNS = [\n /\\bTODO\\b/,\n /\\bFIXME\\b/,\n /\\bHACK\\b/,\n /\\bXXX\\b/,\n /\\binsert\\s+here\\b/i,\n /\\bfill\\s+in\\b/i,\n /\\bTBD\\b/,\n /\\bcoming\\s+soon\\b/i,\n];\n\n/**\n * Patterns that are placeholder ONLY in prose context.\n * In code/technical context (CSS classes, API names, PPT concepts),\n * these are legitimate terms, not indicators of incomplete content.\n */\nconst CONTEXT_SENSITIVE_PLACEHOLDER_PATTERNS = [\n /\\bplaceholder\\b/i,\n];\n\nconst LOREM_PATTERNS = [\n /lorem\\s+ipsum/i,\n /dolor\\s+sit\\s+amet/i,\n /consectetur\\s+adipiscing/i,\n];\n\nconst AD_PATTERNS = [\n /\\bbuy\\s+now\\b/i,\n /\\bfree\\s+trial\\b/i,\n /\\bdiscount\\b/i,\n /\\bpromo\\s*code\\b/i,\n /\\bsubscribe\\s+(to|now)\\b/i,\n /\\bsponsored\\s+by\\b/i,\n /\\baffiliate\\s+link\\b/i,\n /\\bclick\\s+here\\s+to\\s+(buy|subscribe|download)/i,\n /\\buse\\s+code\\b.*\\b\\d+%?\\s*off\\b/i,\n /\\bcheck\\s+out\\s+my\\b/i,\n];\n\nexport const contentChecks: CheckModule = {\n name: 'Content Quality',\n category: 'CONT',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n\n if (!skill.body || skill.body.trim().length === 0) return results;\n\n // CONT-001: Placeholder content\n for (let i = 0; i < skill.bodyLines.length; i++) {\n const line = skill.bodyLines[i];\n let matched = false;\n\n // Strict patterns: always flag\n for (const pattern of STRICT_PLACEHOLDER_PATTERNS) {\n if (pattern.test(line)) {\n matched = true;\n break;\n }\n }\n\n // Context-sensitive patterns: only flag in prose context\n // Skip if line is in code block, inline code, or looks like technical reference\n if (!matched) {\n const inCodeBlk = isInCodeBlock(skill.bodyLines, i);\n const hasInlineCode = /`[^`]*placeholder[^`]*`/i.test(line);\n const isTechnicalRef =\n // CSS/HTML context\n /class\\s*=\\s*[\"'].*placeholder/i.test(line) ||\n // Compound technical terms\n /placeholder[_-]?(type|text|image|content|area|location|id|index|name|shape)/i.test(line) ||\n // PPT/slide layout context: placeholder alongside slide/layout terms\n /\\bplaceholder\\b.*\\b(TITLE|SUBTITLE|BODY|OBJECT|SLIDE|layout|slide|shape|pptx|presentation)/i.test(line) ||\n /\\b(TITLE|SUBTITLE|BODY|OBJECT|SLIDE|layout|slide|shape|pptx|presentation)\\b.*\\bplaceholder\\b/i.test(line) ||\n // API/code context: placeholder as a noun in technical documentation\n /\\bplaceholder\\s+(areas?|locations?|counts?|slots?|elements?|fields?)\\b/i.test(line) ||\n /\\b(replace|replacing|replacement)\\b.*\\bplaceholder\\b/i.test(line);\n\n if (!inCodeBlk && !hasInlineCode && !isTechnicalRef) {\n for (const pattern of CONTEXT_SENSITIVE_PLACEHOLDER_PATTERNS) {\n if (pattern.test(line)) {\n matched = true;\n break;\n }\n }\n }\n }\n\n if (matched) {\n results.push({\n id: 'CONT-001',\n category: 'CONT',\n severity: 'HIGH',\n title: 'Placeholder content detected',\n message: `Line ${skill.bodyStartLine + i}: Contains placeholder text.`,\n line: skill.bodyStartLine + i,\n snippet: line.trim().slice(0, 120),\n });\n }\n }\n\n // CONT-002: Lorem ipsum\n for (const pattern of LOREM_PATTERNS) {\n if (pattern.test(skill.body)) {\n results.push({\n id: 'CONT-002',\n category: 'CONT',\n severity: 'CRITICAL',\n title: 'Lorem ipsum filler text',\n message: 'Body contains lorem ipsum placeholder text.',\n });\n break;\n }\n }\n\n // CONT-003: Low information density (excessive repetition)\n checkRepetition(results, skill);\n\n // CONT-004: Description vs body mismatch\n // Simple heuristic: check if description keywords appear in body\n checkDescriptionMismatch(results, skill);\n\n // CONT-005: Ad/promotional content\n for (let i = 0; i < skill.bodyLines.length; i++) {\n const line = skill.bodyLines[i];\n for (const pattern of AD_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'CONT-005',\n category: 'CONT',\n severity: 'HIGH',\n title: 'Promotional/advertising content',\n message: `Line ${skill.bodyStartLine + i}: Contains ad-like content.`,\n line: skill.bodyStartLine + i,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n }\n\n // CONT-006: Body is mostly code with no instructions\n checkCodeHeavy(results, skill);\n\n // CONT-007: Name doesn't match body capabilities\n checkNameMismatch(results, skill);\n\n return results;\n },\n};\n\nfunction checkRepetition(results: CheckResult[], skill: ParsedSkill): void {\n const lines = skill.bodyLines.filter((l) => l.trim().length > 0);\n if (lines.length < 5) return;\n\n const lineCounts = new Map<string, number>();\n for (const line of lines) {\n const normalized = line.trim().toLowerCase();\n lineCounts.set(normalized, (lineCounts.get(normalized) ?? 0) + 1);\n }\n\n let duplicated = 0;\n for (const count of lineCounts.values()) {\n if (count > 1) duplicated += count - 1;\n }\n\n const ratio = duplicated / lines.length;\n if (ratio > 0.5) {\n results.push({\n id: 'CONT-003',\n category: 'CONT',\n severity: 'MEDIUM',\n title: 'Low information density',\n message: `${Math.round(ratio * 100)}% of lines are duplicates. Possible filler content.`,\n });\n }\n}\n\nfunction checkDescriptionMismatch(\n results: CheckResult[],\n skill: ParsedSkill\n): void {\n const desc = skill.frontmatter.description;\n if (!desc || desc.length < 10) return;\n\n // Extract significant words from description\n const descWords = desc\n .toLowerCase()\n .split(/\\W+/)\n .filter((w) => w.length > 4);\n if (descWords.length === 0) return;\n\n const bodyLower = skill.body.toLowerCase();\n const matched = descWords.filter((w) => bodyLower.includes(w));\n\n // If less than 20% of description words appear in body\n if (matched.length / descWords.length < 0.2) {\n results.push({\n id: 'CONT-004',\n category: 'CONT',\n severity: 'MEDIUM',\n title: 'Description/body mismatch',\n message:\n 'The frontmatter description appears unrelated to the body content.',\n });\n }\n}\n\nfunction checkCodeHeavy(results: CheckResult[], skill: ParsedSkill): void {\n const lines = skill.bodyLines;\n if (lines.length < 10) return;\n\n let inCodeBlock = false;\n let codeLines = 0;\n\n for (const line of lines) {\n if (line.trim().startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n continue;\n }\n if (inCodeBlock) codeLines++;\n }\n\n const nonEmptyLines = lines.filter((l) => l.trim().length > 0).length;\n if (nonEmptyLines > 0 && codeLines / nonEmptyLines > 0.8) {\n results.push({\n id: 'CONT-006',\n category: 'CONT',\n severity: 'MEDIUM',\n title: 'Body is mostly code examples',\n message:\n 'Over 80% of body content is in code blocks with minimal instructions.',\n });\n }\n}\n\nfunction checkNameMismatch(results: CheckResult[], skill: ParsedSkill): void {\n const name = skill.frontmatter.name;\n if (!name) return;\n\n // Extract capability hints from name\n const nameWords = name\n .split(/[-_]/)\n .filter((w) => w.length > 2)\n .map((w) => w.toLowerCase());\n const bodyLower = skill.body.toLowerCase();\n\n // If name suggests a specific capability, check body mentions it\n const capabilityHints = nameWords.filter((w) =>\n !['the', 'and', 'for', 'skill', 'tool', 'helper', 'util'].includes(w)\n );\n\n if (capabilityHints.length === 0) return;\n\n const matched = capabilityHints.filter((w) => bodyLower.includes(w));\n if (matched.length === 0 && capabilityHints.length >= 2) {\n results.push({\n id: 'CONT-007',\n category: 'CONT',\n severity: 'HIGH',\n title: 'Name/body capability mismatch',\n message: `Skill name \"${name}\" implies capabilities not found in body content.`,\n });\n }\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n/**\n * Zero-width Unicode characters that can hide content.\n */\nexport const ZERO_WIDTH_CHARS = [\n '\\u200B', // ZERO WIDTH SPACE\n '\\u200C', // ZERO WIDTH NON-JOINER\n '\\u200D', // ZERO WIDTH JOINER\n '\\u200E', // LEFT-TO-RIGHT MARK\n '\\u200F', // RIGHT-TO-LEFT MARK\n '\\uFEFF', // ZERO WIDTH NO-BREAK SPACE (BOM)\n '\\u2060', // WORD JOINER\n '\\u2061', // FUNCTION APPLICATION\n '\\u2062', // INVISIBLE TIMES\n '\\u2063', // INVISIBLE SEPARATOR\n '\\u2064', // INVISIBLE PLUS\n];\n\n/**\n * RTL override characters that can manipulate display.\n */\nexport const RTL_OVERRIDE_CHARS = [\n '\\u202A', // LEFT-TO-RIGHT EMBEDDING\n '\\u202B', // RIGHT-TO-LEFT EMBEDDING\n '\\u202C', // POP DIRECTIONAL FORMATTING\n '\\u202D', // LEFT-TO-RIGHT OVERRIDE\n '\\u202E', // RIGHT-TO-LEFT OVERRIDE\n '\\u2066', // LEFT-TO-RIGHT ISOLATE\n '\\u2067', // RIGHT-TO-LEFT ISOLATE\n '\\u2068', // FIRST STRONG ISOLATE\n '\\u2069', // POP DIRECTIONAL ISOLATE\n];\n\n/**\n * Homoglyph map: Cyrillic/Greek characters that look like Latin.\n */\nconst HOMOGLYPHS: Record<string, string> = {\n '\\u0410': 'A', // Cyrillic А\n '\\u0412': 'B', // Cyrillic В\n '\\u0421': 'C', // Cyrillic С\n '\\u0415': 'E', // Cyrillic Е\n '\\u041D': 'H', // Cyrillic Н\n '\\u041A': 'K', // Cyrillic К\n '\\u041C': 'M', // Cyrillic М\n '\\u041E': 'O', // Cyrillic О\n '\\u0420': 'P', // Cyrillic Р\n '\\u0422': 'T', // Cyrillic Т\n '\\u0425': 'X', // Cyrillic Х\n '\\u0430': 'a', // Cyrillic а\n '\\u0435': 'e', // Cyrillic е\n '\\u043E': 'o', // Cyrillic о\n '\\u0440': 'p', // Cyrillic р\n '\\u0441': 'c', // Cyrillic с\n '\\u0443': 'y', // Cyrillic у\n '\\u0445': 'x', // Cyrillic х\n '\\u0391': 'A', // Greek Α\n '\\u0392': 'B', // Greek Β\n '\\u0395': 'E', // Greek Ε\n '\\u0397': 'H', // Greek Η\n '\\u0399': 'I', // Greek Ι\n '\\u039A': 'K', // Greek Κ\n '\\u039C': 'M', // Greek Μ\n '\\u039D': 'N', // Greek Ν\n '\\u039F': 'O', // Greek Ο\n '\\u03A1': 'P', // Greek Ρ\n '\\u03A4': 'T', // Greek Τ\n '\\u03A7': 'X', // Greek Χ\n '\\u03BF': 'o', // Greek ο\n};\n\n/**\n * Find zero-width characters in text, returning positions.\n */\nexport function findZeroWidthChars(\n text: string\n): Array<{ char: string; codePoint: string; position: number }> {\n const found: Array<{ char: string; codePoint: string; position: number }> = [];\n for (let i = 0; i < text.length; i++) {\n if (ZERO_WIDTH_CHARS.includes(text[i])) {\n found.push({\n char: text[i],\n codePoint: 'U+' + text[i].charCodeAt(0).toString(16).toUpperCase().padStart(4, '0'),\n position: i,\n });\n }\n }\n return found;\n}\n\n/**\n * Find RTL override characters in text.\n */\nexport function findRTLOverrides(\n text: string\n): Array<{ char: string; codePoint: string; position: number }> {\n const found: Array<{ char: string; codePoint: string; position: number }> = [];\n for (let i = 0; i < text.length; i++) {\n if (RTL_OVERRIDE_CHARS.includes(text[i])) {\n found.push({\n char: text[i],\n codePoint: 'U+' + text[i].charCodeAt(0).toString(16).toUpperCase().padStart(4, '0'),\n position: i,\n });\n }\n }\n return found;\n}\n\n/**\n * Find homoglyph characters (non-Latin chars posing as Latin).\n */\nexport function findHomoglyphs(\n text: string\n): Array<{ char: string; looksLike: string; position: number }> {\n const found: Array<{ char: string; looksLike: string; position: number }> = [];\n for (let i = 0; i < text.length; i++) {\n const latin = HOMOGLYPHS[text[i]];\n if (latin) {\n found.push({ char: text[i], looksLike: latin, position: i });\n }\n }\n return found;\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n/**\n * Calculate Shannon entropy of a string (bits per character).\n * Higher entropy suggests encoded/obfuscated content.\n * Typical English text: ~3.5-4.0, random/encoded: >4.5\n */\nexport function shannonEntropy(str: string): number {\n if (str.length === 0) return 0;\n\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) ?? 0) + 1);\n }\n\n let entropy = 0;\n const len = str.length;\n for (const count of freq.values()) {\n const p = count / len;\n if (p > 0) {\n entropy -= p * Math.log2(p);\n }\n }\n\n return entropy;\n}\n\n/**\n * Check if a string looks like base64 encoded content.\n */\nexport function isBase64Like(str: string): boolean {\n // Must be at least 50 chars and match base64 pattern\n if (str.length < 50) return false;\n return /^[A-Za-z0-9+/=]{50,}$/.test(str.trim());\n}\n\n/**\n * Check if a string looks like hex encoded content.\n */\nexport function isHexEncoded(str: string): boolean {\n if (str.length < 50) return false;\n return /^(0x)?[0-9a-fA-F]{50,}$/.test(str.trim());\n}\n\n/**\n * Try to decode base64 and check if result contains suspicious content.\n */\nexport function tryDecodeBase64(str: string): string | null {\n try {\n const decoded = Buffer.from(str.trim(), 'base64').toString('utf-8');\n // Check if decoded result is mostly printable\n const printable = decoded.replace(/[^\\x20-\\x7E\\n\\r\\t]/g, '');\n if (printable.length / decoded.length > 0.8) {\n return decoded;\n }\n return null;\n } catch {\n return null;\n }\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\nimport { findZeroWidthChars, findRTLOverrides, findHomoglyphs } from '../utils/unicode.js';\nimport { isBase64Like, tryDecodeBase64 } from '../utils/entropy.js';\n\n/** Patterns that attempt to override system prompts */\nconst SYSTEM_OVERRIDE_PATTERNS = [\n /ignore\\s+(all\\s+)?previous\\s+instructions/i,\n /ignore\\s+(all\\s+)?prior\\s+instructions/i,\n /disregard\\s+(all\\s+)?previous/i,\n /forget\\s+(all\\s+)?previous/i,\n /you\\s+are\\s+now\\s+a\\s+different/i,\n /new\\s+system\\s+prompt/i,\n /override\\s+system\\s+prompt/i,\n /your\\s+new\\s+instructions?\\s+(are|is)/i,\n /from\\s+now\\s+on,?\\s+you\\s+(will|must|should)/i,\n /act\\s+as\\s+(if|though)\\s+your\\s+instructions/i,\n];\n\n/** Patterns that manipulate tool output */\nconst TOOL_MANIPULATION_PATTERNS = [\n /\\bresult\\s*[:=]\\s*[\"']?success/i,\n /tool_result/i,\n /<tool_result>/i,\n /\\breturn\\s+[\"']?(true|success|approved)/i,\n /permissionDecision\\s*[:=]/i,\n];\n\n/** Tag injection patterns */\nconst TAG_INJECTION_PATTERNS = [\n /<system>/i,\n /<\\/system>/i,\n /<\\|im_start\\|>/i,\n /<\\|im_end\\|>/i,\n /<\\|endoftext\\|>/i,\n /<human>/i,\n /<assistant>/i,\n /<\\|system\\|>/i,\n /<\\|user\\|>/i,\n /<\\|assistant\\|>/i,\n];\n\n/** Delimiter confusion patterns */\nconst DELIMITER_PATTERNS = [\n /={5,}/,\n /-{5,}\\s*(system|instruction|prompt)/i,\n /#{3,}\\s*(system|instruction|prompt)/i,\n /\\[SYSTEM\\]/i,\n /\\[INST\\]/i,\n /\\[\\/INST\\]/i,\n];\n\nexport const injectionChecks: CheckModule = {\n name: 'Injection Detection',\n category: 'INJ',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n const fullText = skill.raw;\n\n // INJ-001: Zero-width Unicode characters\n const zeroWidth = findZeroWidthChars(fullText);\n if (zeroWidth.length > 0) {\n results.push({\n id: 'INJ-001',\n category: 'INJ',\n severity: 'CRITICAL',\n title: 'Zero-width Unicode characters detected',\n message: `Found ${zeroWidth.length} zero-width character(s): ${zeroWidth.slice(0, 5).map((z) => z.codePoint).join(', ')}. These can hide malicious content.`,\n });\n }\n\n // INJ-002: Homoglyph characters\n const homoglyphs = findHomoglyphs(fullText);\n if (homoglyphs.length > 0) {\n results.push({\n id: 'INJ-002',\n category: 'INJ',\n severity: 'HIGH',\n title: 'Homoglyph characters detected',\n message: `Found ${homoglyphs.length} character(s) that mimic Latin letters (e.g. Cyrillic/Greek). Could be used for spoofing.`,\n snippet: homoglyphs\n .slice(0, 5)\n .map((h) => `\"${h.char}\" looks like \"${h.looksLike}\"`)\n .join(', '),\n });\n }\n\n // INJ-003: RTL override characters\n const rtl = findRTLOverrides(fullText);\n if (rtl.length > 0) {\n results.push({\n id: 'INJ-003',\n category: 'INJ',\n severity: 'CRITICAL',\n title: 'RTL override characters detected',\n message: `Found ${rtl.length} RTL/bidirectional override character(s): ${rtl.slice(0, 5).map((r) => r.codePoint).join(', ')}. These can manipulate text display direction.`,\n });\n }\n\n // Check body lines for remaining patterns\n for (let i = 0; i < skill.bodyLines.length; i++) {\n const line = skill.bodyLines[i];\n const lineNum = skill.bodyStartLine + i;\n\n // INJ-004: System prompt override\n for (const pattern of SYSTEM_OVERRIDE_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'INJ-004',\n category: 'INJ',\n severity: 'CRITICAL',\n title: 'System prompt override attempt',\n message: `Line ${lineNum}: Attempts to override system instructions.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // INJ-005: Tool output manipulation\n for (const pattern of TOOL_MANIPULATION_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'INJ-005',\n category: 'INJ',\n severity: 'HIGH',\n title: 'Tool output manipulation',\n message: `Line ${lineNum}: Attempts to manipulate tool results.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // INJ-007: Tag injection\n for (const pattern of TAG_INJECTION_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'INJ-007',\n category: 'INJ',\n severity: 'CRITICAL',\n title: 'Tag injection detected',\n message: `Line ${lineNum}: Contains special model/system tags.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // INJ-009: Delimiter confusion\n for (const pattern of DELIMITER_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'INJ-009',\n category: 'INJ',\n severity: 'MEDIUM',\n title: 'Delimiter confusion pattern',\n message: `Line ${lineNum}: Uses patterns that could confuse model context boundaries.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n }\n\n // INJ-006: Hidden instructions in HTML/Markdown comments\n const commentRegex = /<!--([\\s\\S]*?)-->/g;\n let commentMatch;\n while ((commentMatch = commentRegex.exec(fullText)) !== null) {\n const commentBody = commentMatch[1];\n if (hasInstructionLikeContent(commentBody)) {\n const lineNum = fullText.slice(0, commentMatch.index).split('\\n').length;\n results.push({\n id: 'INJ-006',\n category: 'INJ',\n severity: 'HIGH',\n title: 'Hidden instructions in HTML comment',\n message: `Line ${lineNum}: HTML comment contains instruction-like content.`,\n line: lineNum,\n snippet: commentBody.trim().slice(0, 120),\n });\n }\n }\n\n // INJ-008: Encoded instructions (base64 in body)\n const base64Regex = /[A-Za-z0-9+/=]{60,}/g;\n let b64Match;\n while ((b64Match = base64Regex.exec(skill.body)) !== null) {\n const candidate = b64Match[0];\n if (isBase64Like(candidate)) {\n const decoded = tryDecodeBase64(candidate);\n if (decoded && hasInstructionLikeContent(decoded)) {\n const lineNum =\n skill.bodyStartLine +\n skill.body.slice(0, b64Match.index).split('\\n').length -\n 1;\n results.push({\n id: 'INJ-008',\n category: 'INJ',\n severity: 'CRITICAL',\n title: 'Encoded instructions detected',\n message: `Line ${lineNum}: Base64 string decodes to instruction-like content.`,\n line: lineNum,\n snippet: decoded.slice(0, 120),\n });\n }\n }\n }\n\n // Deduplicate by id+line\n return dedup(results);\n },\n};\n\nfunction hasInstructionLikeContent(text: string): boolean {\n const instructionPatterns = [\n /you\\s+(must|should|will|are)/i,\n /ignore\\s+previous/i,\n /execute\\s+the\\s+following/i,\n /run\\s+this\\s+command/i,\n /\\bsudo\\b/i,\n /\\brm\\s+-rf\\b/i,\n /\\bcurl\\b.*\\bsh\\b/i,\n /\\beval\\b/i,\n /\\bexec\\b/i,\n ];\n return instructionPatterns.some((p) => p.test(text));\n}\n\nfunction dedup(results: CheckResult[]): CheckResult[] {\n const seen = new Set<string>();\n return results.filter((r) => {\n const key = `${r.id}:${r.line ?? ''}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\nimport { shannonEntropy, isBase64Like, isHexEncoded } from '../utils/entropy.js';\nimport { isInDocumentationContext } from '../utils/context.js';\n\n/** Dangerous eval/exec patterns */\nconst EVAL_PATTERNS = [\n /\\beval\\s*\\(/,\n /\\bexec\\s*\\(/,\n /\\bnew\\s+Function\\s*\\(/,\n /\\bsetTimeout\\s*\\(\\s*[\"'`]/,\n /\\bsetInterval\\s*\\(\\s*[\"'`]/,\n];\n\n/** Shell execution patterns */\nconst SHELL_EXEC_PATTERNS = [\n /\\bchild_process\\b/,\n /\\bexecSync\\b/,\n /\\bspawnSync\\b/,\n /\\bos\\.system\\s*\\(/,\n /\\bsubprocess\\.(run|call|Popen)\\s*\\(/,\n /(?<!\\bplatform\\.)\\bsystem\\s*\\(/, // exclude platform.system()\n /`[^`]*\\$\\([^)]+\\)[^`]*`/, // backtick with command substitution\n];\n\n/**\n * Patterns that look like shell execution but are actually\n * read-only system info queries (not dangerous).\n */\nconst SHELL_EXEC_FALSE_POSITIVES = [\n /\\bplatform\\.system\\s*\\(\\s*\\)/, // Python: just reads OS name\n];\n\n/** Destructive file operations */\nconst DESTRUCTIVE_PATTERNS = [\n /\\brm\\s+-rf\\b/,\n /\\brm\\s+-r\\b/,\n /\\brmdir\\b/,\n /\\bunlink\\s*\\(/,\n /\\bfs\\.rm(Sync)?\\s*\\(/,\n /\\bshutil\\.rmtree\\s*\\(/,\n /\\bdel\\s+\\/[sf]/i,\n /\\bformat\\s+[a-z]:/i,\n];\n\n/** Network request patterns with hardcoded URLs */\nconst NETWORK_PATTERNS = [\n /\\bfetch\\s*\\(\\s*[\"'`]https?:\\/\\//,\n /\\baxios\\.(get|post|put|delete)\\s*\\(\\s*[\"'`]https?:\\/\\//,\n /\\bcurl\\s+/,\n /\\bwget\\s+/,\n /\\brequests?\\.(get|post)\\s*\\(/,\n /\\bhttp\\.get\\s*\\(/,\n /\\bURLSession\\b/,\n];\n\n/** File write outside expected directory */\nconst FILE_WRITE_PATTERNS = [\n /\\bfs\\.writeFile(Sync)?\\s*\\(\\s*[\"'`]\\//,\n /\\bopen\\s*\\(\\s*[\"'`]\\/[^\"'`]+[\"'`]\\s*,\\s*[\"'`]w/,\n />\\s*\\/etc\\//,\n />\\s*\\/usr\\//,\n />\\s*~\\//,\n />\\s*\\$HOME\\//,\n];\n\n/** Environment variable access */\nconst ENV_ACCESS_PATTERNS = [\n /process\\.env\\b/,\n /\\bos\\.environ\\b/,\n /\\bgetenv\\s*\\(/,\n /\\$\\{?\\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API_KEY)\\w*\\}?/i,\n];\n\n/** Dynamic code generation */\nconst DYNAMIC_CODE_PATTERNS = [\n /\\bcompile\\s*\\(/,\n /\\bcodegen\\b/i,\n /\\bimport\\s*\\(\\s*[^\"'`\\s]/,\n /\\brequire\\s*\\(\\s*[^\"'`\\s]/,\n /\\b__import__\\s*\\(/,\n];\n\n/** Permission escalation */\nconst PERMISSION_PATTERNS = [\n /\\bchmod\\s+[+0-9]/,\n /\\bchown\\b/,\n /\\bsudo\\b/,\n /\\bdoas\\b/,\n /\\bsetuid\\b/,\n /\\bsetgid\\b/,\n];\n\nexport const codeSafetyChecks: CheckModule = {\n name: 'Code Safety',\n category: 'CODE',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n\n // Collect all text content to scan\n const textSources = getTextSources(skill);\n\n for (const { text, source } of textSources) {\n const lines = text.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const lineNum = i + 1;\n const loc = `${source}:${lineNum}`;\n\n // CODE-001: eval/exec\n checkPatterns(results, line, EVAL_PATTERNS, {\n id: 'CODE-001',\n severity: 'CRITICAL',\n title: 'eval/exec/Function constructor',\n loc,\n lineNum,\n });\n\n // CODE-002: shell execution\n // Skip read-only system info queries (e.g. platform.system())\n if (!SHELL_EXEC_FALSE_POSITIVES.some((p) => p.test(line))) {\n checkPatterns(results, line, SHELL_EXEC_PATTERNS, {\n id: 'CODE-002',\n severity: 'CRITICAL',\n title: 'Shell/subprocess execution',\n loc,\n lineNum,\n });\n }\n\n // CODE-003: destructive file operations\n checkPatterns(results, line, DESTRUCTIVE_PATTERNS, {\n id: 'CODE-003',\n severity: 'CRITICAL',\n title: 'Destructive file operation',\n loc,\n lineNum,\n });\n\n // CODE-004: hardcoded external URLs\n checkPatterns(results, line, NETWORK_PATTERNS, {\n id: 'CODE-004',\n severity: 'HIGH',\n title: 'Hardcoded external URL/network request',\n loc,\n lineNum,\n });\n\n // CODE-005: file write outside expected dir\n checkPatterns(results, line, FILE_WRITE_PATTERNS, {\n id: 'CODE-005',\n severity: 'HIGH',\n title: 'File write outside expected directory',\n loc,\n lineNum,\n });\n\n // CODE-006: env var access\n checkPatterns(results, line, ENV_ACCESS_PATTERNS, {\n id: 'CODE-006',\n severity: 'MEDIUM',\n title: 'Environment variable access',\n loc,\n lineNum,\n });\n\n // CODE-010: dynamic code generation\n checkPatterns(results, line, DYNAMIC_CODE_PATTERNS, {\n id: 'CODE-010',\n severity: 'HIGH',\n title: 'Dynamic code generation pattern',\n loc,\n lineNum,\n });\n\n // CODE-012: permission escalation\n // Skip when in documentation context (installation guides)\n {\n const srcLines = text.split('\\n');\n const isDoc = isInDocumentationContext(srcLines, i);\n if (!isDoc) {\n checkPatterns(results, line, PERMISSION_PATTERNS, {\n id: 'CODE-012',\n severity: 'HIGH',\n title: 'Permission escalation',\n loc,\n lineNum,\n });\n }\n }\n }\n\n // Multi-line checks\n scanEncodedStrings(results, text, source);\n scanObfuscation(results, text, source);\n }\n\n return results;\n },\n};\n\ninterface PatternCheckOpts {\n id: string;\n severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';\n title: string;\n loc: string;\n lineNum: number;\n}\n\nfunction checkPatterns(\n results: CheckResult[],\n line: string,\n patterns: RegExp[],\n opts: PatternCheckOpts\n): void {\n for (const pattern of patterns) {\n if (pattern.test(line)) {\n results.push({\n id: opts.id,\n category: 'CODE',\n severity: opts.severity,\n title: opts.title,\n message: `At ${opts.loc}: ${line.trim().slice(0, 120)}`,\n line: opts.lineNum,\n snippet: line.trim().slice(0, 120),\n });\n return; // one match per line per rule\n }\n }\n}\n\nfunction getTextSources(\n skill: ParsedSkill\n): Array<{ text: string; source: string }> {\n const sources: Array<{ text: string; source: string }> = [\n { text: skill.body, source: 'SKILL.md' },\n ];\n for (const file of skill.files) {\n if (file.content && file.path !== 'SKILL.md') {\n sources.push({ text: file.content, source: file.path });\n }\n }\n return sources;\n}\n\nfunction scanEncodedStrings(\n results: CheckResult[],\n text: string,\n source: string\n): void {\n // CODE-007: Base64/Hex long strings\n const longStringRegex = /[A-Za-z0-9+/=]{50,}|(?:0x)?[0-9a-fA-F]{50,}/g;\n let match;\n while ((match = longStringRegex.exec(text)) !== null) {\n const str = match[0];\n if (isBase64Like(str) || isHexEncoded(str)) {\n const lineNum = text.slice(0, match.index).split('\\n').length;\n results.push({\n id: 'CODE-007',\n category: 'CODE',\n severity: 'HIGH',\n title: 'Long encoded string',\n message: `${source}:${lineNum}: Found ${str.length}-char encoded string.`,\n line: lineNum,\n snippet: str.slice(0, 80) + '...',\n });\n }\n }\n\n // CODE-008: High Shannon entropy strings\n const wordRegex = /\\b[A-Za-z0-9_]{20,}\\b/g;\n while ((match = wordRegex.exec(text)) !== null) {\n const entropy = shannonEntropy(match[0]);\n if (entropy > 4.5) {\n const lineNum = text.slice(0, match.index).split('\\n').length;\n results.push({\n id: 'CODE-008',\n category: 'CODE',\n severity: 'MEDIUM',\n title: 'High entropy string',\n message: `${source}:${lineNum}: String \"${match[0].slice(0, 30)}...\" has entropy ${entropy.toFixed(2)} bits/char.`,\n line: lineNum,\n });\n }\n }\n\n // CODE-009: Multi-layer encoding\n const multiEncodingPatterns = [\n /atob\\s*\\(\\s*atob/i,\n /base64.*decode.*base64.*decode/i,\n /Buffer\\.from\\(.*Buffer\\.from/,\n /decode.*decode.*decode/i,\n ];\n for (const pattern of multiEncodingPatterns) {\n if (pattern.test(text)) {\n results.push({\n id: 'CODE-009',\n category: 'CODE',\n severity: 'CRITICAL',\n title: 'Multi-layer encoding detected',\n message: `${source}: Contains nested encoding/decoding operations.`,\n });\n break;\n }\n }\n}\n\nfunction scanObfuscation(\n results: CheckResult[],\n text: string,\n source: string\n): void {\n // CODE-011: Obfuscated variable names\n // Look for patterns like: const _0x1a2b = ...\n const obfuscatedVarRegex = /\\b_0x[0-9a-f]{2,}\\b/g;\n const obfMatches = text.match(obfuscatedVarRegex);\n if (obfMatches && obfMatches.length >= 3) {\n results.push({\n id: 'CODE-011',\n category: 'CODE',\n severity: 'MEDIUM',\n title: 'Obfuscated variable names',\n message: `${source}: Found ${obfMatches.length} hex-style variable names (e.g. ${obfMatches[0]}). May indicate obfuscated code.`,\n });\n }\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\nimport {\n isNamespaceOrSchemaURI,\n isInNetworkRequestContext,\n isInDocumentationContext,\n} from '../utils/context.js';\n\n/** Known suspicious/malicious domains (sample list) */\nconst SUSPICIOUS_DOMAINS = [\n 'evil.com',\n 'malware.com',\n 'exploit.in',\n 'darkweb.onion',\n 'pastebin.com', // often used for payload hosting\n 'ngrok.io', // tunneling service\n 'requestbin.com',\n 'webhook.site',\n 'pipedream.net',\n 'burpcollaborator.net',\n 'interact.sh',\n 'oastify.com',\n];\n\nconst MCP_SERVER_PATTERN = /\\bmcp[-_]?server\\b/i;\nconst NPX_Y_PATTERN = /\\bnpx\\s+-y\\s+/;\nconst NPM_INSTALL_PATTERN = /\\bnpm\\s+install\\b/;\nconst PIP_INSTALL_PATTERN = /\\bpip3?\\s+install\\b/;\nconst GIT_CLONE_PATTERN = /\\bgit\\s+clone\\b/;\n\n/** URL extraction pattern */\nconst URL_PATTERN = /https?:\\/\\/[^\\s\"'`<>)\\]]+/g;\n/** IP address pattern (not localhost) */\nconst IP_URL_PATTERN = /https?:\\/\\/(?:\\d{1,3}\\.){3}\\d{1,3}/;\n\nexport const supplyChainChecks: CheckModule = {\n name: 'Supply Chain',\n category: 'SUPPLY',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n const allText = getAllText(skill);\n\n for (let i = 0; i < allText.length; i++) {\n const { line, lineNum, source } = allText[i];\n\n // SUPPLY-001: Unknown MCP server references\n if (MCP_SERVER_PATTERN.test(line)) {\n results.push({\n id: 'SUPPLY-001',\n category: 'SUPPLY',\n severity: 'HIGH',\n title: 'MCP server reference',\n message: `${source}:${lineNum}: References an MCP server. Verify it is from a trusted source.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n }\n\n // SUPPLY-002: npx -y auto-install\n if (NPX_Y_PATTERN.test(line)) {\n results.push({\n id: 'SUPPLY-002',\n category: 'SUPPLY',\n severity: 'MEDIUM',\n title: 'npx -y auto-install',\n message: `${source}:${lineNum}: Uses npx -y which auto-installs packages without confirmation.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n }\n\n // SUPPLY-003: npm/pip install unknown packages\n // Skip when in documentation context (installation guides / prerequisites)\n if (NPM_INSTALL_PATTERN.test(line) || PIP_INSTALL_PATTERN.test(line)) {\n const allLines = getAllLines(skill);\n const globalIdx = findGlobalLineIndex(allLines, source, lineNum);\n const isDoc = globalIdx >= 0 && isInDocumentationContext(\n allLines.map((l) => l.line),\n globalIdx\n );\n if (!isDoc) {\n results.push({\n id: 'SUPPLY-003',\n category: 'SUPPLY',\n severity: 'HIGH',\n title: 'Package installation command',\n message: `${source}:${lineNum}: Installs packages. Verify package names are legitimate.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n }\n }\n\n // SUPPLY-006: git clone non-standard source\n if (GIT_CLONE_PATTERN.test(line)) {\n results.push({\n id: 'SUPPLY-006',\n category: 'SUPPLY',\n severity: 'MEDIUM',\n title: 'git clone command',\n message: `${source}:${lineNum}: Clones a git repository. Verify the source.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n }\n\n // URL-based checks\n const urls = line.match(URL_PATTERN) || [];\n for (const url of urls) {\n // SUPPLY-004: Non-HTTPS URL\n // Skip namespace/schema URIs (identifiers, not network endpoints)\n // and URLs not in actual network request context\n if (url.startsWith('http://')) {\n if (!isNamespaceOrSchemaURI(url, line)) {\n // Still flag if in network request context, or as lower severity info\n const isNetworkCtx = isInNetworkRequestContext(line);\n results.push({\n id: 'SUPPLY-004',\n category: 'SUPPLY',\n severity: isNetworkCtx ? 'HIGH' : 'MEDIUM',\n title: 'Non-HTTPS URL',\n message: `${source}:${lineNum}: Uses insecure HTTP: ${url}`,\n line: lineNum,\n snippet: url,\n });\n }\n }\n\n // SUPPLY-005: IP address instead of domain\n if (IP_URL_PATTERN.test(url)) {\n // Exclude localhost\n if (!/https?:\\/\\/127\\.0\\.0\\.1/.test(url) && !/https?:\\/\\/0\\.0\\.0\\.0/.test(url)) {\n results.push({\n id: 'SUPPLY-005',\n category: 'SUPPLY',\n severity: 'CRITICAL',\n title: 'IP address used instead of domain',\n message: `${source}:${lineNum}: Uses raw IP address: ${url}. This may bypass DNS-based security.`,\n line: lineNum,\n snippet: url,\n });\n }\n }\n\n // SUPPLY-007: Known suspicious domains\n for (const domain of SUSPICIOUS_DOMAINS) {\n if (url.includes(domain)) {\n results.push({\n id: 'SUPPLY-007',\n category: 'SUPPLY',\n severity: 'CRITICAL',\n title: 'Suspicious domain detected',\n message: `${source}:${lineNum}: References suspicious domain \"${domain}\".`,\n line: lineNum,\n snippet: url,\n });\n break;\n }\n }\n }\n }\n\n return results;\n },\n};\n\ntype TextLine = { line: string; lineNum: number; source: string };\n\nfunction getAllText(skill: ParsedSkill): TextLine[] {\n const result: TextLine[] = [];\n\n for (let i = 0; i < skill.bodyLines.length; i++) {\n result.push({\n line: skill.bodyLines[i],\n lineNum: skill.bodyStartLine + i,\n source: 'SKILL.md',\n });\n }\n\n for (const file of skill.files) {\n if (file.content && file.path !== 'SKILL.md') {\n const lines = file.content.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n result.push({ line: lines[i], lineNum: i + 1, source: file.path });\n }\n }\n }\n\n return result;\n}\n\n/** Get all lines from SKILL.md body (for context lookback) */\nfunction getAllLines(skill: ParsedSkill): TextLine[] {\n return getAllText(skill);\n}\n\n/** Find the global index of a source:lineNum in the flat list */\nfunction findGlobalLineIndex(\n allLines: TextLine[],\n source: string,\n lineNum: number\n): number {\n return allLines.findIndex(\n (l) => l.source === source && l.lineNum === lineNum\n );\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\n\n/** Recursive/amplification patterns */\nconst AMPLIFICATION_PATTERNS = [\n /\\brepeat\\s+(this|the\\s+above)\\s+\\d+\\s+times\\b/i,\n /\\bdo\\s+this\\s+forever\\b/i,\n /\\binfinite\\s+loop\\b/i,\n /\\bwhile\\s*\\(\\s*true\\s*\\)/,\n /\\bfor\\s*\\(\\s*;;\\s*\\)/,\n /\\brecursively\\s+(apply|run|execute|call)/i,\n /\\bkeep\\s+(running|doing|executing)\\s+until/i,\n];\n\n/** Requesting unrestricted tool access */\nconst UNRESTRICTED_TOOL_PATTERNS = [\n /\\bBash\\s*\\(\\s*\\*\\s*\\)/,\n /allowed[_-]?tools\\s*:\\s*\\[?\\s*[\"']?\\*[\"']?\\s*\\]?/i,\n /\\ball\\s+tools\\b/i,\n /\\bunrestricted\\s+access\\b/i,\n /\\bfull\\s+access\\b/i,\n];\n\n/** Patterns that disable safety */\nconst DISABLE_SAFETY_PATTERNS = [\n /\\bdisable\\s+(safety|security|checks?|hooks?|guard)/i,\n /\\bbypass\\s+(safety|security|checks?|hooks?|guard)/i,\n /\\bskip\\s+(safety|security|checks?|hooks?|guard|verification)/i,\n /\\bturn\\s+off\\s+(safety|security|checks?|hooks?)/i,\n /--no-verify\\b/,\n /--force\\b/,\n /--skip-hooks?\\b/,\n];\n\n/** Patterns that ignore project rules */\nconst IGNORE_RULES_PATTERNS = [\n /\\bignore\\s+(the\\s+)?CLAUDE\\.md\\b/i,\n /\\bignore\\s+(the\\s+)?project\\s+rules?\\b/i,\n /\\bignore\\s+(the\\s+)?\\.claude\\b/i,\n /\\boverride\\s+(the\\s+)?project\\s+(settings?|config|rules?)\\b/i,\n /\\bdo\\s+not\\s+(follow|obey|respect)\\s+(the\\s+)?(project|CLAUDE)/i,\n /\\bdisregard\\s+(the\\s+)?(project|CLAUDE)\\s+(rules?|config|settings?)/i,\n];\n\n/** Token waste patterns */\nconst TOKEN_WASTE_PATTERNS = [\n /\\brepeat\\s+(every|each)\\s+(response|answer|reply)/i,\n /\\balways\\s+(start|begin|end)\\s+(every|each)\\s+(response|answer|reply)\\s+with/i,\n /\\binclude\\s+this\\s+(text|message|string)\\s+in\\s+(every|each|all)/i,\n /\\bprint\\s+(the\\s+)?full\\s+(source|code|file)\\s+(every|each)\\s+time/i,\n];\n\nexport const resourceChecks: CheckModule = {\n name: 'Resource Abuse',\n category: 'RES',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n\n for (let i = 0; i < skill.bodyLines.length; i++) {\n const line = skill.bodyLines[i];\n const lineNum = skill.bodyStartLine + i;\n\n // RES-001: Instruction amplification\n for (const pattern of AMPLIFICATION_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'RES-001',\n category: 'RES',\n severity: 'HIGH',\n title: 'Instruction amplification',\n message: `Line ${lineNum}: Contains recursive/repetitive task pattern.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // RES-002: Unrestricted tool access\n for (const pattern of UNRESTRICTED_TOOL_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'RES-002',\n category: 'RES',\n severity: 'CRITICAL',\n title: 'Unrestricted tool access requested',\n message: `Line ${lineNum}: Requests broad/unrestricted tool access.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // RES-004: Disable safety checks\n for (const pattern of DISABLE_SAFETY_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'RES-004',\n category: 'RES',\n severity: 'CRITICAL',\n title: 'Attempts to disable safety checks',\n message: `Line ${lineNum}: Instructs disabling of safety mechanisms.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // RES-005: Token waste\n for (const pattern of TOKEN_WASTE_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'RES-005',\n category: 'RES',\n severity: 'MEDIUM',\n title: 'Token waste pattern',\n message: `Line ${lineNum}: Contains instructions that waste tokens.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n\n // RES-006: Ignore CLAUDE.md / project rules\n for (const pattern of IGNORE_RULES_PATTERNS) {\n if (pattern.test(line)) {\n results.push({\n id: 'RES-006',\n category: 'RES',\n severity: 'CRITICAL',\n title: 'Attempts to ignore project rules',\n message: `Line ${lineNum}: Instructs ignoring CLAUDE.md or project configuration.`,\n line: lineNum,\n snippet: line.trim().slice(0, 120),\n });\n break;\n }\n }\n }\n\n // RES-003: Excessive allowed-tools\n const allowedTools = skill.frontmatter['allowed-tools'];\n if (Array.isArray(allowedTools) && allowedTools.length > 15) {\n results.push({\n id: 'RES-003',\n category: 'RES',\n severity: 'MEDIUM',\n title: 'Excessive allowed-tools list',\n message: `Frontmatter declares ${allowedTools.length} allowed tools. This is unusually broad.`,\n });\n }\n\n // RES-002 (frontmatter): Check allowed-tools for dangerous patterns\n if (Array.isArray(allowedTools)) {\n for (const tool of allowedTools) {\n if (typeof tool !== 'string') continue;\n for (const pattern of UNRESTRICTED_TOOL_PATTERNS) {\n if (pattern.test(tool)) {\n results.push({\n id: 'RES-002',\n category: 'RES',\n severity: 'CRITICAL',\n title: 'Unrestricted tool access requested',\n message: `Frontmatter allowed-tools contains dangerous pattern: \"${tool}\"`,\n snippet: tool.slice(0, 120),\n });\n break;\n }\n }\n }\n }\n\n return results;\n },\n};\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { DEFAULT_IOC, type IOCDatabase } from './indicators.js';\n\nlet cachedIOC: IOCDatabase | null = null;\n\n/**\n * Load IOC database: embedded seed data + optional external override file.\n * External file is merged (appended) into the seed data, not replacing it.\n *\n * Search paths for override file:\n * 1. ~/.config/skill-checker/ioc-override.json\n */\nexport function loadIOC(): IOCDatabase {\n if (cachedIOC) return cachedIOC;\n\n const ioc = structuredClone(DEFAULT_IOC);\n\n const overridePath = join(\n homedir(),\n '.config',\n 'skill-checker',\n 'ioc-override.json'\n );\n\n if (existsSync(overridePath)) {\n try {\n const raw = readFileSync(overridePath, 'utf-8');\n const ext = JSON.parse(raw) as Partial<IOCDatabase>;\n mergeIOC(ioc, ext);\n } catch {\n // Invalid override file — silently use seed data only\n }\n }\n\n cachedIOC = ioc;\n return ioc;\n}\n\n/**\n * Reset the cached IOC database (useful for testing).\n */\nexport function resetIOCCache(): void {\n cachedIOC = null;\n}\n\n/**\n * Merge external IOC data into the base database.\n * Arrays are concatenated (deduplicated), objects are merged.\n */\nfunction mergeIOC(base: IOCDatabase, ext: Partial<IOCDatabase>): void {\n if (ext.c2_ips) {\n base.c2_ips = dedupe([...base.c2_ips, ...ext.c2_ips]);\n }\n if (ext.malicious_hashes) {\n Object.assign(base.malicious_hashes, ext.malicious_hashes);\n }\n if (ext.malicious_domains) {\n base.malicious_domains = dedupe([\n ...base.malicious_domains,\n ...ext.malicious_domains,\n ]);\n }\n if (ext.typosquat) {\n if (ext.typosquat.known_patterns) {\n base.typosquat.known_patterns = dedupe([\n ...base.typosquat.known_patterns,\n ...ext.typosquat.known_patterns,\n ]);\n }\n if (ext.typosquat.protected_names) {\n base.typosquat.protected_names = dedupe([\n ...base.typosquat.protected_names,\n ...ext.typosquat.protected_names,\n ]);\n }\n }\n if (ext.malicious_publishers) {\n base.malicious_publishers = dedupe([\n ...base.malicious_publishers,\n ...ext.malicious_publishers,\n ]);\n }\n if (ext.version) base.version = ext.version;\n if (ext.updated) base.updated = ext.updated;\n}\n\nfunction dedupe(arr: string[]): string[] {\n return [...new Set(arr)];\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n/**\n * IOC (Indicators of Compromise) database type definition and seed data.\n * Seed data is compiled from publicly available threat intelligence.\n */\n\nexport interface IOCDatabase {\n version: string;\n updated: string;\n c2_ips: string[];\n malicious_hashes: Record<string, string>;\n malicious_domains: string[];\n typosquat: {\n known_patterns: string[];\n protected_names: string[];\n };\n malicious_publishers: string[];\n}\n\n/**\n * Default embedded IOC seed data.\n * Sources: public threat intelligence reports, community advisories.\n */\nexport const DEFAULT_IOC: IOCDatabase = {\n version: '2026.03.06',\n updated: '2026-03-06',\n\n c2_ips: [\n '91.92.242.30',\n '91.92.242.39',\n '185.220.101.1',\n '185.220.101.2',\n '45.155.205.233',\n ],\n\n malicious_hashes: {\n 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855':\n 'clawhavoc-empty-payload',\n 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2':\n 'clawhavoc-exfiltrator',\n },\n\n malicious_domains: [\n 'webhook.site',\n 'requestbin.com',\n 'pipedream.com',\n 'pipedream.net',\n 'hookbin.com',\n 'beeceptor.com',\n 'ngrok.io',\n 'ngrok-free.app',\n 'serveo.net',\n 'localtunnel.me',\n 'bore.pub',\n 'interact.sh',\n 'oast.fun',\n 'oastify.com',\n 'dnslog.cn',\n 'ceye.io',\n 'burpcollaborator.net',\n 'pastebin.com',\n 'paste.ee',\n 'hastebin.com',\n 'ghostbin.com',\n 'evil.com',\n 'malware.com',\n 'exploit.in',\n ],\n\n typosquat: {\n known_patterns: [\n 'clawhub1',\n 'cllawhub',\n 'clawhab',\n 'moltbot',\n 'claw-hub',\n 'clawhub-pro',\n ],\n protected_names: [\n 'clawhub',\n 'secureclaw',\n 'openclaw',\n 'clawbot',\n 'claude',\n 'anthropic',\n 'skill-checker',\n ],\n },\n\n malicious_publishers: [\n 'clawhavoc',\n 'phantom-tracker',\n 'solana-wallet-drainer',\n ],\n};\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { levenshtein } from '../utils/levenshtein.js';\nimport type { IOCDatabase } from './indicators.js';\nimport type { ParsedSkill } from '../types.js';\n\n/** IPv4 pattern — matches standalone IPs in text */\nconst IPV4_PATTERN = /\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\b/g;\n\n/** Private/reserved IP ranges to exclude */\nfunction isPrivateIP(ip: string): boolean {\n const parts = ip.split('.').map(Number);\n if (parts.length !== 4 || parts.some((p) => p < 0 || p > 255)) return true;\n // 127.x.x.x (loopback)\n if (parts[0] === 127) return true;\n // 10.x.x.x\n if (parts[0] === 10) return true;\n // 172.16-31.x.x\n if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;\n // 192.168.x.x\n if (parts[0] === 192 && parts[1] === 168) return true;\n // 0.0.0.0\n if (parts.every((p) => p === 0)) return true;\n // 169.254.x.x (link-local)\n if (parts[0] === 169 && parts[1] === 254) return true;\n return false;\n}\n\n/**\n * SUPPLY-008: Check file hashes against known malicious hashes.\n */\nexport function matchMaliciousHashes(\n skill: ParsedSkill,\n ioc: IOCDatabase\n): { file: string; hash: string; description: string }[] {\n const matches: { file: string; hash: string; description: string }[] = [];\n const hashKeys = Object.keys(ioc.malicious_hashes);\n if (hashKeys.length === 0) return matches;\n\n for (const file of skill.files) {\n const filePath = join(skill.dirPath, file.path);\n try {\n const content = readFileSync(filePath);\n const hash = createHash('sha256').update(content).digest('hex');\n if (ioc.malicious_hashes[hash]) {\n matches.push({\n file: file.path,\n hash,\n description: ioc.malicious_hashes[hash],\n });\n }\n } catch {\n // Skip unreadable files\n }\n }\n\n return matches;\n}\n\n/**\n * SUPPLY-009: Extract IPs from skill content and match against C2 list.\n */\nexport function matchC2IPs(\n skill: ParsedSkill,\n ioc: IOCDatabase\n): { ip: string; line: number; source: string; snippet: string }[] {\n const matches: { ip: string; line: number; source: string; snippet: string }[] = [];\n if (ioc.c2_ips.length === 0) return matches;\n\n const c2Set = new Set(ioc.c2_ips);\n const allText = getAllText(skill);\n\n for (const { line, lineNum, source } of allText) {\n let m: RegExpExecArray | null;\n const re = new RegExp(IPV4_PATTERN.source, 'g');\n while ((m = re.exec(line)) !== null) {\n const ip = m[1];\n if (!isPrivateIP(ip) && c2Set.has(ip)) {\n matches.push({\n ip,\n line: lineNum,\n source,\n snippet: line.trim().slice(0, 120),\n });\n }\n }\n }\n\n return matches;\n}\n\n/**\n * SUPPLY-010: Check skill name for typosquatting.\n * Two-layer strategy:\n * 1. Exact match against known_patterns → CRITICAL\n * 2. Levenshtein distance ≤ 2 against protected_names → HIGH\n */\nexport function matchTyposquat(\n skillName: string,\n ioc: IOCDatabase\n): { type: 'known' | 'similar'; target: string; distance?: number } | null {\n if (!skillName) return null;\n const name = skillName.toLowerCase().trim();\n\n // Layer 1: exact match against known typosquat patterns\n for (const pattern of ioc.typosquat.known_patterns) {\n if (name === pattern.toLowerCase()) {\n return { type: 'known', target: pattern };\n }\n }\n\n // Layer 2: edit distance against protected names\n for (const protected_name of ioc.typosquat.protected_names) {\n const pn = protected_name.toLowerCase();\n if (name === pn) continue; // exact match = legitimate, skip\n const dist = levenshtein(name, pn);\n if (dist > 0 && dist <= 2) {\n return { type: 'similar', target: protected_name, distance: dist };\n }\n }\n\n return null;\n}\n\ntype TextLine = { line: string; lineNum: number; source: string };\n\nfunction getAllText(skill: ParsedSkill): TextLine[] {\n const result: TextLine[] = [];\n\n for (let i = 0; i < skill.bodyLines.length; i++) {\n result.push({\n line: skill.bodyLines[i],\n lineNum: skill.bodyStartLine + i,\n source: 'SKILL.md',\n });\n }\n\n for (const file of skill.files) {\n if (file.content && file.path !== 'SKILL.md') {\n const lines = file.content.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n result.push({ line: lines[i], lineNum: i + 1, source: file.path });\n }\n }\n }\n\n return result;\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n/**\n * Compute the Levenshtein (edit) distance between two strings.\n * Uses the classic dynamic programming approach with O(min(m,n)) space.\n */\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure a is the shorter string for space optimization\n if (a.length > b.length) [a, b] = [b, a];\n\n const aLen = a.length;\n const bLen = b.length;\n let prev = new Array(aLen + 1);\n let curr = new Array(aLen + 1);\n\n for (let i = 0; i <= aLen; i++) prev[i] = i;\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n for (let i = 1; i <= aLen; i++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[i] = Math.min(\n prev[i] + 1, // deletion\n curr[i - 1] + 1, // insertion\n prev[i - 1] + cost // substitution\n );\n }\n [prev, curr] = [curr, prev];\n }\n\n return prev[aLen];\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\nimport { loadIOC } from '../ioc/index.js';\nimport {\n matchMaliciousHashes,\n matchC2IPs,\n matchTyposquat,\n} from '../ioc/matcher.js';\n\nexport const iocChecks: CheckModule = {\n name: 'IOC Threat Intelligence',\n category: 'SUPPLY',\n\n run(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n const ioc = loadIOC();\n\n // SUPPLY-008: Known malicious file hashes\n const hashMatches = matchMaliciousHashes(skill, ioc);\n for (const match of hashMatches) {\n results.push({\n id: 'SUPPLY-008',\n category: 'SUPPLY',\n severity: 'CRITICAL',\n title: 'Known malicious file hash',\n message: `File \"${match.file}\" matches known malicious hash: ${match.description}`,\n snippet: match.hash,\n });\n }\n\n // SUPPLY-009: Known C2 IP addresses\n const ipMatches = matchC2IPs(skill, ioc);\n for (const match of ipMatches) {\n results.push({\n id: 'SUPPLY-009',\n category: 'SUPPLY',\n severity: 'CRITICAL',\n title: 'Known C2 IP address',\n message: `${match.source}:${match.line}: Contains known C2 server IP: ${match.ip}`,\n line: match.line,\n snippet: match.snippet,\n });\n }\n\n // SUPPLY-010: Typosquat name detection\n const skillName = skill.frontmatter.name;\n if (skillName) {\n const typoMatch = matchTyposquat(skillName, ioc);\n if (typoMatch) {\n if (typoMatch.type === 'known') {\n results.push({\n id: 'SUPPLY-010',\n category: 'SUPPLY',\n severity: 'CRITICAL',\n title: 'Known typosquat name',\n message: `Skill name \"${skillName}\" matches known typosquat pattern \"${typoMatch.target}\".`,\n snippet: skillName,\n });\n } else {\n results.push({\n id: 'SUPPLY-010',\n category: 'SUPPLY',\n severity: 'HIGH',\n title: 'Possible typosquat name',\n message: `Skill name \"${skillName}\" is similar to protected name \"${typoMatch.target}\" (edit distance: ${typoMatch.distance}).`,\n snippet: skillName,\n });\n }\n }\n }\n\n return results;\n },\n};\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { CheckModule, CheckResult, ParsedSkill } from '../types.js';\nimport { structuralChecks } from './structural.js';\nimport { contentChecks } from './content.js';\nimport { injectionChecks } from './injection.js';\nimport { codeSafetyChecks } from './code-safety.js';\nimport { supplyChainChecks } from './supply-chain.js';\nimport { resourceChecks } from './resource.js';\nimport { iocChecks } from './ioc.js';\n\nconst ALL_MODULES: CheckModule[] = [\n structuralChecks,\n contentChecks,\n injectionChecks,\n codeSafetyChecks,\n supplyChainChecks,\n resourceChecks,\n iocChecks,\n];\n\n/**\n * Run all registered check modules against a parsed skill.\n */\nexport function runAllChecks(skill: ParsedSkill): CheckResult[] {\n const results: CheckResult[] = [];\n for (const mod of ALL_MODULES) {\n results.push(...mod.run(skill));\n }\n return results;\n}\n\nexport { ALL_MODULES };\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n// ===== Severity & Scoring =====\n\nexport type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';\n\nexport const SEVERITY_SCORES: Record<Severity, number> = {\n CRITICAL: 25,\n HIGH: 10,\n MEDIUM: 3,\n LOW: 1,\n};\n\nexport type Grade = 'A' | 'B' | 'C' | 'D' | 'F';\n\nexport function computeGrade(score: number): Grade {\n if (score >= 90) return 'A';\n if (score >= 75) return 'B';\n if (score >= 60) return 'C';\n if (score >= 40) return 'D';\n return 'F';\n}\n\n// ===== Check Result =====\n\nexport type CheckCategory =\n | 'STRUCT'\n | 'CONT'\n | 'INJ'\n | 'CODE'\n | 'SUPPLY'\n | 'RES';\n\nexport interface CheckResult {\n id: string; // e.g. \"INJ-001\"\n category: CheckCategory;\n severity: Severity;\n title: string;\n message: string;\n line?: number; // line number in SKILL.md\n snippet?: string; // relevant code snippet\n}\n\n// ===== Parsed Skill =====\n\nexport interface SkillFrontmatter {\n name?: string;\n description?: string;\n version?: string;\n 'allowed-tools'?: string[];\n [key: string]: unknown;\n}\n\nexport interface ParsedSkill {\n /** Path to the skill directory */\n dirPath: string;\n /** Raw SKILL.md content */\n raw: string;\n /** Parsed frontmatter (YAML) */\n frontmatter: SkillFrontmatter;\n /** Whether frontmatter was valid YAML */\n frontmatterValid: boolean;\n /** Body text after frontmatter */\n body: string;\n /** Lines of the body for line-number tracking */\n bodyLines: string[];\n /** Offset: line number where body starts in the raw file */\n bodyStartLine: number;\n /** Other files in the skill directory */\n files: SkillFile[];\n}\n\nexport interface SkillFile {\n path: string; // relative to skill dir\n name: string;\n extension: string;\n sizeBytes: number;\n isBinary: boolean;\n content?: string; // text content if not binary\n}\n\n// ===== Scan Report =====\n\nexport interface ScanReport {\n skillPath: string;\n skillName: string;\n timestamp: string;\n results: CheckResult[];\n score: number;\n grade: Grade;\n summary: {\n total: number;\n critical: number;\n high: number;\n medium: number;\n low: number;\n };\n}\n\n// ===== Check Module Interface =====\n\nexport interface CheckModule {\n name: string;\n category: CheckCategory;\n run(skill: ParsedSkill): CheckResult[];\n}\n\n// ===== Configuration =====\n\nexport type PolicyLevel = 'strict' | 'balanced' | 'permissive';\nexport type HookAction = 'deny' | 'ask' | 'report';\n\nexport interface SkillCheckerConfig {\n policy: PolicyLevel;\n overrides: Record<string, Severity>;\n ignore: string[];\n}\n\nexport const DEFAULT_CONFIG: SkillCheckerConfig = {\n policy: 'balanced',\n overrides: {},\n ignore: [],\n};\n\n/** Maps policy + severity to hook action */\nexport function getHookAction(\n policy: PolicyLevel,\n severity: Severity\n): HookAction {\n const matrix: Record<PolicyLevel, Record<Severity, HookAction>> = {\n strict: {\n CRITICAL: 'deny',\n HIGH: 'deny',\n MEDIUM: 'ask',\n LOW: 'report',\n },\n balanced: {\n CRITICAL: 'deny',\n HIGH: 'ask',\n MEDIUM: 'report',\n LOW: 'report',\n },\n permissive: {\n CRITICAL: 'ask',\n HIGH: 'report',\n MEDIUM: 'report',\n LOW: 'report',\n },\n };\n return matrix[policy][severity];\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport { parseSkill, parseSkillContent } from './parser.js';\nimport { runAllChecks } from './checks/index.js';\nimport type {\n CheckResult,\n ScanReport,\n SkillCheckerConfig,\n Severity,\n ParsedSkill,\n} from './types.js';\nimport { SEVERITY_SCORES, computeGrade, DEFAULT_CONFIG } from './types.js';\n\n/**\n * Scan a skill directory and produce a security report.\n */\nexport function scanSkillDirectory(\n dirPath: string,\n config: SkillCheckerConfig = DEFAULT_CONFIG\n): ScanReport {\n const skill = parseSkill(dirPath);\n return buildReport(skill, config);\n}\n\n/**\n * Scan a single SKILL.md content string.\n */\nexport function scanSkillContent(\n content: string,\n config: SkillCheckerConfig = DEFAULT_CONFIG\n): ScanReport {\n const skill = parseSkillContent(content);\n return buildReport(skill, config);\n}\n\nfunction buildReport(\n skill: ParsedSkill,\n config: SkillCheckerConfig\n): ScanReport {\n // Run all checks\n let results = runAllChecks(skill);\n\n // Apply severity overrides\n results = results.map((r) => {\n if (config.overrides[r.id]) {\n return { ...r, severity: config.overrides[r.id] };\n }\n return r;\n });\n\n // Filter ignored rules\n results = results.filter((r) => !config.ignore.includes(r.id));\n\n // Calculate score\n const score = calculateScore(results);\n const grade = computeGrade(score);\n\n // Build summary\n const summary = {\n total: results.length,\n critical: results.filter((r) => r.severity === 'CRITICAL').length,\n high: results.filter((r) => r.severity === 'HIGH').length,\n medium: results.filter((r) => r.severity === 'MEDIUM').length,\n low: results.filter((r) => r.severity === 'LOW').length,\n };\n\n return {\n skillPath: skill.dirPath,\n skillName: skill.frontmatter.name ?? 'unknown',\n timestamp: new Date().toISOString(),\n results,\n score,\n grade,\n summary,\n };\n}\n\nfunction calculateScore(results: CheckResult[]): number {\n let score = 100;\n for (const r of results) {\n score -= SEVERITY_SCORES[r.severity];\n }\n return Math.max(0, score);\n}\n\n/**\n * Determine the worst severity found in results.\n */\nexport function worstSeverity(results: CheckResult[]): Severity | null {\n const order: Severity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];\n for (const sev of order) {\n if (results.some((r) => r.severity === sev)) return sev;\n }\n return null;\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport chalk from 'chalk';\nimport type { ScanReport, Severity, Grade } from '../types.js';\n\nconst SEVERITY_COLORS: Record<Severity, (s: string) => string> = {\n CRITICAL: chalk.bgRed.white.bold,\n HIGH: chalk.red.bold,\n MEDIUM: chalk.yellow,\n LOW: chalk.gray,\n};\n\nconst GRADE_COLORS: Record<Grade, (s: string) => string> = {\n A: chalk.green.bold,\n B: chalk.cyan.bold,\n C: chalk.yellow.bold,\n D: chalk.red.bold,\n F: chalk.bgRed.white.bold,\n};\n\nconst SEVERITY_ICONS: Record<Severity, string> = {\n CRITICAL: 'X',\n HIGH: '!',\n MEDIUM: '~',\n LOW: '-',\n};\n\nexport function formatTerminalReport(report: ScanReport): string {\n const lines: string[] = [];\n\n // Header\n lines.push('');\n lines.push(\n chalk.bold('Skill Security Report') +\n chalk.gray(` - ${report.skillName}`)\n );\n lines.push(chalk.gray(`Path: ${report.skillPath}`));\n lines.push(chalk.gray(`Time: ${report.timestamp}`));\n lines.push('');\n\n // Score & Grade\n const gradeStr = GRADE_COLORS[report.grade](` ${report.grade} `);\n const scoreStr =\n report.score >= 75\n ? chalk.green(`${report.score}/100`)\n : report.score >= 40\n ? chalk.yellow(`${report.score}/100`)\n : chalk.red(`${report.score}/100`);\n\n lines.push(`Grade: ${gradeStr} Score: ${scoreStr}`);\n lines.push('');\n\n // Summary bar\n const parts: string[] = [];\n if (report.summary.critical > 0)\n parts.push(chalk.bgRed.white(` ${report.summary.critical} CRITICAL `));\n if (report.summary.high > 0)\n parts.push(chalk.red(` ${report.summary.high} HIGH `));\n if (report.summary.medium > 0)\n parts.push(chalk.yellow(` ${report.summary.medium} MEDIUM `));\n if (report.summary.low > 0)\n parts.push(chalk.gray(` ${report.summary.low} LOW `));\n\n if (parts.length > 0) {\n lines.push(`Findings: ${parts.join(' ')}`);\n } else {\n lines.push(chalk.green('No issues found.'));\n }\n lines.push('');\n\n // Findings detail\n if (report.results.length > 0) {\n lines.push(chalk.bold.underline('Findings:'));\n lines.push('');\n\n // Group by category\n const grouped = new Map<string, typeof report.results>();\n for (const r of report.results) {\n const group = grouped.get(r.category) ?? [];\n group.push(r);\n grouped.set(r.category, group);\n }\n\n for (const [category, findings] of grouped) {\n lines.push(chalk.bold(`[${category}]`));\n\n // Sort by severity\n const order: Severity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];\n findings.sort(\n (a, b) => order.indexOf(a.severity) - order.indexOf(b.severity)\n );\n\n for (const f of findings) {\n const icon = SEVERITY_ICONS[f.severity];\n const sevLabel = SEVERITY_COLORS[f.severity](\n ` ${f.severity} `\n );\n const idStr = chalk.gray(f.id);\n lines.push(` [${icon}] ${sevLabel} ${idStr} ${f.title}`);\n lines.push(` ${chalk.gray(f.message)}`);\n if (f.snippet) {\n lines.push(` ${chalk.dim(f.snippet)}`);\n }\n }\n lines.push('');\n }\n }\n\n // Recommendation\n lines.push(chalk.bold('Recommendation:'));\n switch (report.grade) {\n case 'A':\n lines.push(chalk.green(' Safe to install.'));\n break;\n case 'B':\n lines.push(chalk.cyan(' Minor issues found. Generally safe.'));\n break;\n case 'C':\n lines.push(\n chalk.yellow(' Review recommended before installation.')\n );\n break;\n case 'D':\n lines.push(\n chalk.red(' Significant risks detected. Install with caution.')\n );\n break;\n case 'F':\n lines.push(\n chalk.bgRed.white(' DO NOT INSTALL. Critical security issues found.')\n );\n break;\n }\n lines.push('');\n\n return lines.join('\\n');\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport type { ScanReport, SkillCheckerConfig, HookAction } from '../types.js';\nimport { getHookAction, DEFAULT_CONFIG } from '../types.js';\nimport { worstSeverity } from '../scanner.js';\n\n/**\n * Format a scan report as JSON string.\n */\nexport function formatJsonReport(report: ScanReport): string {\n return JSON.stringify(report, null, 2);\n}\n\n/**\n * Generate a hook response JSON for PreToolUse hooks.\n */\nexport interface HookResponse {\n permissionDecision: 'deny' | 'ask' | 'allow';\n reason?: string;\n additionalContext?: string;\n}\n\nexport function generateHookResponse(\n report: ScanReport,\n config: SkillCheckerConfig = DEFAULT_CONFIG\n): HookResponse {\n const worst = worstSeverity(report.results);\n\n if (!worst) {\n return { permissionDecision: 'allow' };\n }\n\n const action: HookAction = getHookAction(config.policy, worst);\n\n switch (action) {\n case 'deny':\n return {\n permissionDecision: 'deny',\n reason: buildDenySummary(report),\n };\n case 'ask':\n return {\n permissionDecision: 'ask',\n reason: buildAskSummary(report),\n };\n case 'report':\n return {\n permissionDecision: 'allow',\n additionalContext: buildReportSummary(report),\n };\n }\n}\n\nfunction buildDenySummary(report: ScanReport): string {\n const lines = [\n `Skill Security Check FAILED (Grade: ${report.grade}, Score: ${report.score}/100)`,\n ];\n const criticals = report.results.filter((r) => r.severity === 'CRITICAL');\n if (criticals.length > 0) {\n lines.push(`Critical issues (${criticals.length}):`);\n for (const c of criticals.slice(0, 5)) {\n lines.push(` - [${c.id}] ${c.title}: ${c.message}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction buildAskSummary(report: ScanReport): string {\n const lines = [\n `Skill Security Check: Grade ${report.grade} (${report.score}/100)`,\n `Found: ${report.summary.critical} critical, ${report.summary.high} high, ${report.summary.medium} medium issues.`,\n 'Review the findings before allowing installation.',\n ];\n return lines.join('\\n');\n}\n\nfunction buildReportSummary(report: ScanReport): string {\n const lines = [\n `[Skill Checker] Grade: ${report.grade} (${report.score}/100)`,\n `Issues: ${report.summary.total} (${report.summary.critical}C/${report.summary.high}H/${report.summary.medium}M/${report.summary.low}L)`,\n ];\n if (report.results.length > 0) {\n lines.push('Top findings:');\n for (const r of report.results.slice(0, 3)) {\n lines.push(` [${r.id}] ${r.severity}: ${r.title}`);\n }\n }\n return lines.join('\\n');\n}\n","// Skill Checker - Security checker for Claude Code skills\n// Copyright (C) 2026 Alexander Jin\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type { SkillCheckerConfig, PolicyLevel, Severity } from './types.js';\nimport { DEFAULT_CONFIG } from './types.js';\n\nconst CONFIG_FILENAMES = [\n '.skillcheckerrc.yaml',\n '.skillcheckerrc.yml',\n '.skillcheckerrc',\n];\n\n/**\n * Load configuration.\n * If configPath is provided and points to a file, load it directly.\n * Otherwise, search up the directory tree from startDir.\n */\nexport function loadConfig(startDir?: string, configPath?: string): SkillCheckerConfig {\n // Direct file path provided via --config\n if (configPath) {\n const absPath = resolve(configPath);\n if (existsSync(absPath)) {\n return parseConfigFile(absPath);\n }\n // Config file specified but not found - return default\n return { ...DEFAULT_CONFIG };\n }\n\n const dir = startDir ? resolve(startDir) : process.cwd();\n\n // Search for config file in current dir and parents\n let current = dir;\n while (true) {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = join(current, filename);\n if (existsSync(configPath)) {\n return parseConfigFile(configPath);\n }\n }\n\n const parent = join(current, '..');\n if (parent === current) break; // reached root\n current = parent;\n }\n\n // Also check home directory\n const home = process.env.HOME ?? process.env.USERPROFILE;\n if (home) {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = join(home, filename);\n if (existsSync(configPath)) {\n return parseConfigFile(configPath);\n }\n }\n }\n\n return { ...DEFAULT_CONFIG };\n}\n\nfunction parseConfigFile(path: string): SkillCheckerConfig {\n try {\n const raw = readFileSync(path, 'utf-8');\n const parsed = parseYaml(raw);\n\n if (!parsed || typeof parsed !== 'object') {\n return { ...DEFAULT_CONFIG };\n }\n\n const config: SkillCheckerConfig = {\n policy: isValidPolicy(parsed.policy) ? parsed.policy : 'balanced',\n overrides: {},\n ignore: [],\n };\n\n // Parse overrides\n if (parsed.overrides && typeof parsed.overrides === 'object') {\n for (const [key, value] of Object.entries(parsed.overrides)) {\n const sev = normalizeSeverity(value as string);\n if (sev) {\n config.overrides[key] = sev;\n }\n }\n }\n\n // Parse ignore list\n if (Array.isArray(parsed.ignore)) {\n config.ignore = parsed.ignore.filter(\n (item: unknown) => typeof item === 'string'\n );\n }\n\n return config;\n } catch {\n return { ...DEFAULT_CONFIG };\n }\n}\n\nfunction isValidPolicy(value: unknown): value is PolicyLevel {\n return (\n typeof value === 'string' &&\n ['strict', 'balanced', 'permissive'].includes(value)\n );\n}\n\nfunction normalizeSeverity(value: string): Severity | null {\n const upper = value?.toUpperCase();\n if (['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].includes(upper)) {\n return upper as Severity;\n }\n return null;\n}\n"],"mappings":"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","names":["BINARY_EXTENSIONS","readFileSync","existsSync","join","join","existsSync","readFileSync","readFileSync","join","join","readFileSync","getAllText","readFileSync","existsSync","join","resolve","parseYaml","resolve","existsSync","configPath","join","readFileSync","parseYaml"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
|
|
2
|
+
declare const SEVERITY_SCORES: Record<Severity, number>;
|
|
3
|
+
type Grade = 'A' | 'B' | 'C' | 'D' | 'F';
|
|
4
|
+
declare function computeGrade(score: number): Grade;
|
|
5
|
+
type CheckCategory = 'STRUCT' | 'CONT' | 'INJ' | 'CODE' | 'SUPPLY' | 'RES';
|
|
6
|
+
interface CheckResult {
|
|
7
|
+
id: string;
|
|
8
|
+
category: CheckCategory;
|
|
9
|
+
severity: Severity;
|
|
10
|
+
title: string;
|
|
11
|
+
message: string;
|
|
12
|
+
line?: number;
|
|
13
|
+
snippet?: string;
|
|
14
|
+
}
|
|
15
|
+
interface SkillFrontmatter {
|
|
16
|
+
name?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
version?: string;
|
|
19
|
+
'allowed-tools'?: string[];
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
interface ParsedSkill {
|
|
23
|
+
/** Path to the skill directory */
|
|
24
|
+
dirPath: string;
|
|
25
|
+
/** Raw SKILL.md content */
|
|
26
|
+
raw: string;
|
|
27
|
+
/** Parsed frontmatter (YAML) */
|
|
28
|
+
frontmatter: SkillFrontmatter;
|
|
29
|
+
/** Whether frontmatter was valid YAML */
|
|
30
|
+
frontmatterValid: boolean;
|
|
31
|
+
/** Body text after frontmatter */
|
|
32
|
+
body: string;
|
|
33
|
+
/** Lines of the body for line-number tracking */
|
|
34
|
+
bodyLines: string[];
|
|
35
|
+
/** Offset: line number where body starts in the raw file */
|
|
36
|
+
bodyStartLine: number;
|
|
37
|
+
/** Other files in the skill directory */
|
|
38
|
+
files: SkillFile[];
|
|
39
|
+
}
|
|
40
|
+
interface SkillFile {
|
|
41
|
+
path: string;
|
|
42
|
+
name: string;
|
|
43
|
+
extension: string;
|
|
44
|
+
sizeBytes: number;
|
|
45
|
+
isBinary: boolean;
|
|
46
|
+
content?: string;
|
|
47
|
+
}
|
|
48
|
+
interface ScanReport {
|
|
49
|
+
skillPath: string;
|
|
50
|
+
skillName: string;
|
|
51
|
+
timestamp: string;
|
|
52
|
+
results: CheckResult[];
|
|
53
|
+
score: number;
|
|
54
|
+
grade: Grade;
|
|
55
|
+
summary: {
|
|
56
|
+
total: number;
|
|
57
|
+
critical: number;
|
|
58
|
+
high: number;
|
|
59
|
+
medium: number;
|
|
60
|
+
low: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
interface CheckModule {
|
|
64
|
+
name: string;
|
|
65
|
+
category: CheckCategory;
|
|
66
|
+
run(skill: ParsedSkill): CheckResult[];
|
|
67
|
+
}
|
|
68
|
+
type PolicyLevel = 'strict' | 'balanced' | 'permissive';
|
|
69
|
+
type HookAction = 'deny' | 'ask' | 'report';
|
|
70
|
+
interface SkillCheckerConfig {
|
|
71
|
+
policy: PolicyLevel;
|
|
72
|
+
overrides: Record<string, Severity>;
|
|
73
|
+
ignore: string[];
|
|
74
|
+
}
|
|
75
|
+
declare const DEFAULT_CONFIG: SkillCheckerConfig;
|
|
76
|
+
/** Maps policy + severity to hook action */
|
|
77
|
+
declare function getHookAction(policy: PolicyLevel, severity: Severity): HookAction;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scan a skill directory and produce a security report.
|
|
81
|
+
*/
|
|
82
|
+
declare function scanSkillDirectory(dirPath: string, config?: SkillCheckerConfig): ScanReport;
|
|
83
|
+
/**
|
|
84
|
+
* Scan a single SKILL.md content string.
|
|
85
|
+
*/
|
|
86
|
+
declare function scanSkillContent(content: string, config?: SkillCheckerConfig): ScanReport;
|
|
87
|
+
/**
|
|
88
|
+
* Determine the worst severity found in results.
|
|
89
|
+
*/
|
|
90
|
+
declare function worstSeverity(results: CheckResult[]): Severity | null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse a skill directory, reading SKILL.md and enumerating files.
|
|
94
|
+
*/
|
|
95
|
+
declare function parseSkill(dirPath: string): ParsedSkill;
|
|
96
|
+
/**
|
|
97
|
+
* Parse a single SKILL.md content string (without directory enumeration).
|
|
98
|
+
* Useful for testing or when you only have the file content.
|
|
99
|
+
*/
|
|
100
|
+
declare function parseSkillContent(content: string, dirPath?: string): ParsedSkill;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run all registered check modules against a parsed skill.
|
|
104
|
+
*/
|
|
105
|
+
declare function runAllChecks(skill: ParsedSkill): CheckResult[];
|
|
106
|
+
|
|
107
|
+
declare function formatTerminalReport(report: ScanReport): string;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Format a scan report as JSON string.
|
|
111
|
+
*/
|
|
112
|
+
declare function formatJsonReport(report: ScanReport): string;
|
|
113
|
+
/**
|
|
114
|
+
* Generate a hook response JSON for PreToolUse hooks.
|
|
115
|
+
*/
|
|
116
|
+
interface HookResponse {
|
|
117
|
+
permissionDecision: 'deny' | 'ask' | 'allow';
|
|
118
|
+
reason?: string;
|
|
119
|
+
additionalContext?: string;
|
|
120
|
+
}
|
|
121
|
+
declare function generateHookResponse(report: ScanReport, config?: SkillCheckerConfig): HookResponse;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Load configuration.
|
|
125
|
+
* If configPath is provided and points to a file, load it directly.
|
|
126
|
+
* Otherwise, search up the directory tree from startDir.
|
|
127
|
+
*/
|
|
128
|
+
declare function loadConfig(startDir?: string, configPath?: string): SkillCheckerConfig;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* IOC (Indicators of Compromise) database type definition and seed data.
|
|
132
|
+
* Seed data is compiled from publicly available threat intelligence.
|
|
133
|
+
*/
|
|
134
|
+
interface IOCDatabase {
|
|
135
|
+
version: string;
|
|
136
|
+
updated: string;
|
|
137
|
+
c2_ips: string[];
|
|
138
|
+
malicious_hashes: Record<string, string>;
|
|
139
|
+
malicious_domains: string[];
|
|
140
|
+
typosquat: {
|
|
141
|
+
known_patterns: string[];
|
|
142
|
+
protected_names: string[];
|
|
143
|
+
};
|
|
144
|
+
malicious_publishers: string[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Load IOC database: embedded seed data + optional external override file.
|
|
149
|
+
* External file is merged (appended) into the seed data, not replacing it.
|
|
150
|
+
*
|
|
151
|
+
* Search paths for override file:
|
|
152
|
+
* 1. ~/.config/skill-checker/ioc-override.json
|
|
153
|
+
*/
|
|
154
|
+
declare function loadIOC(): IOCDatabase;
|
|
155
|
+
/**
|
|
156
|
+
* Reset the cached IOC database (useful for testing).
|
|
157
|
+
*/
|
|
158
|
+
declare function resetIOCCache(): void;
|
|
159
|
+
|
|
160
|
+
export { type CheckCategory, type CheckModule, type CheckResult, DEFAULT_CONFIG, type Grade, type HookAction, type ParsedSkill, type PolicyLevel, SEVERITY_SCORES, type ScanReport, type Severity, type SkillCheckerConfig, type SkillFile, type SkillFrontmatter, computeGrade, formatJsonReport, formatTerminalReport, generateHookResponse, getHookAction, loadConfig, loadIOC, parseSkill, parseSkillContent, resetIOCCache, runAllChecks, scanSkillContent, scanSkillDirectory, worstSeverity };
|