reskill 1.15.0 → 1.16.0-beta.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.
@@ -1 +1 @@
1
- {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/publish.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkDpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAcR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAYpE;AA2ND;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI1D;AAoBD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAyBnE;AAwQD,eAAO,MAAM,cAAc,SAYH,CAAC;AAEzB,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/publish.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmDpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAcR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAYpE;AA2ND;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI1D;AAoBD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAyBnE;AA+SD,eAAO,MAAM,cAAc,SAYH,CAAC;AAEzB,eAAe,cAAc,CAAC"}
package/dist/cli/index.js CHANGED
@@ -8073,6 +8073,344 @@ class SkillValidator {
8073
8073
  return `sha256-${hash.digest('hex')}`;
8074
8074
  }
8075
8075
  }
8076
+ /**
8077
+ * ContentScanner - Detect malicious patterns in SKILL.md content
8078
+ *
8079
+ * Features:
8080
+ * - Context-aware: skips safe zones (frontmatter, code blocks, quotes, blockquotes)
8081
+ * - 6 built-in detection rules across 3 risk levels
8082
+ * - Configurable: override levels, disable rules, add custom rules
8083
+ * - Pure string operations in scan() — no fs dependency, suitable for server use
8084
+ * - scanFile() convenience method for CLI use
8085
+ */ // ============================================================================
8086
+ // Safe Zone Masking
8087
+ // ============================================================================
8088
+ /**
8089
+ * Mask safe zones in Markdown content with spaces, preserving line structure.
8090
+ *
8091
+ * Safe zones (content replaced with spaces):
8092
+ * - YAML frontmatter (`---` ... `---` at file start)
8093
+ * - Fenced code blocks (``` or ~~~)
8094
+ * - Indented code blocks (4 spaces / tab after blank line)
8095
+ * - Blockquotes (`> ` prefix)
8096
+ * - Inline code (`` `...` ``)
8097
+ * - Double-quoted text (`"..."`, min 3 chars between quotes)
8098
+ *
8099
+ * Line breaks are preserved so line numbers remain correct.
8100
+ */ function maskSafeZones(content) {
8101
+ const lines = content.split('\n');
8102
+ const result = [];
8103
+ let inFrontmatter = false;
8104
+ let inFencedCode = false;
8105
+ let fenceChar = '';
8106
+ let fenceLength = 0;
8107
+ let prevLineBlank = false;
8108
+ let prevLineIndentedCode = false;
8109
+ for(let i = 0; i < lines.length; i++){
8110
+ const line = lines[i];
8111
+ // --- YAML Frontmatter (only at file start) ---
8112
+ if (0 === i && '---' === line.trim()) {
8113
+ inFrontmatter = true;
8114
+ result.push(maskLine(line));
8115
+ continue;
8116
+ }
8117
+ if (inFrontmatter) {
8118
+ result.push(maskLine(line));
8119
+ if ('---' === line.trim()) inFrontmatter = false;
8120
+ continue;
8121
+ }
8122
+ // --- Fenced code blocks (``` or ~~~) ---
8123
+ const fenceMatch = line.match(/^(`{3,}|~{3,})/);
8124
+ if (!inFencedCode && fenceMatch) {
8125
+ inFencedCode = true;
8126
+ fenceChar = fenceMatch[1][0];
8127
+ fenceLength = fenceMatch[1].length;
8128
+ result.push(maskLine(line));
8129
+ prevLineBlank = false;
8130
+ prevLineIndentedCode = false;
8131
+ continue;
8132
+ }
8133
+ if (inFencedCode) {
8134
+ result.push(maskLine(line));
8135
+ const closeMatch = line.match(/^(`{3,}|~{3,})\s*$/);
8136
+ if (closeMatch && closeMatch[1][0] === fenceChar && closeMatch[1].length >= fenceLength) inFencedCode = false;
8137
+ prevLineBlank = false;
8138
+ prevLineIndentedCode = false;
8139
+ continue;
8140
+ }
8141
+ // --- Blockquote ---
8142
+ if (/^>\s?/.test(line)) {
8143
+ result.push(maskLine(line));
8144
+ prevLineBlank = false;
8145
+ prevLineIndentedCode = false;
8146
+ continue;
8147
+ }
8148
+ // --- Indented code block (4 spaces or tab, after blank line) ---
8149
+ if (/^(?: |\t)/.test(line) && (prevLineBlank || prevLineIndentedCode)) {
8150
+ result.push(maskLine(line));
8151
+ prevLineBlank = false;
8152
+ prevLineIndentedCode = true;
8153
+ continue;
8154
+ }
8155
+ // --- Normal line: mask inline code and double-quoted text ---
8156
+ result.push(maskInline(line));
8157
+ prevLineBlank = '' === line.trim();
8158
+ prevLineIndentedCode = false;
8159
+ }
8160
+ return result.join('\n');
8161
+ }
8162
+ /** Replace all characters in a line with spaces (preserving length) */ function maskLine(line) {
8163
+ return ' '.repeat(line.length);
8164
+ }
8165
+ /**
8166
+ * Mask inline code (`` `...` ``) and double-quoted text (`"..."`) within a line.
8167
+ * Uses regex replacement for efficiency (avoids char-by-char concatenation on long lines).
8168
+ * Single quotes are NOT masked to avoid false matches with apostrophes.
8169
+ */ function maskInline(line) {
8170
+ let result = line;
8171
+ // Inline code: `...`
8172
+ result = result.replace(/`[^`]+`/g, (m)=>' '.repeat(m.length));
8173
+ // Double-quoted text: "..." (min 3 chars between quotes)
8174
+ result = result.replace(/"[^"]{3,}"/g, (m)=>' '.repeat(m.length));
8175
+ return result;
8176
+ }
8177
+ // ============================================================================
8178
+ // Rule Helpers
8179
+ // ============================================================================
8180
+ /** Find lines matching any of the given patterns, return one match per line */ function findLineMatches(content, patterns) {
8181
+ const lines = content.split('\n');
8182
+ const matches = [];
8183
+ for(let i = 0; i < lines.length; i++)for (const pattern of patterns)if (pattern.test(lines[i])) {
8184
+ matches.push({
8185
+ line: i + 1
8186
+ });
8187
+ break;
8188
+ }
8189
+ return matches;
8190
+ }
8191
+ // ============================================================================
8192
+ // Default Rules
8193
+ // ============================================================================
8194
+ const SNIPPET_MAX_LENGTH = 120;
8195
+ /** Built-in detection rules */ const DEFAULT_RULES = [
8196
+ // Rule 1: Prompt Injection (high)
8197
+ {
8198
+ id: 'prompt-injection',
8199
+ level: 'high',
8200
+ message: 'Detected prompt injection attempt',
8201
+ skipSafeZones: true,
8202
+ check: (content)=>findLineMatches(content, [
8203
+ /ignore\s+(all\s+)?previous\s+instructions/i,
8204
+ /disregard\s+(all\s+)?(prior|previous|above)\s+(instructions|rules|context)/i,
8205
+ /you\s+are\s+now\s+/i,
8206
+ /from\s+now\s+on[,\s]+you\s+are/i,
8207
+ /new\s+system\s+prompt/i,
8208
+ /override\s+(your|the)\s+(system|safety|security)\s+(prompt|rules|instructions)/i,
8209
+ /forget\s+(?:all\s+)?(?:your\s+)?(?:previous\s+|prior\s+)?(?:instructions|rules|constraints)/i,
8210
+ /entering\s+(a\s+)?new\s+(mode|context|session)/i
8211
+ ])
8212
+ },
8213
+ // Rule 2: Data Exfiltration (high)
8214
+ {
8215
+ id: 'data-exfiltration',
8216
+ level: 'high',
8217
+ message: 'Detected potential data exfiltration command',
8218
+ skipSafeZones: true,
8219
+ check: (content)=>{
8220
+ const lines = content.split('\n');
8221
+ const matches = [];
8222
+ const commandPattern = /\b(curl|wget|fetch|http\.post|requests\.post|nc\b|ncat|netcat)\b/i;
8223
+ const sensitivePattern = /(\$[A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*|\$ENV\b|\$\{[^}]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[^}]*\})/i;
8224
+ for(let i = 0; i < lines.length; i++)if (commandPattern.test(lines[i]) && sensitivePattern.test(lines[i])) matches.push({
8225
+ line: i + 1
8226
+ });
8227
+ return matches;
8228
+ }
8229
+ },
8230
+ // Rule 3: Content Obfuscation (high) — scans ALL content including safe zones
8231
+ {
8232
+ id: 'obfuscation',
8233
+ level: 'high',
8234
+ message: 'Detected content obfuscation',
8235
+ skipSafeZones: false,
8236
+ check: (content)=>{
8237
+ const matches = [];
8238
+ const lines = content.split('\n');
8239
+ // Zero-width characters (suspicious in any context)
8240
+ const zeroWidthPattern = /[\u200B\u200C\u200D\uFEFF\u2060\u180E]/;
8241
+ for(let i = 0; i < lines.length; i++)if (zeroWidthPattern.test(lines[i])) matches.push({
8242
+ line: i + 1,
8243
+ snippet: 'Zero-width Unicode characters detected'
8244
+ });
8245
+ // Long base64-like strings (>200 continuous chars)
8246
+ const base64Pattern = /[A-Za-z0-9+/=]{200,}/;
8247
+ for(let i = 0; i < lines.length; i++)if (base64Pattern.test(lines[i])) matches.push({
8248
+ line: i + 1,
8249
+ snippet: 'Suspicious base64-encoded block detected'
8250
+ });
8251
+ // Large HTML comments (>200 chars of content)
8252
+ const commentRegex = /<!--([\s\S]{200,}?)-->/g;
8253
+ let match;
8254
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex exec loop
8255
+ while(null !== (match = commentRegex.exec(content))){
8256
+ const lineNum = content.slice(0, match.index).split('\n').length;
8257
+ matches.push({
8258
+ line: lineNum,
8259
+ snippet: `Large HTML comment block (${match[1].length} chars)`
8260
+ });
8261
+ }
8262
+ return matches;
8263
+ }
8264
+ },
8265
+ // Rule 4: Sensitive File Access (medium)
8266
+ {
8267
+ id: 'sensitive-file-access',
8268
+ level: 'medium',
8269
+ message: 'References sensitive file path',
8270
+ skipSafeZones: true,
8271
+ check: (content)=>findLineMatches(content, [
8272
+ /~\/\.ssh\b/,
8273
+ /~\/\.aws\b/,
8274
+ /~\/\.gnupg\b/,
8275
+ /~\/\.config\/gcloud\b/,
8276
+ /\bid_rsa\b/i,
8277
+ /\bid_ed25519\b/i,
8278
+ /\/etc\/passwd\b/,
8279
+ /\/etc\/shadow\b/,
8280
+ /\.env\b(?!\.\w)/
8281
+ ])
8282
+ },
8283
+ // Rule 5: Stealth Instructions (medium) — phrase + action verb matching
8284
+ {
8285
+ id: 'stealth-instructions',
8286
+ level: 'medium',
8287
+ message: 'Detected instruction to hide actions from user',
8288
+ skipSafeZones: true,
8289
+ check: (content)=>{
8290
+ const actionVerbs = 'execute|delete|remove|send|transmit|modify|overwrite|install|download|upload|run|write|create|destroy|drop';
8291
+ const patterns = [
8292
+ new RegExp(`silently\\s+(?:${actionVerbs})`, 'i'),
8293
+ new RegExp(`without\\s+telling\\s+the\\s+user.{0,30}(?:${actionVerbs})`, 'i'),
8294
+ new RegExp("(?:do\\s+not|don'?t)\\s+show\\s+.{0,40}(?:to\\s+the\\s+user|to\\s+user)", 'i'),
8295
+ new RegExp("hide\\s+(?:this|the|these|all)\\s+.{0,30}(?:from\\s+the\\s+user|from\\s+user)", 'i'),
8296
+ new RegExp("(?:do\\s+not|don'?t)\\s+mention\\s+.{0,30}(?:to\\s+the\\s+user|to\\s+user)", 'i'),
8297
+ new RegExp("keep\\s+(?:this|it)\\s+(?:a\\s+)?secret\\s+from\\s+(?:the\\s+)?user", 'i')
8298
+ ];
8299
+ // Safe patterns to exclude (common in legitimate DevOps/automation skills)
8300
+ const safePatterns = [
8301
+ /silently\s+(?:ignore|skip|fail|discard|suppress|continue|pass|drop|swallow)/i
8302
+ ];
8303
+ const lines = content.split('\n');
8304
+ const matches = [];
8305
+ for(let i = 0; i < lines.length; i++){
8306
+ const line = lines[i];
8307
+ if (!safePatterns.some((p)=>p.test(line))) {
8308
+ for (const pattern of patterns)if (pattern.test(line)) {
8309
+ matches.push({
8310
+ line: i + 1
8311
+ });
8312
+ break;
8313
+ }
8314
+ }
8315
+ }
8316
+ return matches;
8317
+ }
8318
+ },
8319
+ // Rule 6: Oversized Content (low) — scans ALL content
8320
+ {
8321
+ id: 'oversized-content',
8322
+ level: 'low',
8323
+ message: 'Content exceeds recommended size limit',
8324
+ skipSafeZones: false,
8325
+ check: (content)=>{
8326
+ const MAX_SIZE_BYTES = 51200;
8327
+ const sizeBytes = Buffer.byteLength(content, 'utf-8');
8328
+ if (sizeBytes > MAX_SIZE_BYTES) return [
8329
+ {
8330
+ snippet: `Content size: ${(sizeBytes / 1024).toFixed(1)}KB (limit: 50KB)`
8331
+ }
8332
+ ];
8333
+ return [];
8334
+ }
8335
+ }
8336
+ ];
8337
+ // ============================================================================
8338
+ // ContentScanner
8339
+ // ============================================================================
8340
+ /** Build the effective rule set from defaults + options */ function buildRuleSet(options) {
8341
+ let rules = DEFAULT_RULES.map((r)=>({
8342
+ ...r
8343
+ }));
8344
+ if (options?.disabledRules?.length) {
8345
+ const disabled = new Set(options.disabledRules);
8346
+ rules = rules.filter((r)=>!disabled.has(r.id));
8347
+ }
8348
+ if (options?.overrides) for (const rule of rules){
8349
+ const override = options.overrides[rule.id];
8350
+ if (override) rule.level = override;
8351
+ }
8352
+ if (options?.customRules?.length) rules.push(...options.customRules);
8353
+ return rules;
8354
+ }
8355
+ /**
8356
+ * Content scanner for SKILL.md files.
8357
+ *
8358
+ * Detects prompt injection, data exfiltration, obfuscation, sensitive file
8359
+ * access, stealth instructions, and oversized content.
8360
+ *
8361
+ * @example
8362
+ * ```typescript
8363
+ * // Default usage (CLI)
8364
+ * const scanner = new ContentScanner();
8365
+ * const result = scanner.scan(content);
8366
+ *
8367
+ * // Custom usage (private registry server)
8368
+ * const scanner = new ContentScanner({
8369
+ * overrides: { 'prompt-injection': 'medium' },
8370
+ * disabledRules: ['stealth-instructions'],
8371
+ * });
8372
+ * ```
8373
+ */ class ContentScanner {
8374
+ rules;
8375
+ constructor(options){
8376
+ this.rules = buildRuleSet(options);
8377
+ }
8378
+ /**
8379
+ * Scan content string for malicious patterns.
8380
+ * Pure string operation — no file system access.
8381
+ */ scan(content) {
8382
+ const originalLines = content.split('\n');
8383
+ const maskedContent = maskSafeZones(content);
8384
+ const findings = [];
8385
+ for (const rule of this.rules){
8386
+ const targetContent = rule.skipSafeZones ? maskedContent : content;
8387
+ const matches = rule.check(targetContent);
8388
+ for (const match of matches){
8389
+ // Use custom snippet if provided, otherwise generate from original content
8390
+ const snippet = match.snippet ?? (null != match.line ? originalLines[match.line - 1]?.trim().slice(0, SNIPPET_MAX_LENGTH) : void 0);
8391
+ findings.push({
8392
+ rule: rule.id,
8393
+ level: rule.level,
8394
+ message: rule.message,
8395
+ line: match.line,
8396
+ snippet
8397
+ });
8398
+ }
8399
+ }
8400
+ const hasHighRisk = findings.some((f)=>'high' === f.level);
8401
+ return {
8402
+ passed: !hasHighRisk,
8403
+ findings
8404
+ };
8405
+ }
8406
+ /**
8407
+ * Scan a file for malicious patterns.
8408
+ * Convenience wrapper that reads the file then calls scan().
8409
+ */ scanFile(filePath) {
8410
+ const content = external_node_fs_.readFileSync(filePath, 'utf-8');
8411
+ return this.scan(content);
8412
+ }
8413
+ }
8076
8414
  /**
8077
8415
  * publish command - Publish a skill to the registry
8078
8416
  *
@@ -8372,6 +8710,25 @@ class SkillValidator {
8372
8710
  logger_logger.newline();
8373
8711
  logger_logger.log('No changes made (--dry-run)');
8374
8712
  }
8713
+ /**
8714
+ * Display content scan findings
8715
+ */ function displayScanFindings(scanResult) {
8716
+ if (0 === scanResult.findings.length) {
8717
+ logger_logger.log(' ✓ Content security scan passed');
8718
+ return;
8719
+ }
8720
+ logger_logger.newline();
8721
+ logger_logger.log('⚠ Content Security Scan:');
8722
+ logger_logger.newline();
8723
+ for (const finding of scanResult.findings){
8724
+ const levelTag = finding.level.toUpperCase().padEnd(6);
8725
+ const lineInfo = finding.line ? ` (line ${finding.line})` : '';
8726
+ logger_logger.log(` ${levelTag} ${finding.rule}${lineInfo}`);
8727
+ logger_logger.log(` ${finding.message}`);
8728
+ if (finding.snippet) logger_logger.log(` > ${finding.snippet}`);
8729
+ logger_logger.newline();
8730
+ }
8731
+ }
8375
8732
  // ============================================================================
8376
8733
  // Main Action
8377
8734
  // ============================================================================
@@ -8414,6 +8771,18 @@ async function publishAction(skillPath, options) {
8414
8771
  }
8415
8772
  // 3. Validate
8416
8773
  const validation = validator.validate(absolutePath);
8774
+ // 3.5. Content security scan
8775
+ const skillMdPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(absolutePath, 'SKILL.md');
8776
+ if (external_node_fs_.existsSync(skillMdPath)) {
8777
+ const scanner = new ContentScanner();
8778
+ const scanResult = scanner.scanFile(skillMdPath);
8779
+ displayScanFindings(scanResult);
8780
+ if (!scanResult.passed) {
8781
+ logger_logger.newline();
8782
+ logger_logger.error('Content security scan failed. Fix the issues above before publishing.');
8783
+ process.exit(1);
8784
+ }
8785
+ }
8417
8786
  // 4. Get git info
8418
8787
  let gitInfo;
8419
8788
  try {
@@ -0,0 +1,113 @@
1
+ /**
2
+ * ContentScanner - Detect malicious patterns in SKILL.md content
3
+ *
4
+ * Features:
5
+ * - Context-aware: skips safe zones (frontmatter, code blocks, quotes, blockquotes)
6
+ * - 6 built-in detection rules across 3 risk levels
7
+ * - Configurable: override levels, disable rules, add custom rules
8
+ * - Pure string operations in scan() — no fs dependency, suitable for server use
9
+ * - scanFile() convenience method for CLI use
10
+ */
11
+ export type RiskLevel = 'high' | 'medium' | 'low';
12
+ export interface ScanFinding {
13
+ /** Rule ID that triggered this finding */
14
+ rule: string;
15
+ /** Risk level */
16
+ level: RiskLevel;
17
+ /** Human-readable description */
18
+ message: string;
19
+ /** Line number in the original content (1-based) */
20
+ line?: number;
21
+ /** Content snippet (truncated) */
22
+ snippet?: string;
23
+ }
24
+ export interface ScanResult {
25
+ /** false if any high-risk finding exists */
26
+ passed: boolean;
27
+ /** All findings across all rules */
28
+ findings: ScanFinding[];
29
+ }
30
+ export interface ScanRuleMatch {
31
+ /** Line number (1-based) */
32
+ line?: number;
33
+ /** Optional custom snippet (if omitted, scanner generates from original content) */
34
+ snippet?: string;
35
+ }
36
+ export interface ScanRule {
37
+ /** Unique rule identifier */
38
+ id: string;
39
+ /** Risk level */
40
+ level: RiskLevel;
41
+ /** Description shown when rule triggers */
42
+ message: string;
43
+ /** Whether to skip safe zones (code blocks, quotes, etc.) when scanning */
44
+ skipSafeZones: boolean;
45
+ /** Detection function — receives content string (masked if skipSafeZones) */
46
+ check: (content: string) => ScanRuleMatch[];
47
+ }
48
+ export interface ScannerOptions {
49
+ /** Override risk levels for specific rules */
50
+ overrides?: Record<string, RiskLevel>;
51
+ /** Disable specific rules by ID */
52
+ disabledRules?: string[];
53
+ /** Add custom detection rules */
54
+ customRules?: ScanRule[];
55
+ }
56
+ /**
57
+ * Mask safe zones in Markdown content with spaces, preserving line structure.
58
+ *
59
+ * Safe zones (content replaced with spaces):
60
+ * - YAML frontmatter (`---` ... `---` at file start)
61
+ * - Fenced code blocks (``` or ~~~)
62
+ * - Indented code blocks (4 spaces / tab after blank line)
63
+ * - Blockquotes (`> ` prefix)
64
+ * - Inline code (`` `...` ``)
65
+ * - Double-quoted text (`"..."`, min 3 chars between quotes)
66
+ *
67
+ * Line breaks are preserved so line numbers remain correct.
68
+ */
69
+ export declare function maskSafeZones(content: string): string;
70
+ /** Built-in detection rules */
71
+ export declare const DEFAULT_RULES: readonly ScanRule[];
72
+ /**
73
+ * Content scanner for SKILL.md files.
74
+ *
75
+ * Detects prompt injection, data exfiltration, obfuscation, sensitive file
76
+ * access, stealth instructions, and oversized content.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // Default usage (CLI)
81
+ * const scanner = new ContentScanner();
82
+ * const result = scanner.scan(content);
83
+ *
84
+ * // Custom usage (private registry server)
85
+ * const scanner = new ContentScanner({
86
+ * overrides: { 'prompt-injection': 'medium' },
87
+ * disabledRules: ['stealth-instructions'],
88
+ * });
89
+ * ```
90
+ */
91
+ export declare class ContentScanner {
92
+ private rules;
93
+ constructor(options?: ScannerOptions);
94
+ /**
95
+ * Scan content string for malicious patterns.
96
+ * Pure string operation — no file system access.
97
+ */
98
+ scan(content: string): ScanResult;
99
+ /**
100
+ * Scan a file for malicious patterns.
101
+ * Convenience wrapper that reads the file then calls scan().
102
+ */
103
+ scanFile(filePath: string): ScanResult;
104
+ }
105
+ /**
106
+ * Error thrown when content scanning detects high-risk findings.
107
+ * Carries the full findings array for display purposes.
108
+ */
109
+ export declare class ContentScanError extends Error {
110
+ readonly findings: ScanFinding[];
111
+ constructor(findings: ScanFinding[]);
112
+ }
113
+ //# sourceMappingURL=content-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-scanner.d.ts","sourceRoot":"","sources":["../../src/core/content-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,MAAM,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,iBAAiB;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,aAAa,EAAE,OAAO,CAAC;IACvB,6EAA6E;IAC7E,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,aAAa,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,iCAAiC;IACjC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC1B;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA6ErD;AAmDD,+BAA+B;AAC/B,eAAO,MAAM,aAAa,EAAE,SAAS,QAAQ,EA6L5C,CAAC;AA+BF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAa;gBAEd,OAAO,CAAC,EAAE,cAAc;IAIpC;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAoCjC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU;CAIvC;AAMD;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;gBAErB,QAAQ,EAAE,WAAW,EAAE;CAQpC"}
@@ -1,4 +1,6 @@
1
1
  export type { AgentConfig, AgentType } from './agent-registry.js';
