agentshield-sdk 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/LICENSE +21 -0
  3. package/README.md +975 -0
  4. package/bin/agent-shield.js +680 -0
  5. package/package.json +118 -0
  6. package/src/adaptive.js +330 -0
  7. package/src/agent-protocol.js +998 -0
  8. package/src/alert-tuning.js +480 -0
  9. package/src/allowlist.js +603 -0
  10. package/src/audit-immutable.js +914 -0
  11. package/src/audit-streaming.js +469 -0
  12. package/src/badges.js +196 -0
  13. package/src/behavior-profiling.js +289 -0
  14. package/src/benchmark-harness.js +804 -0
  15. package/src/canary.js +271 -0
  16. package/src/certification.js +563 -0
  17. package/src/circuit-breaker.js +321 -0
  18. package/src/compliance.js +617 -0
  19. package/src/confidence-tuning.js +324 -0
  20. package/src/confused-deputy.js +624 -0
  21. package/src/context-scoring.js +360 -0
  22. package/src/conversation.js +494 -0
  23. package/src/cost-optimizer.js +1024 -0
  24. package/src/ctf.js +462 -0
  25. package/src/detector-core.js +1999 -0
  26. package/src/distributed.js +359 -0
  27. package/src/document-scanner.js +795 -0
  28. package/src/embedding.js +307 -0
  29. package/src/encoding.js +429 -0
  30. package/src/enterprise.js +405 -0
  31. package/src/errors.js +100 -0
  32. package/src/eu-ai-act.js +523 -0
  33. package/src/fuzzer.js +764 -0
  34. package/src/honeypot.js +328 -0
  35. package/src/i18n-patterns.js +523 -0
  36. package/src/index.js +430 -0
  37. package/src/integrations.js +528 -0
  38. package/src/llm-redteam.js +670 -0
  39. package/src/main.js +741 -0
  40. package/src/main.mjs +38 -0
  41. package/src/mcp-bridge.js +542 -0
  42. package/src/mcp-certification.js +846 -0
  43. package/src/mcp-sdk-integration.js +355 -0
  44. package/src/mcp-security-runtime.js +741 -0
  45. package/src/mcp-server.js +740 -0
  46. package/src/middleware.js +208 -0
  47. package/src/model-finetuning.js +884 -0
  48. package/src/model-fingerprint.js +1042 -0
  49. package/src/multi-agent-trust.js +453 -0
  50. package/src/multi-agent.js +404 -0
  51. package/src/multimodal.js +296 -0
  52. package/src/nist-mapping.js +505 -0
  53. package/src/observability.js +330 -0
  54. package/src/openclaw.js +450 -0
  55. package/src/otel.js +544 -0
  56. package/src/owasp-2025.js +483 -0
  57. package/src/pii.js +390 -0
  58. package/src/plugin-marketplace.js +628 -0
  59. package/src/plugin-system.js +349 -0
  60. package/src/policy-dsl.js +775 -0
  61. package/src/policy-extended.js +635 -0
  62. package/src/policy.js +443 -0
  63. package/src/presets.js +409 -0
  64. package/src/production.js +557 -0
  65. package/src/prompt-leakage.js +321 -0
  66. package/src/rag-vulnerability.js +579 -0
  67. package/src/redteam.js +475 -0
  68. package/src/response-handler.js +429 -0
  69. package/src/scanners.js +357 -0
  70. package/src/self-healing.js +363 -0
  71. package/src/semantic.js +339 -0
  72. package/src/shield-score.js +250 -0
  73. package/src/sso-saml.js +897 -0
  74. package/src/stream-scanner.js +806 -0
  75. package/src/testing.js +505 -0
  76. package/src/threat-encyclopedia.js +629 -0
  77. package/src/threat-intel-network.js +1017 -0
  78. package/src/token-analysis.js +467 -0
  79. package/src/tool-guard.js +412 -0
  80. package/src/tool-output-validator.js +354 -0
  81. package/src/utils.js +83 -0
  82. package/src/watermark.js +235 -0
  83. package/src/worker-scanner.js +601 -0
  84. package/types/index.d.ts +2088 -0
