loki-mode 7.49.0 → 7.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/prd-analyzer.py +215 -1
- package/autonomy/prd-checklist.sh +315 -0
- package/autonomy/run.sh +124 -0
- package/autonomy/spec-interrogation.sh +224 -4
- package/autonomy/spec.sh +25 -16
- package/autonomy/verify.sh +108 -26
- package/dashboard/__init__.py +1 -1
- package/dashboard/audit.py +202 -21
- package/docs/INSTALLATION.md +2 -2
- package/docs/siem-integration.md +102 -0
- package/loki-ts/dist/loki.js +231 -230
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/references/invariant-checks.md +109 -0
- package/src/audit/crosslink.js +413 -0
- package/src/audit/index.js +32 -0
- package/src/observability/siem-export.js +424 -0
- package/src/policies/cost.js +270 -1
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SIEM event export for audit/security events.
|
|
5
|
+
*
|
|
6
|
+
* Two well-specified, vendor-agnostic export formats:
|
|
7
|
+
* 1. CEF (Common Event Format) - ArcSight/QRadar/most SIEMs accept it.
|
|
8
|
+
* 2. Splunk HEC (HTTP Event Collector) JSON - Splunk's native ingest API.
|
|
9
|
+
*
|
|
10
|
+
* Design mirrors src/observability/otel.js:
|
|
11
|
+
* - Zero egress unless an endpoint env var is configured (auto-detect, no
|
|
12
|
+
* required flags).
|
|
13
|
+
* - SSRF-safe endpoint validation (only http:/https:, same guard the
|
|
14
|
+
* OTLPExporter uses).
|
|
15
|
+
* - Network failures are logged, never thrown: observability must never
|
|
16
|
+
* break the application.
|
|
17
|
+
*
|
|
18
|
+
* Auto-detected configuration (no flags required):
|
|
19
|
+
* LOKI_SPLUNK_HEC_URL - Splunk HEC collector URL. Presence enables HEC.
|
|
20
|
+
* LOKI_SPLUNK_HEC_TOKEN - Splunk HEC auth token (sent as "Splunk <token>").
|
|
21
|
+
* LOKI_SPLUNK_HEC_INDEX - optional Splunk index name.
|
|
22
|
+
* LOKI_SPLUNK_HEC_SOURCETYPE - optional sourcetype (default loki:audit).
|
|
23
|
+
* LOKI_CEF_VENDOR / LOKI_CEF_PRODUCT - override CEF vendor/product fields.
|
|
24
|
+
*
|
|
25
|
+
* GitHub Enterprise SAML SSO event ingestion is intentionally NOT implemented
|
|
26
|
+
* here. It is a docs-only follow-up (see docs/siem-integration.md): it requires
|
|
27
|
+
* an outbound API client with org-scoped admin tokens and lives outside the
|
|
28
|
+
* local audit path, so no code is warranted yet.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Version (matches the OTEL scope-version pattern)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
let _version = '0.0.0';
|
|
38
|
+
try {
|
|
39
|
+
const pkg = require(path.join(__dirname, '..', '..', 'package.json'));
|
|
40
|
+
_version = pkg.version || '0.0.0';
|
|
41
|
+
} catch (_) {
|
|
42
|
+
// Fallback if package.json is not found
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// CEF severity mapping
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
// CEF severity is 0-10. Map common audit levels into that range.
|
|
50
|
+
const CEF_SEVERITY = {
|
|
51
|
+
debug: 1,
|
|
52
|
+
info: 3,
|
|
53
|
+
notice: 4,
|
|
54
|
+
warning: 6,
|
|
55
|
+
warn: 6,
|
|
56
|
+
error: 8,
|
|
57
|
+
critical: 9,
|
|
58
|
+
alert: 10,
|
|
59
|
+
emergency: 10,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function cefSeverityFor(entry) {
|
|
63
|
+
// Failed events are elevated regardless of declared level.
|
|
64
|
+
if (entry && entry.success === false) {
|
|
65
|
+
return 8;
|
|
66
|
+
}
|
|
67
|
+
const level = String((entry && (entry.level || entry.severity)) || 'info').toLowerCase();
|
|
68
|
+
return Object.prototype.hasOwnProperty.call(CEF_SEVERITY, level)
|
|
69
|
+
? CEF_SEVERITY[level]
|
|
70
|
+
: 3;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// CEF (Common Event Format) formatter
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
// CEF header pipes must be escaped with a backslash. Backslash itself too.
|
|
78
|
+
function escapeCefHeader(value) {
|
|
79
|
+
return String(value == null ? '' : value)
|
|
80
|
+
.replace(/\\/g, '\\\\')
|
|
81
|
+
.replace(/\|/g, '\\|')
|
|
82
|
+
// Newlines would break the single-line record; collapse to spaces.
|
|
83
|
+
.replace(/[\r\n]+/g, ' ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// CEF extension values escape backslash, equals, and newlines (not pipes).
|
|
87
|
+
function escapeCefExtension(value) {
|
|
88
|
+
return String(value == null ? '' : value)
|
|
89
|
+
.replace(/\\/g, '\\\\')
|
|
90
|
+
.replace(/=/g, '\\=')
|
|
91
|
+
.replace(/\r/g, '\\r')
|
|
92
|
+
.replace(/\n/g, '\\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Flatten the audit "details" object into dotted keys for CEF extension space.
|
|
97
|
+
* Bounded depth to avoid pathological nesting.
|
|
98
|
+
*/
|
|
99
|
+
function flattenDetails(obj, prefix, out, depth) {
|
|
100
|
+
if (depth > 4 || obj == null || typeof obj !== 'object') return out;
|
|
101
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
102
|
+
const key = prefix ? prefix + '.' + k : k;
|
|
103
|
+
if (v != null && typeof v === 'object' && !Array.isArray(v)) {
|
|
104
|
+
flattenDetails(v, key, out, depth + 1);
|
|
105
|
+
} else {
|
|
106
|
+
out[key] = Array.isArray(v) ? v.join(',') : v;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Convert a Loki audit entry into a single-line CEF record.
|
|
114
|
+
*
|
|
115
|
+
* Format:
|
|
116
|
+
* CEF:0|Vendor|Product|Version|SignatureID|Name|Severity|Extension
|
|
117
|
+
*
|
|
118
|
+
* @param {object} entry - audit entry (audit.py log_event shape or generic).
|
|
119
|
+
* @param {object} [opts] - { vendor, product, version }
|
|
120
|
+
* @returns {string} a single-line CEF record (no trailing newline).
|
|
121
|
+
*/
|
|
122
|
+
function toCEF(entry, opts) {
|
|
123
|
+
const o = opts || {};
|
|
124
|
+
const vendor = o.vendor || process.env.LOKI_CEF_VENDOR || 'Autonomi';
|
|
125
|
+
const product = o.product || process.env.LOKI_CEF_PRODUCT || 'Loki Mode';
|
|
126
|
+
const version = o.version || _version;
|
|
127
|
+
|
|
128
|
+
const e = entry || {};
|
|
129
|
+
// Signature id and name come from the action; fall back to "event".
|
|
130
|
+
const signatureId = e.action || e.event || 'event';
|
|
131
|
+
const name = e.action || e.event || 'Loki audit event';
|
|
132
|
+
const severity = cefSeverityFor(e);
|
|
133
|
+
|
|
134
|
+
const header =
|
|
135
|
+
'CEF:0|' +
|
|
136
|
+
escapeCefHeader(vendor) + '|' +
|
|
137
|
+
escapeCefHeader(product) + '|' +
|
|
138
|
+
escapeCefHeader(version) + '|' +
|
|
139
|
+
escapeCefHeader(signatureId) + '|' +
|
|
140
|
+
escapeCefHeader(name) + '|' +
|
|
141
|
+
String(severity);
|
|
142
|
+
|
|
143
|
+
// Build the extension key=value space. Use CEF standard keys where possible.
|
|
144
|
+
const ext = {};
|
|
145
|
+
if (e.timestamp) ext.rt = e.timestamp;
|
|
146
|
+
if (e.user_id) ext.suser = e.user_id;
|
|
147
|
+
if (e.ip_address) ext.src = e.ip_address;
|
|
148
|
+
if (e.resource_type) ext.cs1 = e.resource_type;
|
|
149
|
+
if (e.resource_type) ext.cs1Label = 'resourceType';
|
|
150
|
+
if (e.resource_id) ext.cs2 = e.resource_id;
|
|
151
|
+
if (e.resource_id) ext.cs2Label = 'resourceId';
|
|
152
|
+
if (e.token_id) ext.cs3 = e.token_id;
|
|
153
|
+
if (e.token_id) ext.cs3Label = 'tokenId';
|
|
154
|
+
if (typeof e.success === 'boolean') ext.outcome = e.success ? 'success' : 'failure';
|
|
155
|
+
if (e.error) ext.msg = e.error;
|
|
156
|
+
|
|
157
|
+
// Flatten details under a "loki." namespace so they survive round-trips.
|
|
158
|
+
if (e.details && typeof e.details === 'object') {
|
|
159
|
+
flattenDetails(e.details, 'loki', ext, 0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const extStr = Object.entries(ext)
|
|
163
|
+
.map(([k, v]) => escapeCefExtension(k) + '=' + escapeCefExtension(v))
|
|
164
|
+
.join(' ');
|
|
165
|
+
|
|
166
|
+
return extStr ? header + '|' + extStr : header + '|';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Splunk HEC JSON formatter
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Convert a Loki audit entry into a Splunk HEC event envelope.
|
|
175
|
+
*
|
|
176
|
+
* HEC expects: { time, host, source, sourcetype, index, event }
|
|
177
|
+
* where "time" is epoch SECONDS (float allowed).
|
|
178
|
+
*
|
|
179
|
+
* @param {object} entry - audit entry.
|
|
180
|
+
* @param {object} [opts] - { sourcetype, index, host, source }
|
|
181
|
+
* @returns {object} the HEC envelope object (JSON-serializable).
|
|
182
|
+
*/
|
|
183
|
+
function toHEC(entry, opts) {
|
|
184
|
+
const o = opts || {};
|
|
185
|
+
const e = entry || {};
|
|
186
|
+
|
|
187
|
+
let epochSeconds = Date.now() / 1000;
|
|
188
|
+
if (e.timestamp) {
|
|
189
|
+
const parsed = Date.parse(e.timestamp);
|
|
190
|
+
if (!Number.isNaN(parsed)) {
|
|
191
|
+
epochSeconds = parsed / 1000;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const envelope = {
|
|
196
|
+
time: epochSeconds,
|
|
197
|
+
source: o.source || 'loki-mode',
|
|
198
|
+
sourcetype: o.sourcetype || process.env.LOKI_SPLUNK_HEC_SOURCETYPE || 'loki:audit',
|
|
199
|
+
event: e,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const host = o.host || process.env.LOKI_SERVICE_NAME;
|
|
203
|
+
if (host) envelope.host = host;
|
|
204
|
+
|
|
205
|
+
const index = o.index || process.env.LOKI_SPLUNK_HEC_INDEX;
|
|
206
|
+
if (index) envelope.index = index;
|
|
207
|
+
|
|
208
|
+
return envelope;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// SSRF-safe endpoint validation (mirrors OTLPExporter constructor guard)
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Validate that an endpoint URL is safe to POST to.
|
|
217
|
+
* Only http: and https: schemes are permitted, matching the OTEL guard.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} endpoint
|
|
220
|
+
* @returns {URL} the parsed URL
|
|
221
|
+
* @throws {Error} if the scheme is not http:/https: or the URL is malformed.
|
|
222
|
+
*/
|
|
223
|
+
function validateEndpoint(endpoint) {
|
|
224
|
+
// Throws TypeError on malformed input; let it propagate to the caller.
|
|
225
|
+
const parsed = new URL(endpoint);
|
|
226
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Invalid SIEM endpoint scheme "${parsed.protocol}". Only http: and https: are allowed.`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return parsed;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Splunk HEC sender
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
function _defaultErrorHandler(err) {
|
|
239
|
+
process.stderr.write(`[loki-siem] HEC export error: ${err && (err.message || err.code) || String(err)}\n`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Splunk HEC sender. Constructed only when an endpoint is configured.
|
|
244
|
+
* No constructor side effects beyond validating the endpoint.
|
|
245
|
+
*/
|
|
246
|
+
class HECSender {
|
|
247
|
+
constructor(endpoint, token, opts) {
|
|
248
|
+
// SSRF guard up front. Mirrors OTLPExporter.
|
|
249
|
+
this._url = validateEndpoint(endpoint);
|
|
250
|
+
this._token = token || '';
|
|
251
|
+
const o = opts || {};
|
|
252
|
+
this._sourcetype = o.sourcetype;
|
|
253
|
+
this._index = o.index;
|
|
254
|
+
this._host = o.host;
|
|
255
|
+
this._source = o.source;
|
|
256
|
+
this._errorHandler = o.errorHandler || _defaultErrorHandler;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Send a single audit entry to Splunk HEC. Fire-and-forget.
|
|
261
|
+
* Returns the serialized body (for testing/inspection).
|
|
262
|
+
*/
|
|
263
|
+
send(entry) {
|
|
264
|
+
const envelope = toHEC(entry, {
|
|
265
|
+
sourcetype: this._sourcetype,
|
|
266
|
+
index: this._index,
|
|
267
|
+
host: this._host,
|
|
268
|
+
source: this._source,
|
|
269
|
+
});
|
|
270
|
+
const body = JSON.stringify(envelope);
|
|
271
|
+
this._post(body);
|
|
272
|
+
return body;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_post(body) {
|
|
276
|
+
const isHttps = this._url.protocol === 'https:';
|
|
277
|
+
const httpModule = isHttps ? require('https') : require('http');
|
|
278
|
+
|
|
279
|
+
const headers = {
|
|
280
|
+
'Content-Type': 'application/json',
|
|
281
|
+
'Content-Length': Buffer.byteLength(body),
|
|
282
|
+
};
|
|
283
|
+
if (this._token) {
|
|
284
|
+
headers.Authorization = `Splunk ${this._token}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const options = {
|
|
288
|
+
hostname: this._url.hostname,
|
|
289
|
+
port: this._url.port || (isHttps ? 443 : 80),
|
|
290
|
+
path: this._url.pathname + (this._url.search || ''),
|
|
291
|
+
method: 'POST',
|
|
292
|
+
headers,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
let req;
|
|
296
|
+
try {
|
|
297
|
+
req = httpModule.request(options, (res) => {
|
|
298
|
+
res.resume(); // drain to free the socket
|
|
299
|
+
});
|
|
300
|
+
} catch (err) {
|
|
301
|
+
// Synchronous construction errors must never escape.
|
|
302
|
+
try { this._errorHandler(err); } catch (_) { /* swallow */ }
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
req.on('error', (err) => {
|
|
307
|
+
// Observability must never break the application.
|
|
308
|
+
try { this._errorHandler(err); } catch (_) { /* swallow */ }
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
req.write(body);
|
|
312
|
+
req.end();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Auto-detection: build a sender only when an endpoint env var is present.
|
|
318
|
+
// No env var configured => null => zero egress.
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Build a Splunk HEC sender from the environment, or return null when no HEC
|
|
323
|
+
* endpoint is configured. This is the "no egress unless configured" gate.
|
|
324
|
+
*
|
|
325
|
+
* @param {object} [env] - environment override (defaults to process.env).
|
|
326
|
+
* @returns {HECSender|null}
|
|
327
|
+
*/
|
|
328
|
+
function createHECSenderFromEnv(env) {
|
|
329
|
+
const e = env || process.env;
|
|
330
|
+
const url = (e.LOKI_SPLUNK_HEC_URL || '').trim();
|
|
331
|
+
if (!url) return null; // Not configured: never send.
|
|
332
|
+
return new HECSender(url, (e.LOKI_SPLUNK_HEC_TOKEN || '').trim(), {
|
|
333
|
+
sourcetype: e.LOKI_SPLUNK_HEC_SOURCETYPE,
|
|
334
|
+
index: e.LOKI_SPLUNK_HEC_INDEX,
|
|
335
|
+
host: e.LOKI_SERVICE_NAME,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Whether any SIEM exporter is configured via the environment.
|
|
341
|
+
* @param {object} [env]
|
|
342
|
+
* @returns {boolean}
|
|
343
|
+
*/
|
|
344
|
+
function isConfigured(env) {
|
|
345
|
+
const e = env || process.env;
|
|
346
|
+
return Boolean((e.LOKI_SPLUNK_HEC_URL || '').trim());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Ready-to-use OTEL collector templates for popular vendors.
|
|
351
|
+
//
|
|
352
|
+
// These are NOT egress: they are env-var recipes a user copies. Each returns
|
|
353
|
+
// the set of env vars to export so the existing OTEL bridge (otel.js) ships to
|
|
354
|
+
// that vendor's OTLP/HTTP endpoint. Region/site and API key are injected by
|
|
355
|
+
// the caller. Documented in docs/siem-integration.md.
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
const OTEL_TEMPLATES = {
|
|
359
|
+
/**
|
|
360
|
+
* Datadog OTLP intake. Datadog accepts OTLP/HTTP at its agent or, with the
|
|
361
|
+
* OTel collector contrib exporter, directly. The common path is the Datadog
|
|
362
|
+
* Agent's OTLP receiver on :4318. For Agentless intake use the collector.
|
|
363
|
+
* site examples: datadoghq.com, datadoghq.eu, us5.datadoghq.com
|
|
364
|
+
*/
|
|
365
|
+
datadog(opts) {
|
|
366
|
+
const o = opts || {};
|
|
367
|
+
const endpoint = o.endpoint || 'http://localhost:4318';
|
|
368
|
+
const vars = {
|
|
369
|
+
LOKI_OTEL_ENDPOINT: endpoint,
|
|
370
|
+
LOKI_SERVICE_NAME: o.serviceName || 'loki-mode',
|
|
371
|
+
};
|
|
372
|
+
// Header is honored by the @opentelemetry exporter when the real SDK path
|
|
373
|
+
// is active. The built-in JSON exporter posts to a local agent that holds
|
|
374
|
+
// the API key, so the header is optional there.
|
|
375
|
+
if (o.apiKey) {
|
|
376
|
+
vars.OTEL_EXPORTER_OTLP_HEADERS = `dd-api-key=${o.apiKey}`;
|
|
377
|
+
}
|
|
378
|
+
if (o.site) {
|
|
379
|
+
vars.OTEL_RESOURCE_ATTRIBUTES =
|
|
380
|
+
`deployment.environment=${o.environment || 'production'},dd.site=${o.site}`;
|
|
381
|
+
}
|
|
382
|
+
return vars;
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Honeycomb OTLP/HTTP. Honeycomb ingests OTLP directly at
|
|
387
|
+
* https://api.honeycomb.io (or api.eu1.honeycomb.io). Auth via the
|
|
388
|
+
* x-honeycomb-team header; dataset via x-honeycomb-dataset for metrics.
|
|
389
|
+
*/
|
|
390
|
+
honeycomb(opts) {
|
|
391
|
+
const o = opts || {};
|
|
392
|
+
const endpoint = o.endpoint || 'https://api.honeycomb.io';
|
|
393
|
+
const headers = [];
|
|
394
|
+
if (o.apiKey) headers.push(`x-honeycomb-team=${o.apiKey}`);
|
|
395
|
+
if (o.dataset) headers.push(`x-honeycomb-dataset=${o.dataset}`);
|
|
396
|
+
const vars = {
|
|
397
|
+
LOKI_OTEL_ENDPOINT: endpoint,
|
|
398
|
+
LOKI_SERVICE_NAME: o.serviceName || 'loki-mode',
|
|
399
|
+
};
|
|
400
|
+
if (headers.length) {
|
|
401
|
+
vars.OTEL_EXPORTER_OTLP_HEADERS = headers.join(',');
|
|
402
|
+
}
|
|
403
|
+
return vars;
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
module.exports = {
|
|
408
|
+
// Formatters
|
|
409
|
+
toCEF,
|
|
410
|
+
toHEC,
|
|
411
|
+
cefSeverityFor,
|
|
412
|
+
escapeCefHeader,
|
|
413
|
+
escapeCefExtension,
|
|
414
|
+
// Endpoint safety
|
|
415
|
+
validateEndpoint,
|
|
416
|
+
// HEC sender
|
|
417
|
+
HECSender,
|
|
418
|
+
createHECSenderFromEnv,
|
|
419
|
+
isConfigured,
|
|
420
|
+
// OTEL vendor templates
|
|
421
|
+
OTEL_TEMPLATES,
|
|
422
|
+
// Version (for testing)
|
|
423
|
+
_version,
|
|
424
|
+
};
|