2
+ export type { RiskLevel, ScanFinding, ScannerOptions, ScanResult, ScanRule, ScanRuleMatch, } from './content-scanner.js';
3
+ export { ContentScanError, ContentScanner, DEFAULT_RULES, maskSafeZones } from './content-scanner.js';
2
4
  export { agents, detectInstalledAgents, getAgentConfig, getAgentSkillsDir, getAllAgentTypes, isValidAgentType, } from './agent-registry.js';
3
5
  export type { RegistryAuth, ReskillConfig } from './auth-manager.js';
4
6
  export { AuthManager } from './auth-manager.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACtE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,cAAc,oBAAoB,EAAE,kBAAkB,CAAC;AAC7F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEzD,YAAY,EACV,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE,YAAY,EACV,SAAS,EACT,WAAW,EACX,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,GACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEtG,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACtE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,cAAc,oBAAoB,EAAE,kBAAkB,CAAC;AAC7F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEzD,YAAY,EACV,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  * Git-based skills management for AI agents
5
5
  * Supports 17+ coding agents: Cursor, Claude Code, GitHub Copilot, etc.
6
6
  */
7
- export { agents, CacheManager, ConfigLoader, DEFAULT_REGISTRIES, detectInstalledAgents, GitResolver, generateSkillMd, getAgentConfig, getAgentSkillsDir, getAllAgentTypes, HttpResolver, hasValidSkillMd, Installer, isValidAgentType, LockManager, parseSkillFromDir, parseSkillMd, parseSkillMdFile, SkillManager, SkillValidationError, validateSkillDescription, validateSkillName, } from './core/index.js';
8
- export type { AgentConfig, AgentType, InstalledSkill, InstallMode, InstallOptions, InstallResult, ListOptions, LockedSkill, ParsedSkill, ParsedSkillRef, ParsedVersion, SkillJson, SkillMdFrontmatter, SkillsJson, SkillsLock, UpdateOptions, VersionType, } from './types/index.js';
7
+ export { agents, CacheManager, ConfigLoader, ContentScanError, ContentScanner, DEFAULT_REGISTRIES, DEFAULT_RULES, detectInstalledAgents, GitResolver, generateSkillMd, getAgentConfig, getAgentSkillsDir, getAllAgentTypes, HttpResolver, hasValidSkillMd, Installer, isValidAgentType, LockManager, maskSafeZones, parseSkillFromDir, parseSkillMd, parseSkillMdFile, SkillManager, SkillValidationError, validateSkillDescription, validateSkillName, } from './core/index.js';
8
+ export type { AgentConfig, AgentType, InstalledSkill, InstallMode, InstallOptions, InstallResult, ListOptions, LockedSkill, ParsedSkill, ParsedSkillRef, ParsedVersion, RiskLevel, ScanFinding, ScannerOptions, ScanResult, ScanRule, ScanRuleMatch, SkillJson, SkillMdFrontmatter, SkillsJson, SkillsLock, UpdateOptions, VersionType, } from './types/index.js';
9
9
  export { getCanonicalSkillPath, getCanonicalSkillsDir, isPathSafe, sanitizeName, shortenPath, } from './utils/fs.js';
