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.
- package/CHANGELOG.md +191 -0
- package/LICENSE +21 -0
- package/README.md +975 -0
- package/bin/agent-shield.js +680 -0
- package/package.json +118 -0
- package/src/adaptive.js +330 -0
- package/src/agent-protocol.js +998 -0
- package/src/alert-tuning.js +480 -0
- package/src/allowlist.js +603 -0
- package/src/audit-immutable.js +914 -0
- package/src/audit-streaming.js +469 -0
- package/src/badges.js +196 -0
- package/src/behavior-profiling.js +289 -0
- package/src/benchmark-harness.js +804 -0
- package/src/canary.js +271 -0
- package/src/certification.js +563 -0
- package/src/circuit-breaker.js +321 -0
- package/src/compliance.js +617 -0
- package/src/confidence-tuning.js +324 -0
- package/src/confused-deputy.js +624 -0
- package/src/context-scoring.js +360 -0
- package/src/conversation.js +494 -0
- package/src/cost-optimizer.js +1024 -0
- package/src/ctf.js +462 -0
- package/src/detector-core.js +1999 -0
- package/src/distributed.js +359 -0
- package/src/document-scanner.js +795 -0
- package/src/embedding.js +307 -0
- package/src/encoding.js +429 -0
- package/src/enterprise.js +405 -0
- package/src/errors.js +100 -0
- package/src/eu-ai-act.js +523 -0
- package/src/fuzzer.js +764 -0
- package/src/honeypot.js +328 -0
- package/src/i18n-patterns.js +523 -0
- package/src/index.js +430 -0
- package/src/integrations.js +528 -0
- package/src/llm-redteam.js +670 -0
- package/src/main.js +741 -0
- package/src/main.mjs +38 -0
- package/src/mcp-bridge.js +542 -0
- package/src/mcp-certification.js +846 -0
- package/src/mcp-sdk-integration.js +355 -0
- package/src/mcp-security-runtime.js +741 -0
- package/src/mcp-server.js +740 -0
- package/src/middleware.js +208 -0
- package/src/model-finetuning.js +884 -0
- package/src/model-fingerprint.js +1042 -0
- package/src/multi-agent-trust.js +453 -0
- package/src/multi-agent.js +404 -0
- package/src/multimodal.js +296 -0
- package/src/nist-mapping.js +505 -0
- package/src/observability.js +330 -0
- package/src/openclaw.js +450 -0
- package/src/otel.js +544 -0
- package/src/owasp-2025.js +483 -0
- package/src/pii.js +390 -0
- package/src/plugin-marketplace.js +628 -0
- package/src/plugin-system.js +349 -0
- package/src/policy-dsl.js +775 -0
- package/src/policy-extended.js +635 -0
- package/src/policy.js +443 -0
- package/src/presets.js +409 -0
- package/src/production.js +557 -0
- package/src/prompt-leakage.js +321 -0
- package/src/rag-vulnerability.js +579 -0
- package/src/redteam.js +475 -0
- package/src/response-handler.js +429 -0
- package/src/scanners.js +357 -0
- package/src/self-healing.js +363 -0
- package/src/semantic.js +339 -0
- package/src/shield-score.js +250 -0
- package/src/sso-saml.js +897 -0
- package/src/stream-scanner.js +806 -0
- package/src/testing.js +505 -0
- package/src/threat-encyclopedia.js +629 -0
- package/src/threat-intel-network.js +1017 -0
- package/src/token-analysis.js +467 -0
- package/src/tool-guard.js +412 -0
- package/src/tool-output-validator.js +354 -0
- package/src/utils.js +83 -0
- package/src/watermark.js +235 -0
- package/src/worker-scanner.js +601 -0
- package/types/index.d.ts +2088 -0
package/src/policy.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Policy-as-Code (#23), Structured Logging (#4), and Webhook Alerts (#22)
|
|
5
|
+
*
|
|
6
|
+
* - Policy Engine: JSON config files defining security policies for an agent.
|
|
7
|
+
* - Structured Logger: JSON-formatted threat events for SIEM/dashboards.
|
|
8
|
+
* - Webhooks: Real-time notifications to Slack, Discord, HTTP endpoints.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { AgentShield } = require('./index');
|
|
12
|
+
const { CircuitBreaker, RateLimiter } = require('./circuit-breaker');
|
|
13
|
+
const { PermissionBoundary } = require('./tool-guard');
|
|
14
|
+
const { PIIRedactor, DLPEngine, ContentPolicy } = require('./pii');
|
|
15
|
+
|
|
16
|
+
// =========================================================================
|
|
17
|
+
// POLICY ENGINE
|
|
18
|
+
// =========================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a fully configured AgentShield from a JSON policy object.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} policy - The policy configuration.
|
|
24
|
+
* @returns {object} { shield, circuitBreaker, rateLimiter, permissions, piiRedactor, dlp, contentPolicy }
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const policy = {
|
|
28
|
+
* sensitivity: 'high',
|
|
29
|
+
* blockOnThreat: true,
|
|
30
|
+
* blockThreshold: 'high',
|
|
31
|
+
* circuitBreaker: { threshold: 5, windowMs: 60000 },
|
|
32
|
+
* rateLimiter: { maxRequests: 100, windowMs: 60000 },
|
|
33
|
+
* permissions: {
|
|
34
|
+
* allowedTools: ['search', 'calculator'],
|
|
35
|
+
* tools: {
|
|
36
|
+
* search: { blockArgs: ['password', 'secret'] }
|
|
37
|
+
* }
|
|
38
|
+
* },
|
|
39
|
+
* pii: { categories: ['email', 'ssn', 'credit_card'] },
|
|
40
|
+
* dlp: {
|
|
41
|
+
* rules: [
|
|
42
|
+
* { name: 'internal_project', pattern: 'Project\\s+Phoenix', action: 'block' }
|
|
43
|
+
* ]
|
|
44
|
+
* },
|
|
45
|
+
* contentPolicy: { blockedCategories: ['medical_advice', 'legal_advice'] }
|
|
46
|
+
* };
|
|
47
|
+
*
|
|
48
|
+
* const stack = loadPolicy(policy);
|
|
49
|
+
*/
|
|
50
|
+
const loadPolicy = (policy) => {
|
|
51
|
+
const stack = {};
|
|
52
|
+
|
|
53
|
+
// Core shield
|
|
54
|
+
stack.shield = new AgentShield({
|
|
55
|
+
sensitivity: policy.sensitivity || 'medium',
|
|
56
|
+
blockOnThreat: policy.blockOnThreat !== undefined ? policy.blockOnThreat : false,
|
|
57
|
+
blockThreshold: policy.blockThreshold || 'high',
|
|
58
|
+
logging: policy.logging || false,
|
|
59
|
+
dangerousTools: policy.dangerousTools,
|
|
60
|
+
sensitiveFilePatterns: policy.sensitiveFilePatterns
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Circuit breaker
|
|
64
|
+
if (policy.circuitBreaker) {
|
|
65
|
+
stack.circuitBreaker = new CircuitBreaker(policy.circuitBreaker);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Rate limiter
|
|
69
|
+
if (policy.rateLimiter) {
|
|
70
|
+
stack.rateLimiter = new RateLimiter(policy.rateLimiter);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Permission boundaries
|
|
74
|
+
if (policy.permissions) {
|
|
75
|
+
stack.permissions = new PermissionBoundary({
|
|
76
|
+
allowedTools: policy.permissions.allowedTools,
|
|
77
|
+
blockedTools: policy.permissions.blockedTools
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (policy.permissions.tools) {
|
|
81
|
+
for (const [toolName, perms] of Object.entries(policy.permissions.tools)) {
|
|
82
|
+
stack.permissions.defineTool(toolName, perms);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// PII redaction
|
|
88
|
+
if (policy.pii) {
|
|
89
|
+
stack.piiRedactor = new PIIRedactor(policy.pii);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// DLP
|
|
93
|
+
if (policy.dlp) {
|
|
94
|
+
stack.dlp = new DLPEngine();
|
|
95
|
+
if (policy.dlp.rules) {
|
|
96
|
+
for (const rule of policy.dlp.rules) {
|
|
97
|
+
stack.dlp.addRule(rule);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Content policy
|
|
103
|
+
if (policy.contentPolicy) {
|
|
104
|
+
stack.contentPolicy = new ContentPolicy(policy.contentPolicy);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return stack;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Loads a policy from a JSON file path.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} filePath - Path to JSON policy file.
|
|
114
|
+
* @returns {object} Configured security stack.
|
|
115
|
+
*/
|
|
116
|
+
const loadPolicyFile = (filePath) => {
|
|
117
|
+
const fs = require('fs');
|
|
118
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
119
|
+
const policy = JSON.parse(content);
|
|
120
|
+
return loadPolicy(policy);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// =========================================================================
|
|
124
|
+
// STRUCTURED LOGGER
|
|
125
|
+
// =========================================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Log levels for structured logging.
|
|
129
|
+
*/
|
|
130
|
+
const LOG_LEVEL = {
|
|
131
|
+
DEBUG: 'debug',
|
|
132
|
+
INFO: 'info',
|
|
133
|
+
WARN: 'warn',
|
|
134
|
+
ERROR: 'error',
|
|
135
|
+
CRITICAL: 'critical'
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
class StructuredLogger {
|
|
139
|
+
/**
|
|
140
|
+
* @param {object} [options]
|
|
141
|
+
* @param {string} [options.serviceName='agent-shield'] - Service name for log entries.
|
|
142
|
+
* @param {string} [options.environment='production'] - Environment name.
|
|
143
|
+
* @param {Function} [options.transport] - Custom transport function. Receives log entry object.
|
|
144
|
+
* @param {boolean} [options.console=true] - Also log to console.
|
|
145
|
+
* @param {number} [options.maxBuffer=1000] - Max buffered log entries.
|
|
146
|
+
*/
|
|
147
|
+
constructor(options = {}) {
|
|
148
|
+
this.serviceName = options.serviceName || 'agent-shield';
|
|
149
|
+
this.environment = options.environment || 'production';
|
|
150
|
+
this.transport = options.transport || null;
|
|
151
|
+
this.useConsole = options.console !== undefined ? options.console : true;
|
|
152
|
+
this.maxBuffer = options.maxBuffer || 1000;
|
|
153
|
+
this.buffer = [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Logs a structured event.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} level - Log level.
|
|
160
|
+
* @param {string} event - Event name.
|
|
161
|
+
* @param {object} [data={}] - Event data.
|
|
162
|
+
* @returns {object} The log entry.
|
|
163
|
+
*/
|
|
164
|
+
log(level, event, data = {}) {
|
|
165
|
+
const entry = {
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
level,
|
|
168
|
+
service: this.serviceName,
|
|
169
|
+
environment: this.environment,
|
|
170
|
+
event,
|
|
171
|
+
...data
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
this.buffer.push(entry);
|
|
175
|
+
if (this.buffer.length > this.maxBuffer) {
|
|
176
|
+
this.buffer.shift();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.useConsole) {
|
|
180
|
+
const method = level === 'error' || level === 'critical' ? 'error' : level === 'warn' ? 'warn' : 'log';
|
|
181
|
+
console[method](JSON.stringify(entry));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.transport) {
|
|
185
|
+
try { this.transport(entry); } catch (e) { console.error('[Agent Shield] transport callback error:', e.message); }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return entry;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Log a threat detection event. */
|
|
192
|
+
logThreat(scanResult, source) {
|
|
193
|
+
return this.log(
|
|
194
|
+
scanResult.stats.critical > 0 ? LOG_LEVEL.CRITICAL : LOG_LEVEL.WARN,
|
|
195
|
+
'threat_detected',
|
|
196
|
+
{
|
|
197
|
+
source,
|
|
198
|
+
status: scanResult.status,
|
|
199
|
+
threatCount: scanResult.threats.length,
|
|
200
|
+
threats: scanResult.threats.map(t => ({
|
|
201
|
+
severity: t.severity,
|
|
202
|
+
category: t.category,
|
|
203
|
+
description: t.description,
|
|
204
|
+
confidence: t.confidence
|
|
205
|
+
})),
|
|
206
|
+
stats: scanResult.stats
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Log a blocked request. */
|
|
212
|
+
logBlock(reason, source, details = {}) {
|
|
213
|
+
return this.log(LOG_LEVEL.ERROR, 'request_blocked', { reason, source, ...details });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Log a circuit breaker trip. */
|
|
217
|
+
logCircuitBreaker(state, details = {}) {
|
|
218
|
+
return this.log(LOG_LEVEL.CRITICAL, 'circuit_breaker', { state, ...details });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Log a PII redaction. */
|
|
222
|
+
logPIIRedaction(findings, source) {
|
|
223
|
+
return this.log(LOG_LEVEL.WARN, 'pii_redacted', {
|
|
224
|
+
source,
|
|
225
|
+
count: findings.length,
|
|
226
|
+
categories: findings.map(f => f.category)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Log a DLP violation. */
|
|
231
|
+
logDLPViolation(violations, source) {
|
|
232
|
+
return this.log(LOG_LEVEL.ERROR, 'dlp_violation', {
|
|
233
|
+
source,
|
|
234
|
+
violations: violations.map(v => ({ rule: v.rule, action: v.action, severity: v.severity }))
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Returns buffered log entries.
|
|
240
|
+
* @param {object} [filter] - Optional filter.
|
|
241
|
+
* @param {string} [filter.level] - Filter by level.
|
|
242
|
+
* @param {string} [filter.event] - Filter by event name.
|
|
243
|
+
* @param {number} [filter.since] - Filter by timestamp (ms).
|
|
244
|
+
* @returns {Array}
|
|
245
|
+
*/
|
|
246
|
+
getEntries(filter = {}) {
|
|
247
|
+
let entries = [...this.buffer];
|
|
248
|
+
|
|
249
|
+
if (filter.level) {
|
|
250
|
+
entries = entries.filter(e => e.level === filter.level);
|
|
251
|
+
}
|
|
252
|
+
if (filter.event) {
|
|
253
|
+
entries = entries.filter(e => e.event === filter.event);
|
|
254
|
+
}
|
|
255
|
+
if (filter.since) {
|
|
256
|
+
const sinceDate = new Date(filter.since).toISOString();
|
|
257
|
+
entries = entries.filter(e => e.timestamp >= sinceDate);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return entries;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
clear() {
|
|
264
|
+
this.buffer = [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// =========================================================================
|
|
269
|
+
// WEBHOOK ALERTS
|
|
270
|
+
// =========================================================================
|
|
271
|
+
|
|
272
|
+
class WebhookAlert {
|
|
273
|
+
/**
|
|
274
|
+
* @param {object} [options]
|
|
275
|
+
* @param {Array<object>} [options.endpoints=[]] - Webhook endpoints.
|
|
276
|
+
* @param {string} [options.minSeverity='high'] - Minimum severity to trigger alert.
|
|
277
|
+
* @param {number} [options.cooldownMs=60000] - Minimum time between alerts to same endpoint.
|
|
278
|
+
*/
|
|
279
|
+
constructor(options = {}) {
|
|
280
|
+
this.endpoints = options.endpoints || [];
|
|
281
|
+
this.minSeverity = options.minSeverity || 'high';
|
|
282
|
+
this.cooldownMs = options.cooldownMs || 60000;
|
|
283
|
+
this.lastAlertTimes = new Map();
|
|
284
|
+
this.alertHistory = [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Adds a webhook endpoint.
|
|
289
|
+
*
|
|
290
|
+
* @param {object} endpoint
|
|
291
|
+
* @param {string} endpoint.url - Webhook URL.
|
|
292
|
+
* @param {string} [endpoint.type='generic'] - Type: 'generic', 'slack', 'discord'.
|
|
293
|
+
* @param {object} [endpoint.headers={}] - Custom headers.
|
|
294
|
+
* @returns {WebhookAlert} this
|
|
295
|
+
*/
|
|
296
|
+
addEndpoint(endpoint) {
|
|
297
|
+
this.endpoints.push({
|
|
298
|
+
url: endpoint.url,
|
|
299
|
+
type: endpoint.type || 'generic',
|
|
300
|
+
headers: endpoint.headers || {}
|
|
301
|
+
});
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Sends an alert if severity threshold is met and cooldown has elapsed.
|
|
307
|
+
*
|
|
308
|
+
* @param {object} event - The threat event.
|
|
309
|
+
* @param {string} event.severity - Threat severity.
|
|
310
|
+
* @param {string} event.description - What happened.
|
|
311
|
+
* @param {object} [event.details] - Additional details.
|
|
312
|
+
* @returns {Promise<Array>} Results of webhook sends.
|
|
313
|
+
*/
|
|
314
|
+
async alert(event) {
|
|
315
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
316
|
+
const minLevel = severityOrder[this.minSeverity] ?? 1;
|
|
317
|
+
const eventLevel = severityOrder[event.severity] ?? 3;
|
|
318
|
+
|
|
319
|
+
if (eventLevel > minLevel) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const results = [];
|
|
324
|
+
const now = Date.now();
|
|
325
|
+
|
|
326
|
+
for (const endpoint of this.endpoints) {
|
|
327
|
+
// Check cooldown
|
|
328
|
+
const lastAlert = this.lastAlertTimes.get(endpoint.url) || 0;
|
|
329
|
+
if (now - lastAlert < this.cooldownMs) {
|
|
330
|
+
results.push({ url: endpoint.url, sent: false, reason: 'cooldown' });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const payload = this._formatPayload(endpoint.type, event);
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
// Use dynamic import for fetch in Node 18+ or fall back to http
|
|
338
|
+
const response = await this._send(endpoint, payload);
|
|
339
|
+
this.lastAlertTimes.set(endpoint.url, now);
|
|
340
|
+
const record = { url: endpoint.url, sent: true, timestamp: now, event: event.description };
|
|
341
|
+
results.push(record);
|
|
342
|
+
this.alertHistory.push(record);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
results.push({ url: endpoint.url, sent: false, error: err.message });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Cap alert history
|
|
349
|
+
if (this.alertHistory.length > 100) {
|
|
350
|
+
this.alertHistory = this.alertHistory.slice(-100);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Prune stale cooldown entries (older than 2x cooldown period)
|
|
354
|
+
if (this.lastAlertTimes.size > 100) {
|
|
355
|
+
const staleThreshold = now - this.cooldownMs * 2;
|
|
356
|
+
for (const [url, time] of this.lastAlertTimes) {
|
|
357
|
+
if (time < staleThreshold) this.lastAlertTimes.delete(url);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return results;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** @private */
|
|
365
|
+
_formatPayload(type, event) {
|
|
366
|
+
if (type === 'slack') {
|
|
367
|
+
return {
|
|
368
|
+
text: `🛡️ *Agent Shield Alert*`,
|
|
369
|
+
blocks: [
|
|
370
|
+
{
|
|
371
|
+
type: 'section',
|
|
372
|
+
text: {
|
|
373
|
+
type: 'mrkdwn',
|
|
374
|
+
text: `*Severity:* ${event.severity.toUpperCase()}\n*Event:* ${event.description}\n*Time:* ${new Date().toISOString()}`
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
]
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (type === 'discord') {
|
|
382
|
+
return {
|
|
383
|
+
embeds: [{
|
|
384
|
+
title: 'Agent Shield Alert',
|
|
385
|
+
description: event.description,
|
|
386
|
+
color: event.severity === 'critical' ? 0xFF0000 : event.severity === 'high' ? 0xFF8800 : 0xFFCC00,
|
|
387
|
+
fields: [
|
|
388
|
+
{ name: 'Severity', value: event.severity.toUpperCase(), inline: true },
|
|
389
|
+
{ name: 'Time', value: new Date().toISOString(), inline: true }
|
|
390
|
+
]
|
|
391
|
+
}]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Generic
|
|
396
|
+
return {
|
|
397
|
+
service: 'agent-shield',
|
|
398
|
+
severity: event.severity,
|
|
399
|
+
description: event.description,
|
|
400
|
+
details: event.details || {},
|
|
401
|
+
timestamp: new Date().toISOString()
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** @private */
|
|
406
|
+
async _send(endpoint, payload) {
|
|
407
|
+
const https = require('https');
|
|
408
|
+
const http = require('http');
|
|
409
|
+
const url = new URL(endpoint.url);
|
|
410
|
+
const transport = url.protocol === 'https:' ? https : http;
|
|
411
|
+
const body = JSON.stringify(payload);
|
|
412
|
+
|
|
413
|
+
return new Promise((resolve, reject) => {
|
|
414
|
+
const req = transport.request({
|
|
415
|
+
hostname: url.hostname,
|
|
416
|
+
port: url.port,
|
|
417
|
+
path: url.pathname + url.search,
|
|
418
|
+
method: 'POST',
|
|
419
|
+
headers: {
|
|
420
|
+
'Content-Type': 'application/json',
|
|
421
|
+
'Content-Length': Buffer.byteLength(body),
|
|
422
|
+
...endpoint.headers
|
|
423
|
+
},
|
|
424
|
+
timeout: 10000
|
|
425
|
+
}, (res) => {
|
|
426
|
+
let data = '';
|
|
427
|
+
res.on('data', chunk => { data += chunk; });
|
|
428
|
+
res.on('end', () => resolve({ status: res.statusCode, body: data }));
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
req.on('error', reject);
|
|
432
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Webhook timeout')); });
|
|
433
|
+
req.write(body);
|
|
434
|
+
req.end();
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
getHistory() {
|
|
439
|
+
return [...this.alertHistory];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
module.exports = { loadPolicy, loadPolicyFile, StructuredLogger, WebhookAlert, LOG_LEVEL };
|