@@ -0,0 +1,628 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Plugin Marketplace (v2.0)
5
+ *
6
+ * Registry and marketplace for community-contributed detection plugins.
7
+ * Supports local plugin directories and remote registries.
8
+ * Includes quality scoring, safety validation, and version management.
9
+ *
10
+ * Zero dependencies — uses Node.js built-in modules only.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const http = require('http');
16
+ const https = require('https');
17
+ const crypto = require('crypto');
18
+
19
+ // =========================================================================
20
+ // PLUGIN SCHEMA
21
+ // =========================================================================
22
+
23
+ /**
24
+ * Required fields for a valid plugin manifest.
25
+ */
26
+ const REQUIRED_FIELDS = ['name', 'version', 'description', 'author', 'patterns'];
27
+ const SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
28
+
29
+ // =========================================================================
30
+ // PLUGIN VALIDATOR
31
+ // =========================================================================
32
+
33
+ /**
34
+ * Validates plugin manifests and pattern definitions for safety and quality.
35
+ */
36
+ class PluginValidator {
37
+ /**
38
+ * Validate a plugin manifest object.
39
+ * @param {object} manifest - The plugin manifest.
40
+ * @returns {object} { valid: boolean, errors: string[], warnings: string[], score: number }
41
+ */
42
+ validate(manifest) {
43
+ const errors = [];
44
+ const warnings = [];
45
+ let score = 100;
46
+
47
+ // Required fields
48
+ for (const field of REQUIRED_FIELDS) {
49
+ if (!manifest[field]) {
50
+ errors.push(`Missing required field: "${field}"`);
51
+ score -= 20;
52
+ }
53
+ }
54
+
55
+ // Name validation
56
+ if (manifest.name) {
57
+ if (typeof manifest.name !== 'string' || manifest.name.length < 3) {
58
+ errors.push('Plugin name must be a string of at least 3 characters.');
59
+ score -= 10;
60
+ }
61
+ if (!/^[a-z0-9-]+$/.test(manifest.name)) {
62
+ errors.push('Plugin name must be lowercase alphanumeric with dashes only.');
63
+ score -= 10;
64
+ }
65
+ }
66
+
67
+ // Version validation
68
+ if (manifest.version && !SEMVER_REGEX.test(manifest.version)) {
69
+ errors.push('Version must follow semver format (e.g., 1.0.0).');
70
+ score -= 10;
71
+ }
72
+
73
+ // Patterns validation
74
+ if (manifest.patterns) {
75
+ if (!Array.isArray(manifest.patterns)) {
76
+ errors.push('Patterns must be an array.');
77
+ score -= 20;
78
+ } else {
79
+ for (let i = 0; i < manifest.patterns.length; i++) {
80
+ const p = manifest.patterns[i];
81
+ if (!p.regex && !p.pattern) {
82
+ errors.push(`Pattern ${i}: must have a "regex" or "pattern" field.`);
83
+ score -= 5;
84
+ }
85
+ if (!p.severity) {
86
+ warnings.push(`Pattern ${i}: missing severity (defaulting to "medium").`);
87
+ score -= 2;
88
+ }
89
+ if (!p.category) {
90
+ warnings.push(`Pattern ${i}: missing category.`);
91
+ score -= 2;
92
+ }
93
+ if (!p.description) {
94
+ warnings.push(`Pattern ${i}: missing description.`);
95
+ score -= 2;
96
+ }
97
+
98
+ // Safety: check regex isn't catastrophically backtracking
99
+ if (p.regex || p.pattern) {
100
+ const regexStr = typeof (p.regex || p.pattern) === 'string' ? (p.regex || p.pattern) : '';
101
+ if (regexStr && this._isReDoSRisk(regexStr)) {
102
+ warnings.push(`Pattern ${i}: regex may be vulnerable to ReDoS (catastrophic backtracking).`);
103
+ score -= 10;
104
+ }
105
+ }
106
+ }
107
+
108
+ if (manifest.patterns.length === 0) {
109
+ errors.push('Plugin must define at least one pattern.');
110
+ score -= 15;
111
+ }
112
+
113
+ if (manifest.patterns.length > 100) {
114
+ warnings.push('Plugin has over 100 patterns — consider splitting into multiple plugins.');
115
+ score -= 5;
116
+ }
117
+ }
118
+ }
119
+
120
+ // Metadata checks
121
+ if (!manifest.description || manifest.description.length < 10) {
122
+ warnings.push('Description should be at least 10 characters.');
123
+ score -= 5;
124
+ }
125
+
126
+ if (!manifest.license) {
127
+ warnings.push('No license specified. Recommend MIT or Apache-2.0.');
128
+ score -= 5;
129
+ }
130
+
131
+ if (!manifest.tags || !Array.isArray(manifest.tags) || manifest.tags.length === 0) {
132
+ warnings.push('Adding tags helps with plugin discovery.');
133
+ score -= 3;
134
+ }
135
+
136
+ if (manifest.testCases && Array.isArray(manifest.testCases) && manifest.testCases.length >= 3) {
137
+ score += 5; // Bonus for including tests
138
+ } else {
139
+ warnings.push('Including testCases improves plugin quality score.');
140
+ }
141
+
142
+ score = Math.max(0, Math.min(100, score));
143
+
144
+ return {
145
+ valid: errors.length === 0,
146
+ errors,
147
+ warnings,
148
+ score,
149
+ grade: score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F'
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Run test cases defined in a plugin manifest.
155
+ * @param {object} manifest - Plugin manifest with testCases.
156
+ * @returns {object} { passed: number, failed: number, results: Array }
157
+ */
158
+ runTests(manifest) {
159
+ if (!manifest.testCases || !Array.isArray(manifest.testCases)) {
160
+ return { passed: 0, failed: 0, results: [] };
161
+ }
162
+
163
+ const results = [];
164
+ let passed = 0;
165
+ let failed = 0;
166
+
167
+ for (const tc of manifest.testCases) {
168
+ const { input, shouldDetect } = tc;
169
+ let detected = false;
170
+
171
+ for (const p of manifest.patterns || []) {
172
+ try {
173
+ const regex = p.regex instanceof RegExp ? p.regex : new RegExp(typeof p.regex === 'string' ? p.regex : p.pattern, 'i');
174
+ if (regex.test(input)) {
175
+ detected = true;
176
+ break;
177
+ }
178
+ } catch (e) {
179
+ // Invalid regex
180
+ }
181
+ }
182
+
183
+ const pass = detected === shouldDetect;
184
+ if (pass) passed++;
185
+ else failed++;
186
+
187
+ results.push({ input: input.substring(0, 80), expected: shouldDetect, actual: detected, pass });
188
+ }
189
+
190
+ return { passed, failed, total: results.length, results };
191
+ }
192
+
193
+ /** @private */
194
+ _isReDoSRisk(regexStr) {
195
+ // Heuristic: nested quantifiers like (a+)+ or (a|b)* are ReDoS risks
196
+ return /\([^)]*[+*][^)]*\)[+*]/.test(regexStr) || /\([^)]*\|[^)]*\)[+*]{2,}/.test(regexStr);
197
+ }
198
+ }
199
+
200
+ // =========================================================================
201
+ // PLUGIN REGISTRY
202
+ // =========================================================================
203
+
204
+ /**
205
+ * Local plugin registry. Manages installed plugins, activation, and versioning.
206
+ */
207
+ class PluginRegistry {
208
+ /**
209
+ * @param {object} [options]
210
+ * @param {string} [options.pluginDir] - Directory to store/load plugins.
211
+ * @param {boolean} [options.autoValidate=true] - Validate plugins on registration.
212
+ */
213
+ constructor(options = {}) {
214
+ this.pluginDir = options.pluginDir || null;
215
+ this.autoValidate = options.autoValidate !== false;
216
+ this._plugins = new Map();
217
+ this._activePlugins = new Set();
218
+ this._validator = new PluginValidator();
219
+ this._installHistory = [];
220
+
221
+ // Load plugins from directory if configured
222
+ if (this.pluginDir) {
223
+ this._loadFromDir();
224
+ }
225
+
226
+ console.log('[Agent Shield] PluginRegistry initialized (plugins: %d)', this._plugins.size);
227
+ }
228
+
229
+ /**
230
+ * Register a plugin.
231
+ * @param {object} manifest - Plugin manifest object.
232
+ * @returns {object} { success: boolean, validation?: object, error?: string }
233
+ */
234
+ register(manifest) {
235
+ if (this.autoValidate) {
236
+ const validation = this._validator.validate(manifest);
237
+ if (!validation.valid) {
238
+ return { success: false, validation, error: `Validation failed: ${validation.errors.join('; ')}` };
239
+ }
240
+ }
241
+
242
+ const id = manifest.name;
243
+ const existing = this._plugins.get(id);
244
+
245
+ if (existing && existing.version === manifest.version) {
246
+ return { success: false, error: `Plugin "${id}" v${manifest.version} is already registered.` };
247
+ }
248
+
249
+ // Compile regex patterns
250
+ const compiled = this._compilePatterns(manifest);
251
+
252
+ this._plugins.set(id, {
253
+ ...manifest,
254
+ compiledPatterns: compiled,
255
+ installedAt: Date.now(),
256
+ checksum: this._checksum(JSON.stringify(manifest))
257
+ });
258
+
259
+ this._activePlugins.add(id);
260
+ this._installHistory.push({ action: 'install', plugin: id, version: manifest.version, timestamp: Date.now() });
261
+
262
+ console.log('[Agent Shield] Plugin registered: %s v%s (%d patterns)', id, manifest.version, compiled.length);
263
+
264
+ // Save to directory if configured
265
+ if (this.pluginDir) {
266
+ this._savePlugin(id, manifest);
267
+ }
268
+
269
+ const validation = this._validator.validate(manifest);
270
+ return { success: true, validation };
271
+ }
272
+
273
+ /**
274
+ * Unregister a plugin.
275
+ * @param {string} name - Plugin name.
276
+ * @returns {boolean}
277
+ */
278
+ unregister(name) {
279
+ const removed = this._plugins.delete(name);
280
+ this._activePlugins.delete(name);
281
+ if (removed) {
282
+ this._installHistory.push({ action: 'uninstall', plugin: name, timestamp: Date.now() });
283
+ }
284
+ return removed;
285
+ }
286
+
287
+ /**
288
+ * Enable a registered plugin.
289
+ * @param {string} name
290
+ * @returns {boolean}
291
+ */
292
+ enable(name) {
293
+ if (!this._plugins.has(name)) return false;
294
+ this._activePlugins.add(name);
295
+ return true;
296
+ }
297
+
298
+ /**
299
+ * Disable a plugin (keeps it registered but inactive).
300
+ * @param {string} name
301
+ * @returns {boolean}
302
+ */
303
+ disable(name) {
304
+ return this._activePlugins.delete(name);
305
+ }
306
+
307
+ /**
308
+ * Get a registered plugin by name.
309
+ * @param {string} name
310
+ * @returns {object|null}
311
+ */
312
+ get(name) {
313
+ return this._plugins.get(name) || null;
314
+ }
315
+
316
+ /**
317
+ * List all registered plugins.
318
+ * @returns {Array<object>}
319
+ */
320
+ list() {
321
+ return [...this._plugins.entries()].map(([name, plugin]) => ({
322
+ name,
323
+ version: plugin.version,
324
+ description: plugin.description,
325
+ author: plugin.author,
326
+ patterns: plugin.compiledPatterns ? plugin.compiledPatterns.length : 0,
327
+ active: this._activePlugins.has(name),
328
+ tags: plugin.tags || [],
329
+ installedAt: plugin.installedAt
330
+ }));
331
+ }
332
+
333
+ /**
334
+ * Search plugins by keyword.
335
+ * @param {string} query
336
+ * @returns {Array<object>}
337
+ */
338
+ search(query) {
339
+ const lower = query.toLowerCase();
340
+ return this.list().filter(p =>
341
+ p.name.includes(lower) ||
342
+ p.description.toLowerCase().includes(lower) ||
343
+ (p.tags && p.tags.some(t => t.toLowerCase().includes(lower)))
344
+ );
345
+ }
346
+
347
+ /**
348
+ * Scan text against all active plugin patterns.
349
+ * @param {string} text - Text to scan.
350
+ * @returns {object} { threats: Array, pluginsUsed: number }
351
+ */
352
+ scan(text) {
353
+ if (!text || text.length < 5) return { threats: [], pluginsUsed: 0 };
354
+
355
+ const threats = [];
356
+ let pluginsUsed = 0;
357
+
358
+ for (const name of this._activePlugins) {
359
+ const plugin = this._plugins.get(name);
360
+ if (!plugin || !plugin.compiledPatterns) continue;
361
+
362
+ pluginsUsed++;
363
+ for (const pattern of plugin.compiledPatterns) {
364
+ try {
365
+ if (pattern.regex.test(text)) {
366
+ threats.push({
367
+ severity: pattern.severity || 'medium',
368
+ category: pattern.category || 'plugin_detection',
369
+ description: pattern.description || `Detected by plugin: ${name}`,
370
+ detail: `Plugin "${name}" v${plugin.version}: ${pattern.detail || pattern.description || 'Pattern matched'}`,
371
+ plugin: name
372
+ });
373
+ }
374
+ } catch (e) {
375
+ // Skip broken patterns
376
+ }
377
+ }
378
+ }
379
+
380
+ return { threats, pluginsUsed };
381
+ }
382
+
383
+ /**
384
+ * Get registry statistics.
385
+ * @returns {object}
386
+ */
387
+ getStats() {
388
+ let totalPatterns = 0;
389
+ for (const plugin of this._plugins.values()) {
390
+ totalPatterns += (plugin.compiledPatterns || []).length;
391
+ }
392
+
393
+ return {
394
+ totalPlugins: this._plugins.size,
395
+ activePlugins: this._activePlugins.size,
396
+ totalPatterns,
397
+ installHistory: this._installHistory.length
398
+ };
399
+ }
400
+
401
+ /** @private */
402
+ _compilePatterns(manifest) {
403
+ if (!manifest.patterns || !Array.isArray(manifest.patterns)) return [];
404
+
405
+ return manifest.patterns.map(p => {
406
+ let regex;
407
+ try {
408
+ if (p.regex instanceof RegExp) {
409
+ regex = p.regex;
410
+ } else if (typeof p.regex === 'string') {
411
+ regex = new RegExp(p.regex, 'i');
412
+ } else if (typeof p.pattern === 'string') {
413
+ regex = new RegExp(p.pattern, 'i');
414
+ } else {
415
+ return null;
416
+ }
417
+ } catch (e) {
418
+ return null;
419
+ }
420
+
421
+ return {
422
+ regex,
423
+ severity: p.severity || 'medium',
424
+ category: p.category || 'plugin_detection',
425
+ description: p.description || '',
426
+ detail: p.detail || ''
427
+ };
428
+ }).filter(Boolean);
429
+ }
430
+
431
+ /** @private */
432
+ _checksum(str) {
433
+ return crypto.createHash('sha256').update(str).digest('hex').substring(0, 16);
434
+ }
435
+
436
+ /** @private */
437
+ _loadFromDir() {
438
+ if (!this.pluginDir || !fs.existsSync(this.pluginDir)) return;
439
+
440
+ try {
441
+ const files = fs.readdirSync(this.pluginDir).filter(f => f.endsWith('.json'));
442
+ for (const file of files) {
443
+ try {
444
+ const content = fs.readFileSync(path.join(this.pluginDir, file), 'utf-8');
445
+ const manifest = JSON.parse(content);
446
+ this.register(manifest);
447
+ } catch (e) {
448
+ console.warn('[Agent Shield] Failed to load plugin %s: %s', file, e.message);
449
+ }
450
+ }
451
+ } catch (e) {
452
+ console.warn('[Agent Shield] Failed to read plugin directory: %s', e.message);
453
+ }
454
+ }
455
+
456
+ /** @private */
457
+ _savePlugin(name, manifest) {
458
+ if (!this.pluginDir) return;
459
+ try {
460
+ if (!fs.existsSync(this.pluginDir)) {
461
+ fs.mkdirSync(this.pluginDir, { recursive: true });
462
+ }
463
+ fs.writeFileSync(
464
+ path.join(this.pluginDir, `${name}.json`),
465
+ JSON.stringify(manifest, null, 2)
466
+ );
467
+ } catch (e) {
468
+ console.warn('[Agent Shield] Failed to save plugin %s: %s', name, e.message);
469
+ }
470
+ }
471
+ }
472
+
473
+ // =========================================================================
474
+ // MARKETPLACE CLIENT
475
+ // =========================================================================
476
+
477
+ /**
478
+ * Client for discovering and fetching plugins from a remote marketplace.
479
+ */
480
+ class MarketplaceClient {
481
+ /**
482
+ * @param {object} [options]
483
+ * @param {string} [options.registryUrl] - Base URL of the plugin registry API.
484
+ * @param {number} [options.timeoutMs=10000] - Request timeout.
485
+ * @param {PluginRegistry} [options.registry] - Local registry to install into.
486
+ */
487
+ constructor(options = {}) {
488
+ this.registryUrl = options.registryUrl || null;
489
+ this.timeoutMs = options.timeoutMs || 10000;
490
+ this.registry = options.registry || null;
491
+ this._cache = new Map();
492
+
493
+ console.log('[Agent Shield] MarketplaceClient initialized (registry: %s)', this.registryUrl || 'none');
494
+ }
495
+
496
+ /**
497
+ * Fetch available plugins from the marketplace.
498
+ * @param {object} [filters] - { category, minScore, query }
499
+ * @returns {Promise<Array<object>>} List of available plugins.
500
+ */
501
+ async browse(filters = {}) {
502
+ if (!this.registryUrl) {
503
+ return { plugins: [], error: 'No registry URL configured.' };
504
+ }
505
+
506
+ try {
507
+ const url = new URL('/api/plugins', this.registryUrl);
508
+ if (filters.category) url.searchParams.set('category', filters.category);
509
+ if (filters.query) url.searchParams.set('q', filters.query);
510
+ if (filters.minScore) url.searchParams.set('minScore', filters.minScore);
511
+
512
+ const response = await this._fetch(url.toString());
513
+ return { plugins: response.plugins || response || [], error: null };
514
+ } catch (err) {
515
+ return { plugins: [], error: err.message };
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Install a plugin from the marketplace by name.
521
+ * @param {string} name - Plugin name.
522
+ * @returns {Promise<object>} { success: boolean, plugin?: object, error?: string }
523
+ */
524
+ async install(name) {
525
+ if (!this.registryUrl) {
526
+ return { success: false, error: 'No registry URL configured.' };
527
+ }
528
+ if (!this.registry) {
529
+ return { success: false, error: 'No local registry configured.' };
530
+ }
531
+
532
+ try {
533
+ const url = new URL(`/api/plugins/${encodeURIComponent(name)}`, this.registryUrl);
534
+ const manifest = await this._fetch(url.toString());
535
+
536
+ const result = this.registry.register(manifest);
537
+ return result;
538
+ } catch (err) {
539
+ return { success: false, error: err.message };
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Publish a plugin to the marketplace.
545
+ * @param {object} manifest - Plugin manifest.
546
+ * @returns {Promise<object>} { success: boolean, error?: string }
547
+ */
548
+ async publish(manifest) {
549
+ if (!this.registryUrl) {
550
+ return { success: false, error: 'No registry URL configured.' };
551
+ }
552
+
553
+ const validator = new PluginValidator();
554
+ const validation = validator.validate(manifest);
555
+ if (!validation.valid) {
556
+ return { success: false, error: `Validation failed: ${validation.errors.join('; ')}`, validation };
557
+ }
558
+
559
+ try {
560
+ const url = new URL('/api/plugins', this.registryUrl);
561
+ await this._post(url.toString(), manifest);
562
+ return { success: true, validation };
563
+ } catch (err) {
564
+ return { success: false, error: err.message };
565
+ }
566
+ }
567
+
568
+ /** @private */
569
+ _fetch(url) {
570
+ return new Promise((resolve, reject) => {
571
+ const parsed = new URL(url);
572
+ const lib = parsed.protocol === 'https:' ? https : http;
573
+
574
+ const req = lib.get({
575
+ hostname: parsed.hostname,
576
+ port: parsed.port,
577
+ path: parsed.pathname + parsed.search,
578
+ timeout: this.timeoutMs
579
+ }, (res) => {
580
+ let data = '';
581
+ res.on('data', chunk => { data += chunk; });
582
+ res.on('end', () => {
583
+ try { resolve(JSON.parse(data)); }
584
+ catch (e) { reject(new Error('Invalid JSON response')); }
585
+ });
586
+ });
587
+
588
+ req.on('error', reject);
589
+ req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
590
+ });
591
+ }
592
+
593
+ /** @private */
594
+ _post(url, body) {
595
+ return new Promise((resolve, reject) => {
596
+ const parsed = new URL(url);
597
+ const lib = parsed.protocol === 'https:' ? https : http;
598
+ const payload = JSON.stringify(body);
599
+
600
+ const req = lib.request({
601
+ hostname: parsed.hostname,
602
+ port: parsed.port,
603
+ path: parsed.pathname,
604
+ method: 'POST',
605
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
606
+ timeout: this.timeoutMs
607
+ }, (res) => {
608
+ let data = '';
609
+ res.on('data', chunk => { data += chunk; });
610
+ res.on('end', () => {
611
+ try { resolve(JSON.parse(data)); }
612
+ catch (e) { reject(new Error('Invalid JSON response')); }
613
+ });
614
+ });
615
+
616
+ req.on('error', reject);
617
+ req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
618
+ req.write(payload);
619
+ req.end();
620
+ });
621
+ }
622
+ }
623
+
624
+ // =========================================================================
625
+ // EXPORTS
626
+ // =========================================================================
627
+
628
+ module.exports = { PluginRegistry, PluginValidator, MarketplaceClient, REQUIRED_FIELDS };