10
10
  export { logger } from './utils/index.js';
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAEhB,YAAY,EACZ,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EAEjB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACV,WAAW,EAEX,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,SAAS,EACT,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,aAAa,EACb,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,MAAM,EACN,YAAY,EACZ,YAAY,EAEZ,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,qBAAqB,EACrB,WAAW,EACX,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAEhB,YAAY,EACZ,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,iBAAiB,EAEjB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACV,WAAW,EAEX,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EAEb,SAAS,EACT,WAAW,EACX,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,EACb,SAAS,EACT,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,aAAa,EACb,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -32,6 +32,359 @@ function __webpack_require__(moduleId) {
32
32
  }
33
33
  /************************************************************************/ // EXTERNAL MODULE: external "node:fs"
34
34
  var external_node_fs_ = __webpack_require__("node:fs");
35
+ /**
36
+ * ContentScanner - Detect malicious patterns in SKILL.md content
37
+ *
38
+ * Features:
39
+ * - Context-aware: skips safe zones (frontmatter, code blocks, quotes, blockquotes)
40
+ * - 6 built-in detection rules across 3 risk levels
41
+ * - Configurable: override levels, disable rules, add custom rules
42
+ * - Pure string operations in scan() — no fs dependency, suitable for server use
43
+ * - scanFile() convenience method for CLI use
44
+ */ // ============================================================================
45
+ // Safe Zone Masking
46
+ // ============================================================================
47
+ /**
48
+ * Mask safe zones in Markdown content with spaces, preserving line structure.
49
+ *
50
+ * Safe zones (content replaced with spaces):
51
+ * - YAML frontmatter (`---` ... `---` at file start)
52
+ * - Fenced code blocks (``` or ~~~)
53
+ * - Indented code blocks (4 spaces / tab after blank line)
54
+ * - Blockquotes (`> ` prefix)
55
+ * - Inline code (`` `...` ``)
56
+ * - Double-quoted text (`"..."`, min 3 chars between quotes)
57
+ *
58
+ * Line breaks are preserved so line numbers remain correct.
59
+ */ function maskSafeZones(content) {
60
+ const lines = content.split('\n');
61
+ const result = [];
62
+ let inFrontmatter = false;
63
+ let inFencedCode = false;
64
+ let fenceChar = '';
65
+ let fenceLength = 0;
66
+ let prevLineBlank = false;
67
+ let prevLineIndentedCode = false;
68
+ for(let i = 0; i < lines.length; i++){
69
+ const line = lines[i];
70
+ // --- YAML Frontmatter (only at file start) ---
71
+ if (0 === i && '---' === line.trim()) {
72
+ inFrontmatter = true;
73
+ result.push(maskLine(line));
74
+ continue;
75
+ }
76
+ if (inFrontmatter) {
77
+ result.push(maskLine(line));
78
+ if ('---' === line.trim()) inFrontmatter = false;
79
+ continue;
80
+ }
81
+ // --- Fenced code blocks (``` or ~~~) ---
82
+ const fenceMatch = line.match(/^(`{3,}|~{3,})/);
83
+ if (!inFencedCode && fenceMatch) {
84
+ inFencedCode = true;
85
+ fenceChar = fenceMatch[1][0];
86
+ fenceLength = fenceMatch[1].length;
87
+ result.push(maskLine(line));
88
+ prevLineBlank = false;
89
+ prevLineIndentedCode = false;
90
+ continue;
91
+ }
92
+ if (inFencedCode) {
93
+ result.push(maskLine(line));
94
+ const closeMatch = line.match(/^(`{3,}|~{3,})\s*$/);
95
+ if (closeMatch && closeMatch[1][0] === fenceChar && closeMatch[1].length >= fenceLength) inFencedCode = false;
96
+ prevLineBlank = false;
97
+ prevLineIndentedCode = false;
98
+ continue;
99
+ }
100
+ // --- Blockquote ---
101
+ if (/^>\s?/.test(line)) {
102
+ result.push(maskLine(line));
103
+ prevLineBlank = false;
104
+ prevLineIndentedCode = false;
105
+ continue;
106
+ }
107
+ // --- Indented code block (4 spaces or tab, after blank line) ---
108
+ if (/^(?: |\t)/.test(line) && (prevLineBlank || prevLineIndentedCode)) {
109
+ result.push(maskLine(line));
110
+ prevLineBlank = false;
111
+ prevLineIndentedCode = true;
112
+ continue;
113
+ }
114
+ // --- Normal line: mask inline code and double-quoted text ---
115
+ result.push(maskInline(line));
116
+ prevLineBlank = '' === line.trim();
117
+ prevLineIndentedCode = false;
118
+ }
119
+ return result.join('\n');
120
+ }
121
+ /** Replace all characters in a line with spaces (preserving length) */ function maskLine(line) {
122
+ return ' '.repeat(line.length);
123
+ }
124
+ /**
125
+ * Mask inline code (`` `...` ``) and double-quoted text (`"..."`) within a line.
126
+ * Uses regex replacement for efficiency (avoids char-by-char concatenation on long lines).
127
+ * Single quotes are NOT masked to avoid false matches with apostrophes.
128
+ */ function maskInline(line) {
129
+ let result = line;
130
+ // Inline code: `...`
131
+ result = result.replace(/`[^`]+`/g, (m)=>' '.repeat(m.length));
132
+ // Double-quoted text: "..." (min 3 chars between quotes)
133
+ result = result.replace(/"[^"]{3,}"/g, (m)=>' '.repeat(m.length));
134
+ return result;
135
+ }
136
+ // ============================================================================
137
+ // Rule Helpers
138
+ // ============================================================================
139
+ /** Find lines matching any of the given patterns, return one match per line */ function findLineMatches(content, patterns) {
140
+ const lines = content.split('\n');
141
+ const matches = [];
142
+ for(let i = 0; i < lines.length; i++)for (const pattern of patterns)if (pattern.test(lines[i])) {
143
+ matches.push({
144
+ line: i + 1
145
+ });
146
+ break;
147
+ }
148
+ return matches;
149
+ }
150
+ // ============================================================================
151
+ // Default Rules
152
+ // ============================================================================
153
+ const SNIPPET_MAX_LENGTH = 120;
154
+ /** Built-in detection rules */ const DEFAULT_RULES = [
155
+ // Rule 1: Prompt Injection (high)
156
+ {
157
+ id: 'prompt-injection',
158
+ level: 'high',
159
+ message: 'Detected prompt injection attempt',
160
+ skipSafeZones: true,
161
+ check: (content)=>findLineMatches(content, [
162
+ /ignore\s+(all\s+)?previous\s+instructions/i,
163
+ /disregard\s+(all\s+)?(prior|previous|above)\s+(instructions|rules|context)/i,
164
+ /you\s+are\s+now\s+/i,
165
+ /from\s+now\s+on[,\s]+you\s+are/i,
166
+ /new\s+system\s+prompt/i,
167
+ /override\s+(your|the)\s+(system|safety|security)\s+(prompt|rules|instructions)/i,
168
+ /forget\s+(?:all\s+)?(?:your\s+)?(?:previous\s+|prior\s+)?(?:instructions|rules|constraints)/i,
169
+ /entering\s+(a\s+)?new\s+(mode|context|session)/i
170
+ ])
171
+ },
172
+ // Rule 2: Data Exfiltration (high)
173
+ {
174
+ id: 'data-exfiltration',
175
+ level: 'high',
176
+ message: 'Detected potential data exfiltration command',
177
+ skipSafeZones: true,
178
+ check: (content)=>{
179
+ const lines = content.split('\n');
180
+ const matches = [];
181
+ const commandPattern = /\b(curl|wget|fetch|http\.post|requests\.post|nc\b|ncat|netcat)\b/i;
182
+ const sensitivePattern = /(\$[A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*|\$ENV\b|\$\{[^}]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[^}]*\})/i;
183
+ for(let i = 0; i < lines.length; i++)if (commandPattern.test(lines[i]) && sensitivePattern.test(lines[i])) matches.push({
184
+ line: i + 1
185
+ });
186
+ return matches;
187
+ }
188
+ },
189
+ // Rule 3: Content Obfuscation (high) — scans ALL content including safe zones
190
+ {
191
+ id: 'obfuscation',
192
+ level: 'high',
193
+ message: 'Detected content obfuscation',
194
+ skipSafeZones: false,
195
+ check: (content)=>{
196
+ const matches = [];
197
+ const lines = content.split('\n');
198
+ // Zero-width characters (suspicious in any context)
199
+ const zeroWidthPattern = /[\u200B\u200C\u200D\uFEFF\u2060\u180E]/;
200
+ for(let i = 0; i < lines.length; i++)if (zeroWidthPattern.test(lines[i])) matches.push({
201
+ line: i + 1,
202
+ snippet: 'Zero-width Unicode characters detected'
203
+ });
204
+ // Long base64-like strings (>200 continuous chars)
205
+ const base64Pattern = /[A-Za-z0-9+/=]{200,}/;
206
+ for(let i = 0; i < lines.length; i++)if (base64Pattern.test(lines[i])) matches.push({
207
+ line: i + 1,
208
+ snippet: 'Suspicious base64-encoded block detected'
209
+ });
210
+ // Large HTML comments (>200 chars of content)
211
+ const commentRegex = /<!--([\s\S]{200,}?)-->/g;
212
+ let match;
213
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex exec loop
214
+ while(null !== (match = commentRegex.exec(content))){
215
+ const lineNum = content.slice(0, match.index).split('\n').length;
216
+ matches.push({
217
+ line: lineNum,
218
+ snippet: `Large HTML comment block (${match[1].length} chars)`
219
+ });
220
+ }
221
+ return matches;
222
+ }
223
+ },
224
+ // Rule 4: Sensitive File Access (medium)
225
+ {
226
+ id: 'sensitive-file-access',
227
+ level: 'medium',
228
+ message: 'References sensitive file path',
229
+ skipSafeZones: true,
230
+ check: (content)=>findLineMatches(content, [
231
+ /~\/\.ssh\b/,
232
+ /~\/\.aws\b/,
233
+ /~\/\.gnupg\b/,
234
+ /~\/\.config\/gcloud\b/,
235
+ /\bid_rsa\b/i,
236
+ /\bid_ed25519\b/i,
237
+ /\/etc\/passwd\b/,
238
+ /\/etc\/shadow\b/,
239
+ /\.env\b(?!\.\w)/
240
+ ])
241
+ },
242
+ // Rule 5: Stealth Instructions (medium) — phrase + action verb matching
243
+ {
244
+ id: 'stealth-instructions',
245
+ level: 'medium',
246
+ message: 'Detected instruction to hide actions from user',
247
+ skipSafeZones: true,
248
+ check: (content)=>{
249
+ const actionVerbs = 'execute|delete|remove|send|transmit|modify|overwrite|install|download|upload|run|write|create|destroy|drop';
250
+ const patterns = [
251
+ new RegExp(`silently\\s+(?:${actionVerbs})`, 'i'),
252
+ new RegExp(`without\\s+telling\\s+the\\s+user.{0,30}(?:${actionVerbs})`, 'i'),
253
+ new RegExp("(?:do\\s+not|don'?t)\\s+show\\s+.{0,40}(?:to\\s+the\\s+user|to\\s+user)", 'i'),
254
+ new RegExp("hide\\s+(?:this|the|these|all)\\s+.{0,30}(?:from\\s+the\\s+user|from\\s+user)", 'i'),
255
+ new RegExp("(?:do\\s+not|don'?t)\\s+mention\\s+.{0,30}(?:to\\s+the\\s+user|to\\s+user)", 'i'),
256
+ new RegExp("keep\\s+(?:this|it)\\s+(?:a\\s+)?secret\\s+from\\s+(?:the\\s+)?user", 'i')
257
+ ];
258
+ // Safe patterns to exclude (common in legitimate DevOps/automation skills)
259
+ const safePatterns = [
260
+ /silently\s+(?:ignore|skip|fail|discard|suppress|continue|pass|drop|swallow)/i
261
+ ];
262
+ const lines = content.split('\n');
263
+ const matches = [];
264
+ for(let i = 0; i < lines.length; i++){
265
+ const line = lines[i];
266
+ if (!safePatterns.some((p)=>p.test(line))) {
267
+ for (const pattern of patterns)if (pattern.test(line)) {
268
+ matches.push({
269
+ line: i + 1
270
+ });
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ return matches;
276
+ }
277
+ },
278
+ // Rule 6: Oversized Content (low) — scans ALL content
279
+ {
280
+ id: 'oversized-content',
281
+ level: 'low',
282
+ message: 'Content exceeds recommended size limit',
283
+ skipSafeZones: false,
284
+ check: (content)=>{
285
+ const MAX_SIZE_BYTES = 51200;
286
+ const sizeBytes = Buffer.byteLength(content, 'utf-8');
287
+ if (sizeBytes > MAX_SIZE_BYTES) return [
288
+ {
289
+ snippet: `Content size: ${(sizeBytes / 1024).toFixed(1)}KB (limit: 50KB)`
290
+ }
291
+ ];
292
+ return [];
293
+ }
294
+ }
295
+ ];
296
+ // ============================================================================
297
+ // ContentScanner
298
+ // ============================================================================
299
+ /** Build the effective rule set from defaults + options */ function buildRuleSet(options) {
300
+ let rules = DEFAULT_RULES.map((r)=>({
301
+ ...r
302
+ }));
303
+ if (options?.disabledRules?.length) {
304
+ const disabled = new Set(options.disabledRules);
305
+ rules = rules.filter((r)=>!disabled.has(r.id));
306
+ }
307
+ if (options?.overrides) for (const rule of rules){
308
+ const override = options.overrides[rule.id];
309
+ if (override) rule.level = override;
310
+ }
311
+ if (options?.customRules?.length) rules.push(...options.customRules);
312
+ return rules;
313
+ }
314
+ /**
315
+ * Content scanner for SKILL.md files.
316
+ *
317
+ * Detects prompt injection, data exfiltration, obfuscation, sensitive file
318
+ * access, stealth instructions, and oversized content.
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * // Default usage (CLI)
323
+ * const scanner = new ContentScanner();
324
+ * const result = scanner.scan(content);
325
+ *
326
+ * // Custom usage (private registry server)
327
+ * const scanner = new ContentScanner({
328
+ * overrides: { 'prompt-injection': 'medium' },
329
+ * disabledRules: ['stealth-instructions'],
330
+ * });
331
+ * ```
332
+ */ class ContentScanner {
333
+ rules;
334
+ constructor(options){
335
+ this.rules = buildRuleSet(options);
336
+ }
337
+ /**
338
+ * Scan content string for malicious patterns.
339
+ * Pure string operation — no file system access.
340
+ */ scan(content) {
341
+ const originalLines = content.split('\n');
342
+ const maskedContent = maskSafeZones(content);
343
+ const findings = [];
344
+ for (const rule of this.rules){
345
+ const targetContent = rule.skipSafeZones ? maskedContent : content;
346
+ const matches = rule.check(targetContent);
347
+ for (const match of matches){
348
+ // Use custom snippet if provided, otherwise generate from original content
349
+ const snippet = match.snippet ?? (null != match.line ? originalLines[match.line - 1]?.trim().slice(0, SNIPPET_MAX_LENGTH) : void 0);
350
+ findings.push({
351
+ rule: rule.id,
352
+ level: rule.level,
353
+ message: rule.message,
354
+ line: match.line,
355
+ snippet
356
+ });
357
+ }
358
+ }
359
+ const hasHighRisk = findings.some((f)=>'high' === f.level);
360
+ return {
361
+ passed: !hasHighRisk,
362
+ findings
363
+ };
364
+ }
365
+ /**
366
+ * Scan a file for malicious patterns.
367
+ * Convenience wrapper that reads the file then calls scan().
368
+ */ scanFile(filePath) {
369
+ const content = external_node_fs_.readFileSync(filePath, 'utf-8');
370
+ return this.scan(content);
371
+ }
372
+ }
373
+ // ============================================================================
374
+ // ContentScanError
375
+ // ============================================================================
376
+ /**
377
+ * Error thrown when content scanning detects high-risk findings.
378
+ * Carries the full findings array for display purposes.
379
+ */ class ContentScanError extends Error {
380
+ findings;
381
+ constructor(findings){
382
+ const highCount = findings.filter((f)=>'high' === f.level).length;
383
+ super(`Content security scan failed: ${highCount} high-risk finding(s) detected`);
384
+ this.name = 'ContentScanError';
385
+ this.findings = findings;
386
+ }
387
+ }
35
388
  /**
36
389
  * Agent Registry - Multi-Agent configuration definitions
37
390
  *
@@ -5000,4 +5353,4 @@ class RegistryResolver {
5000
5353
  return results;
5001
5354
  }
5002
5355
  }
5003
- export { CacheManager, config_loader_ConfigLoader as ConfigLoader, DEFAULT_REGISTRIES, GitResolver, HttpResolver, Installer, LockManager, SkillManager, SkillValidationError, agents, detectInstalledAgents, generateSkillMd, getAgentConfig, getAgentSkillsDir, getAllAgentTypes, getCanonicalSkillPath, getCanonicalSkillsDir, hasValidSkillMd, isPathSafe, isValidAgentType, logger_logger as logger, parseSkillFromDir, parseSkillMd, skill_parser_parseSkillMdFile as parseSkillMdFile, sanitizeName, shortenPath, validateSkillDescription, validateSkillName };
5356
+ export { CacheManager, config_loader_ConfigLoader as ConfigLoader, ContentScanError, ContentScanner, DEFAULT_REGISTRIES, DEFAULT_RULES, GitResolver, HttpResolver, Installer, LockManager, SkillManager, SkillValidationError, agents, detectInstalledAgents, generateSkillMd, getAgentConfig, getAgentSkillsDir, getAllAgentTypes, getCanonicalSkillPath, getCanonicalSkillsDir, hasValidSkillMd, isPathSafe, isValidAgentType, logger_logger as logger, maskSafeZones, parseSkillFromDir, parseSkillMd, skill_parser_parseSkillMdFile as parseSkillMdFile, sanitizeName, shortenPath, validateSkillDescription, validateSkillName };
@@ -0,0 +1,355 @@
1
+ import * as __WEBPACK_EXTERNAL_MODULE_node_fs__ from "node:fs";
2
+ /**
3
+ * ContentScanner - Detect malicious patterns in SKILL.md content
4
+ *
5
+ * Features:
6
+ * - Context-aware: skips safe zones (frontmatter, code blocks, quotes, blockquotes)
7
+ * - 6 built-in detection rules across 3 risk levels
8
+ * - Configurable: override levels, disable rules, add custom rules
9
+ * - Pure string operations in scan() — no fs dependency, suitable for server use
10
+ * - scanFile() convenience method for CLI use
11
+ */ // ============================================================================
12
+ // Safe Zone Masking
13
+ // ============================================================================
14
+ /**
15
+ * Mask safe zones in Markdown content with spaces, preserving line structure.
16
+ *
17
+ * Safe zones (content replaced with spaces):
18
+ * - YAML frontmatter (`---` ... `---` at file start)
19
+ * - Fenced code blocks (``` or ~~~)
20
+ * - Indented code blocks (4 spaces / tab after blank line)
21
+ * - Blockquotes (`> ` prefix)
22
+ * - Inline code (`` `...` ``)
23
+ * - Double-quoted text (`"..."`, min 3 chars between quotes)
24
+ *
25
+ * Line breaks are preserved so line numbers remain correct.
26
+ */ function maskSafeZones(content) {
27
+ const lines = content.split('\n');
28
+ const result = [];
29
+ let inFrontmatter = false;
30
+ let inFencedCode = false;
31
+ let fenceChar = '';
32
+ let fenceLength = 0;
33
+ let prevLineBlank = false;
34
+ let prevLineIndentedCode = false;
35
+ for(let i = 0; i < lines.length; i++){
36
+ const line = lines[i];
37
+ // --- YAML Frontmatter (only at file start) ---
38
+ if (0 === i && '---' === line.trim()) {
39
+ inFrontmatter = true;
40
+ result.push(maskLine(line));
41
+ continue;
42
+ }
43
+ if (inFrontmatter) {
44
+ result.push(maskLine(line));
45
+ if ('---' === line.trim()) inFrontmatter = false;
46
+ continue;
47
+ }
48
+ // --- Fenced code blocks (``` or ~~~) ---
49
+ const fenceMatch = line.match(/^(`{3,}|~{3,})/);
50
+ if (!inFencedCode && fenceMatch) {
51
+ inFencedCode = true;
52
+ fenceChar = fenceMatch[1][0];
53
+ fenceLength = fenceMatch[1].length;
54
+ result.push(maskLine(line));
55
+ prevLineBlank = false;
56
+ prevLineIndentedCode = false;
57
+ continue;
58
+ }
59
+ if (inFencedCode) {
60
+ result.push(maskLine(line));
61
+ const closeMatch = line.match(/^(`{3,}|~{3,})\s*$/);
62
+ if (closeMatch && closeMatch[1][0] === fenceChar && closeMatch[1].length >= fenceLength) inFencedCode = false;
63
+ prevLineBlank = false;
64
+ prevLineIndentedCode = false;
65
+ continue;
66
+ }
67
+ // --- Blockquote ---
68
+ if (/^>\s?/.test(line)) {
69
+ result.push(maskLine(line));
70
+ prevLineBlank = false;
71
+ prevLineIndentedCode = false;
72
+ continue;
73
+ }
74
+ // --- Indented code block (4 spaces or tab, after blank line) ---
75
+ if (/^(?: |\t)/.test(line) && (prevLineBlank || prevLineIndentedCode)) {
76
+ result.push(maskLine(line));
77
+ prevLineBlank = false;
78
+ prevLineIndentedCode = true;
79
+ continue;
80
+ }
81
+ // --- Normal line: mask inline code and double-quoted text ---
82
+ result.push(maskInline(line));
83
+ prevLineBlank = '' === line.trim();
84
+ prevLineIndentedCode = false;
85
+ }
86
+ return result.join('\n');
87
+ }
88
+ /** Replace all characters in a line with spaces (preserving length) */ function maskLine(line) {
89
+ return ' '.repeat(line.length);
90
+ }
91
+ /**
92
+ * Mask inline code (`` `...` ``) and double-quoted text (`"..."`) within a line.
93
+ * Uses regex replacement for efficiency (avoids char-by-char concatenation on long lines).
94
+ * Single quotes are NOT masked to avoid false matches with apostrophes.
95
+ */ function maskInline(line) {
96
+ let result = line;
97
+ // Inline code: `...`
98
+ result = result.replace(/`[^`]+`/g, (m)=>' '.repeat(m.length));
99
+ // Double-quoted text: "..." (min 3 chars between quotes)
100
+ result = result.replace(/"[^"]{3,}"/g, (m)=>' '.repeat(m.length));
101
+ return result;
102
+ }
103
+ // ============================================================================
104
+ // Rule Helpers
105
+ // ============================================================================
106
+ /** Find lines matching any of the given patterns, return one match per line */ function findLineMatches(content, patterns) {
107
+ const lines = content.split('\n');
108
+ const matches = [];
109
+ for(let i = 0; i < lines.length; i++)for (const pattern of patterns)if (pattern.test(lines[i])) {
110
+ matches.push({
111
+ line: i + 1
112
+ });
113
+ break;
114
+ }
115
+ return matches;
116
+ }
117
+ // ============================================================================
118
+ // Default Rules
119
+ // ============================================================================
120
+ const SNIPPET_MAX_LENGTH = 120;
121
+ /** Built-in detection rules */ const DEFAULT_RULES = [
122
+ // Rule 1: Prompt Injection (high)
123
+ {
124
+ id: 'prompt-injection',
125
+ level: 'high',
126
+ message: 'Detected prompt injection attempt',
127
+ skipSafeZones: true,
128
+ check: (content)=>findLineMatches(content, [
129
+ /ignore\s+(all\s+)?previous\s+instructions/i,
130
+ /disregard\s+(all\s+)?(prior|previous|above)\s+(instructions|rules|context)/i,
131
+ /you\s+are\s+now\s+/i,
132
+ /from\s+now\s+on[,\s]+you\s+are/i,
133
+ /new\s+system\s+prompt/i,
134
+ /override\s+(your|the)\s+(system|safety|security)\s+(prompt|rules|instructions)/i,
135
+ /forget\s+(?:all\s+)?(?:your\s+)?(?:previous\s+|prior\s+)?(?:instructions|rules|constraints)/i,
136
+ /entering\s+(a\s+)?new\s+(mode|context|session)/i
137
+ ])
138
+ },
139
+ // Rule 2: Data Exfiltration (high)
140
+ {
141
+ id: 'data-exfiltration',
142
+ level: 'high',
143
+ message: 'Detected potential data exfiltration command',
144
+ skipSafeZones: true,
145
+ check: (content)=>{
146
+ const lines = content.split('\n');
147
+ const matches = [];
148
+ const commandPattern = /\b(curl|wget|fetch|http\.post|requests\.post|nc\b|ncat|netcat)\b/i;
149
+ const sensitivePattern = /(\$[A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*|\$ENV\b|\$\{[^}]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[^}]*\})/i;
150
+ for(let i = 0; i < lines.length; i++)if (commandPattern.test(lines[i]) && sensitivePattern.test(lines[i])) matches.push({
151
+ line: i + 1
152
+ });
153
+ return matches;
154
+ }
155
+ },
156
+ // Rule 3: Content Obfuscation (high) — scans ALL content including safe zones
157
+ {
158
+ id: 'obfuscation',
159
+ level: 'high',
160
+ message: 'Detected content obfuscation',
161
+ skipSafeZones: false,
162
+ check: (content)=>{
163
+ const matches = [];
164
+ const lines = content.split('\n');
165
+ // Zero-width characters (suspicious in any context)
166
+ const zeroWidthPattern = /[\u200B\u200C\u200D\uFEFF\u2060\u180E]/;
167
+ for(let i = 0; i < lines.length; i++)if (zeroWidthPattern.test(lines[i])) matches.push({
168
+ line: i + 1,
169
+ snippet: 'Zero-width Unicode characters detected'
170
+ });
171
+ // Long base64-like strings (>200 continuous chars)
172
+ const base64Pattern = /[A-Za-z0-9+/=]{200,}/;
173
+ for(let i = 0; i < lines.length; i++)if (base64Pattern.test(lines[i])) matches.push({
174
+ line: i + 1,
175
+ snippet: 'Suspicious base64-encoded block detected'
176
+ });
177
+ // Large HTML comments (>200 chars of content)
178
+ const commentRegex = /<!--([\s\S]{200,}?)-->/g;
179
+ let match;
180
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex exec loop
181
+ while(null !== (match = commentRegex.exec(content))){
182
+ const lineNum = content.slice(0, match.index).split('\n').length;
183
+ matches.push({
184
+ line: lineNum,
185
+ snippet: `Large HTML comment block (${match[1].length} chars)`
186
+ });
187
+ }
188
+ return matches;
189
+ }
190
+ },
191
+ // Rule 4: Sensitive File Access (medium)
192
+ {
193
+ id: 'sensitive-file-access',
194
+ level: 'medium',
195
+ message: 'References sensitive file path',
196
+ skipSafeZones: true,
197
+ check: (content)=>findLineMatches(content, [
198
+ /~\/\.ssh\b/,
199
+ /~\/\.aws\b/,
200
+ /~\/\.gnupg\b/,
201
+ /~\/\.config\/gcloud\b/,
202
+ /\bid_rsa\b/i,
203
+ /\bid_ed25519\b/i,
204
+ /\/etc\/passwd\b/,
205
+ /\/etc\/shadow\b/,
206
+ /\.env\b(?!\.\w)/
207
+ ])
208
+ },
209
+ // Rule 5: Stealth Instructions (medium) — phrase + action verb matching
210
+ {
211
+ id: 'stealth-instructions',
212
+ level: 'medium',
213
+ message: 'Detected instruction to hide actions from user',
214
+ skipSafeZones: true,
215
+ check: (content)=>{
216
+ const actionVerbs = 'execute|delete|remove|send|transmit|modify|overwrite|install|download|upload|run|write|create|destroy|drop';
217
+ const patterns = [
218
+ new RegExp(`silently\\s+(?:${actionVerbs})`, 'i'),
219
+ new RegExp(`without\\s+telling\\s+the\\s+user.{0,30}(?:${actionVerbs})`, 'i'),
220
+ new RegExp("(?:do\\s+not|don'?t)\\s+show\\s+.{0,40}(?:to\\s+the\\s+user|to\\s+user)", 'i'),
221
+ new RegExp("hide\\s+(?:this|the|these|all)\\s+.{0,30}(?:from\\s+the\\s+user|from\\s+user)", 'i'),
222
+ new RegExp("(?:do\\s+not|don'?t)\\s+mention\\s+.{0,30}(?:to\\s+the\\s+user|to\\s+user)", 'i'),
223
+ new RegExp("keep\\s+(?:this|it)\\s+(?:a\\s+)?secret\\s+from\\s+(?:the\\s+)?user", 'i')
224
+ ];
225
+ // Safe patterns to exclude (common in legitimate DevOps/automation skills)
226
+ const safePatterns = [
227
+ /silently\s+(?:ignore|skip|fail|discard|suppress|continue|pass|drop|swallow)/i
228
+ ];
229
+ const lines = content.split('\n');
230
+ const matches = [];
231
+ for(let i = 0; i < lines.length; i++){
232
+ const line = lines[i];
233
+ if (!safePatterns.some((p)=>p.test(line))) {
234
+ for (const pattern of patterns)if (pattern.test(line)) {
235
+ matches.push({
236
+ line: i + 1
237
+ });
238
+ break;
239
+ }
240
+ }
241
+ }
242
+ return matches;
243
+ }
244
+ },
245
+ // Rule 6: Oversized Content (low) — scans ALL content
246
+ {
247
+ id: 'oversized-content',
248
+ level: 'low',
249
+ message: 'Content exceeds recommended size limit',
250
+ skipSafeZones: false,
251
+ check: (content)=>{
252
+ const MAX_SIZE_BYTES = 51200;
253
+ const sizeBytes = Buffer.byteLength(content, 'utf-8');
254
+ if (sizeBytes > MAX_SIZE_BYTES) return [
255
+ {
256
+ snippet: `Content size: ${(sizeBytes / 1024).toFixed(1)}KB (limit: 50KB)`
257
+ }
258
+ ];
259
+ return [];
260
+ }
261
+ }
262
+ ];
263
+ // ============================================================================
264
+ // ContentScanner
265
+ // ============================================================================
266
+ /** Build the effective rule set from defaults + options */ function buildRuleSet(options) {
267
+ let rules = DEFAULT_RULES.map((r)=>({
268
+ ...r
269
+ }));
270
+ if (options?.disabledRules?.length) {
271
+ const disabled = new Set(options.disabledRules);
272
+ rules = rules.filter((r)=>!disabled.has(r.id));
273
+ }
274
+ if (options?.overrides) for (const rule of rules){
275
+ const override = options.overrides[rule.id];
276
+ if (override) rule.level = override;
277
+ }
278
+ if (options?.customRules?.length) rules.push(...options.customRules);
279
+ return rules;
280
+ }
281
+ /**
282
+ * Content scanner for SKILL.md files.
283
+ *
284
+ * Detects prompt injection, data exfiltration, obfuscation, sensitive file
285
+ * access, stealth instructions, and oversized content.
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * // Default usage (CLI)
290
+ * const scanner = new ContentScanner();
291
+ * const result = scanner.scan(content);
292
+ *
293
+ * // Custom usage (private registry server)
294
+ * const scanner = new ContentScanner({
295
+ * overrides: { 'prompt-injection': 'medium' },
296
+ * disabledRules: ['stealth-instructions'],
297
+ * });
298
+ * ```
299
+ */ class ContentScanner {
300
+ rules;
301
+ constructor(options){
302
+ this.rules = buildRuleSet(options);
303
+ }
304
+ /**
305
+ * Scan content string for malicious patterns.
306
+ * Pure string operation — no file system access.
307
+ */ scan(content) {
308
+ const originalLines = content.split('\n');
309
+ const maskedContent = maskSafeZones(content);
310
+ const findings = [];
311
+ for (const rule of this.rules){
312
+ const targetContent = rule.skipSafeZones ? maskedContent : content;
313
+ const matches = rule.check(targetContent);
314
+ for (const match of matches){
315
+ // Use custom snippet if provided, otherwise generate from original content
316
+ const snippet = match.snippet ?? (null != match.line ? originalLines[match.line - 1]?.trim().slice(0, SNIPPET_MAX_LENGTH) : void 0);
317
+ findings.push({
318
+ rule: rule.id,
319
+ level: rule.level,
320
+ message: rule.message,
321
+ line: match.line,
322
+ snippet
323
+ });
324
+ }
325
+ }
326
+ const hasHighRisk = findings.some((f)=>'high' === f.level);
327
+ return {
328
+ passed: !hasHighRisk,
329
+ findings
330
+ };
331
+ }
332
+ /**
333
+ * Scan a file for malicious patterns.
334
+ * Convenience wrapper that reads the file then calls scan().
335
+ */ scanFile(filePath) {
336
+ const content = __WEBPACK_EXTERNAL_MODULE_node_fs__.readFileSync(filePath, 'utf-8');
337
+ return this.scan(content);
338
+ }
339
+ }
340
+ // ============================================================================
341
+ // ContentScanError
342
+ // ============================================================================
343
+ /**
344
+ * Error thrown when content scanning detects high-risk findings.
345
+ * Carries the full findings array for display purposes.
346
+ */ class ContentScanError extends Error {
347
+ findings;
348
+ constructor(findings){
349
+ const highCount = findings.filter((f)=>'high' === f.level).length;
350
+ super(`Content security scan failed: ${highCount} high-risk finding(s) detected`);
351
+ this.name = 'ContentScanError';
352
+ this.findings = findings;
353
+ }
354
+ }
355
+ export { ContentScanError, ContentScanner, DEFAULT_RULES, maskSafeZones };
@@ -15,6 +15,10 @@ export type { InstallMode, InstallResult } from '../core/installer.js';
15
15
  * SKILL.md parsing related types (following agentskills.io specification)
16
16
  */
17
17
  export type { ParsedSkill, SkillMdFrontmatter, } from '../core/skill-parser.js';
18
+ /**
19
+ * Content scanning types
20
+ */
21
+ export type { RiskLevel, ScanFinding, ScannerOptions, ScanResult, ScanRule, ScanRuleMatch, } from '../core/content-scanner.js';
18
22
  /**
19
23
  * Version specification format
20
24
  * - Exact version: @v1.0.0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reskill",
3
- "version": "1.15.0",
3
+ "version": "1.16.0-beta.0",
4
4
  "description": "AI Skills Package Manager - Git-based skills management for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,10 @@
16
16
  },
17
17
  "./cli": {
18
18
  "import": "./dist/cli/index.js"
19
+ },
20
+ "./scanner": {
21
+ "import": "./dist/scanner.js",
22
+ "types": "./dist/scanner.d.ts"
19
23
  }
20
24
  },
21
25
  "files": [