@mondaydotcomorg/atp-providers 0.19.7 → 0.19.9

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/dist/index.js CHANGED
@@ -1,9 +1,1152 @@
1
- export { MemoryCache } from './cache/memory.js';
2
- export { RedisCache } from './cache/redis.js';
3
- export { FileCache } from './cache/file.js';
4
- export { EnvAuthProvider } from './auth/env.js';
5
- export * from './oauth/index.js';
6
- export { JSONLAuditSink } from './audit/jsonl.js';
7
- export { OpenTelemetryAuditSink } from './audit/opentelemetry.js';
8
- export { OTelCounter, OTelHistogram, OTelSpan, OTelAttribute, METRIC_CONFIGS, OTEL_SERVICE_NAME, OTEL_TRACER_NAME, OTEL_METER_NAME, ATTRIBUTE_PREFIX_TOOL, ATTRIBUTE_PREFIX_METADATA, } from './audit/otel-metrics.js';
1
+ import { log } from '@mondaydotcomorg/atp-runtime';
2
+ import { promises, existsSync } from 'fs';
3
+ import path, { dirname } from 'path';
4
+ import os from 'os';
5
+ import { createHash } from 'crypto';
6
+ import { mkdir, appendFile, readFile } from 'fs/promises';
7
+ import { trace, metrics, context, SpanStatusCode } from '@opentelemetry/api';
8
+
9
+ var __defProp = Object.defineProperty;
10
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
11
+
12
+ // src/cache/memory.ts
13
+ var MemoryCache = class {
14
+ static {
15
+ __name(this, "MemoryCache");
16
+ }
17
+ name = "memory";
18
+ cache;
19
+ maxKeys;
20
+ defaultTTL;
21
+ constructor(options = {}) {
22
+ this.cache = /* @__PURE__ */ new Map();
23
+ this.maxKeys = options.maxKeys || 1e3;
24
+ this.defaultTTL = options.defaultTTL || 3600;
25
+ }
26
+ async get(key) {
27
+ const entry = this.cache.get(key);
28
+ if (!entry) {
29
+ return null;
30
+ }
31
+ if (entry.expiresAt !== -1 && Date.now() > entry.expiresAt) {
32
+ this.cache.delete(key);
33
+ return null;
34
+ }
35
+ this.cache.delete(key);
36
+ this.cache.set(key, entry);
37
+ return entry.value;
38
+ }
39
+ async set(key, value, ttl) {
40
+ if (this.cache.size >= this.maxKeys && !this.cache.has(key)) {
41
+ const firstKey = this.cache.keys().next().value;
42
+ if (firstKey) {
43
+ this.cache.delete(firstKey);
44
+ }
45
+ }
46
+ const expiresAt = ttl ? Date.now() + ttl * 1e3 : this.defaultTTL > 0 ? Date.now() + this.defaultTTL * 1e3 : -1;
47
+ this.cache.set(key, {
48
+ value,
49
+ expiresAt
50
+ });
51
+ }
52
+ async delete(key) {
53
+ this.cache.delete(key);
54
+ }
55
+ async has(key) {
56
+ const value = await this.get(key);
57
+ return value !== null;
58
+ }
59
+ async clear(pattern) {
60
+ if (!pattern) {
61
+ this.cache.clear();
62
+ return;
63
+ }
64
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
65
+ for (const key of this.cache.keys()) {
66
+ if (regex.test(key)) {
67
+ this.cache.delete(key);
68
+ }
69
+ }
70
+ }
71
+ async mget(keys) {
72
+ return Promise.all(keys.map((key) => this.get(key)));
73
+ }
74
+ async mset(entries) {
75
+ for (const [key, value, ttl] of entries) {
76
+ await this.set(key, value, ttl);
77
+ }
78
+ }
79
+ async disconnect() {
80
+ this.cache.clear();
81
+ }
82
+ /** Get cache statistics */
83
+ getStats() {
84
+ return {
85
+ keys: this.cache.size,
86
+ maxKeys: this.maxKeys,
87
+ utilization: this.cache.size / this.maxKeys * 100
88
+ };
89
+ }
90
+ };
91
+ var RedisCache = class {
92
+ static {
93
+ __name(this, "RedisCache");
94
+ }
95
+ name = "redis";
96
+ redis;
97
+ keyPrefix;
98
+ defaultTTL;
99
+ constructor(options) {
100
+ this.redis = options.redis;
101
+ this.keyPrefix = options.keyPrefix || "atp:cache:";
102
+ this.defaultTTL = options.defaultTTL;
103
+ }
104
+ getFullKey(key) {
105
+ return `${this.keyPrefix}${key}`;
106
+ }
107
+ async get(key) {
108
+ try {
109
+ const value = await this.redis.get(this.getFullKey(key));
110
+ if (!value) return null;
111
+ return JSON.parse(value);
112
+ } catch (error) {
113
+ log.error("RedisCache failed to get key", {
114
+ key,
115
+ error: error instanceof Error ? error.message : error
116
+ });
117
+ return null;
118
+ }
119
+ }
120
+ async set(key, value, ttl) {
121
+ try {
122
+ const serialized = JSON.stringify(value);
123
+ const fullKey = this.getFullKey(key);
124
+ const effectiveTTL = ttl ?? this.defaultTTL;
125
+ if (effectiveTTL) {
126
+ await this.redis.setex(fullKey, effectiveTTL, serialized);
127
+ } else {
128
+ await this.redis.set(fullKey, serialized);
129
+ }
130
+ } catch (error) {
131
+ log.error("RedisCache failed to set key", {
132
+ key,
133
+ error: error instanceof Error ? error.message : error
134
+ });
135
+ }
136
+ }
137
+ async delete(key) {
138
+ try {
139
+ await this.redis.del(this.getFullKey(key));
140
+ } catch (error) {
141
+ log.error("RedisCache failed to delete key", {
142
+ key,
143
+ error: error instanceof Error ? error.message : error
144
+ });
145
+ }
146
+ }
147
+ async has(key) {
148
+ try {
149
+ const exists = await this.redis.exists(this.getFullKey(key));
150
+ return exists === 1;
151
+ } catch (error) {
152
+ log.error("RedisCache failed to check key", {
153
+ key,
154
+ error: error instanceof Error ? error.message : error
155
+ });
156
+ return false;
157
+ }
158
+ }
159
+ async clear(pattern) {
160
+ try {
161
+ if (pattern) {
162
+ const fullPattern = this.getFullKey(pattern);
163
+ const keys = await this.redis.keys(fullPattern);
164
+ if (keys.length > 0) {
165
+ await this.redis.del(...keys);
166
+ }
167
+ } else {
168
+ const keys = await this.redis.keys(this.getFullKey("*"));
169
+ if (keys.length > 0) {
170
+ await this.redis.del(...keys);
171
+ }
172
+ }
173
+ } catch (error) {
174
+ log.error("RedisCache failed to clear cache", {
175
+ error: error instanceof Error ? error.message : error
176
+ });
177
+ }
178
+ }
179
+ async mget(keys) {
180
+ try {
181
+ const fullKeys = keys.map((key) => this.getFullKey(key));
182
+ const values = await this.redis.mget(...fullKeys);
183
+ return values.map((value) => value ? JSON.parse(value) : null);
184
+ } catch (error) {
185
+ log.error("RedisCache failed to mget keys", {
186
+ error: error instanceof Error ? error.message : error
187
+ });
188
+ return keys.map(() => null);
189
+ }
190
+ }
191
+ async mset(entries) {
192
+ try {
193
+ const pipeline = this.redis.pipeline();
194
+ for (const [key, value, ttl] of entries) {
195
+ const serialized = JSON.stringify(value);
196
+ const fullKey = this.getFullKey(key);
197
+ const effectiveTTL = ttl ?? this.defaultTTL;
198
+ if (effectiveTTL) {
199
+ pipeline.setex(fullKey, effectiveTTL, serialized);
200
+ } else {
201
+ pipeline.set(fullKey, serialized);
202
+ }
203
+ }
204
+ await pipeline.exec();
205
+ } catch (error) {
206
+ log.error("RedisCache failed to mset entries", {
207
+ error: error instanceof Error ? error.message : error
208
+ });
209
+ }
210
+ }
211
+ async disconnect() {
212
+ try {
213
+ await this.redis.quit();
214
+ } catch (error) {
215
+ log.error("RedisCache failed to disconnect", {
216
+ error: error instanceof Error ? error.message : error
217
+ });
218
+ }
219
+ }
220
+ };
221
+ var FileCache = class {
222
+ static {
223
+ __name(this, "FileCache");
224
+ }
225
+ name = "file";
226
+ cacheDir;
227
+ maxKeys;
228
+ defaultTTL;
229
+ cleanupInterval;
230
+ cleanupTimer;
231
+ initPromise;
232
+ constructor(options = {}) {
233
+ this.cacheDir = options.cacheDir || path.join(os.tmpdir(), "atp-cache");
234
+ this.maxKeys = options.maxKeys || 1e3;
235
+ this.defaultTTL = options.defaultTTL || 3600;
236
+ this.cleanupInterval = options.cleanupInterval || 300;
237
+ this.initPromise = this.initialize();
238
+ }
239
+ async initialize() {
240
+ try {
241
+ await promises.mkdir(this.cacheDir, {
242
+ recursive: true
243
+ });
244
+ this.startCleanup();
245
+ } catch (error) {
246
+ log.error("FileCache failed to initialize cache directory", {
247
+ error: error instanceof Error ? error.message : error
248
+ });
249
+ }
250
+ }
251
+ async ensureInitialized() {
252
+ if (this.initPromise) {
253
+ await this.initPromise;
254
+ }
255
+ }
256
+ getFilePath(key) {
257
+ const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, "_");
258
+ return path.join(this.cacheDir, `${sanitizedKey}.json`);
259
+ }
260
+ startCleanup() {
261
+ if (this.cleanupInterval > 0) {
262
+ this.cleanupTimer = setInterval(() => {
263
+ this.cleanExpired().catch((error) => {
264
+ log.error("FileCache cleanup error", {
265
+ error
266
+ });
267
+ });
268
+ }, this.cleanupInterval * 1e3);
269
+ if (this.cleanupTimer.unref) {
270
+ this.cleanupTimer.unref();
271
+ }
272
+ }
273
+ }
274
+ async cleanExpired() {
275
+ try {
276
+ await this.ensureInitialized();
277
+ const files = await promises.readdir(this.cacheDir);
278
+ const now = Date.now();
279
+ for (const file of files) {
280
+ if (!file.endsWith(".json")) continue;
281
+ const filePath = path.join(this.cacheDir, file);
282
+ try {
283
+ const content = await promises.readFile(filePath, "utf-8");
284
+ const entry = JSON.parse(content);
285
+ if (entry.expiresAt !== -1 && now > entry.expiresAt) {
286
+ await promises.unlink(filePath);
287
+ }
288
+ } catch {
289
+ try {
290
+ await promises.unlink(filePath);
291
+ } catch {
292
+ }
293
+ }
294
+ }
295
+ await this.enforceMaxKeys();
296
+ } catch (error) {
297
+ log.error("FileCache failed to clean expired entries", {
298
+ error: error instanceof Error ? error.message : error
299
+ });
300
+ }
301
+ }
302
+ async enforceMaxKeys() {
303
+ try {
304
+ const files = await promises.readdir(this.cacheDir);
305
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
306
+ if (jsonFiles.length > this.maxKeys) {
307
+ const fileStats = await Promise.all(jsonFiles.map(async (file) => {
308
+ const filePath = path.join(this.cacheDir, file);
309
+ const stats = await promises.stat(filePath);
310
+ return {
311
+ file,
312
+ mtime: stats.mtime.getTime()
313
+ };
314
+ }));
315
+ fileStats.sort((a, b) => a.mtime - b.mtime);
316
+ const toDelete = fileStats.slice(0, jsonFiles.length - this.maxKeys);
317
+ await Promise.all(toDelete.map((item) => {
318
+ const filePath = path.join(this.cacheDir, item.file);
319
+ return promises.unlink(filePath).catch(() => {
320
+ });
321
+ }));
322
+ }
323
+ } catch (error) {
324
+ log.error("FileCache failed to enforce max keys", {
325
+ error: error instanceof Error ? error.message : error
326
+ });
327
+ }
328
+ }
329
+ async get(key) {
330
+ try {
331
+ await this.ensureInitialized();
332
+ const filePath = this.getFilePath(key);
333
+ const content = await promises.readFile(filePath, "utf-8");
334
+ const entry = JSON.parse(content);
335
+ if (entry.expiresAt !== -1 && Date.now() > entry.expiresAt) {
336
+ await this.delete(key);
337
+ return null;
338
+ }
339
+ return entry.value;
340
+ } catch (error) {
341
+ if (error.code === "ENOENT") {
342
+ return null;
343
+ }
344
+ log.error("FileCache failed to get key", {
345
+ key,
346
+ error: error instanceof Error ? error.message : error
347
+ });
348
+ return null;
349
+ }
350
+ }
351
+ async set(key, value, ttl) {
352
+ try {
353
+ await this.ensureInitialized();
354
+ await this.enforceMaxKeys();
355
+ const expiresAt = ttl ? Date.now() + ttl * 1e3 : this.defaultTTL > 0 ? Date.now() + this.defaultTTL * 1e3 : -1;
356
+ const entry = {
357
+ value,
358
+ expiresAt
359
+ };
360
+ const filePath = this.getFilePath(key);
361
+ await promises.writeFile(filePath, JSON.stringify(entry), "utf-8");
362
+ } catch (error) {
363
+ log.error("FileCache failed to set key", {
364
+ key,
365
+ error: error instanceof Error ? error.message : error
366
+ });
367
+ }
368
+ }
369
+ async delete(key) {
370
+ try {
371
+ await this.ensureInitialized();
372
+ const filePath = this.getFilePath(key);
373
+ await promises.unlink(filePath);
374
+ } catch (error) {
375
+ if (error.code !== "ENOENT") {
376
+ log.error("FileCache failed to delete key", {
377
+ key,
378
+ error: error instanceof Error ? error.message : error
379
+ });
380
+ }
381
+ }
382
+ }
383
+ async has(key) {
384
+ const value = await this.get(key);
385
+ return value !== null;
386
+ }
387
+ async clear(pattern) {
388
+ try {
389
+ await this.ensureInitialized();
390
+ const files = await promises.readdir(this.cacheDir);
391
+ if (!pattern) {
392
+ await Promise.all(files.filter((f) => f.endsWith(".json")).map((file) => {
393
+ const filePath = path.join(this.cacheDir, file);
394
+ return promises.unlink(filePath).catch(() => {
395
+ });
396
+ }));
397
+ return;
398
+ }
399
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
400
+ for (const file of files) {
401
+ if (!file.endsWith(".json")) continue;
402
+ const keyBase = file.replace(".json", "");
403
+ if (regex.test(keyBase)) {
404
+ const filePath = path.join(this.cacheDir, file);
405
+ await promises.unlink(filePath).catch(() => {
406
+ });
407
+ }
408
+ }
409
+ } catch (error) {
410
+ log.error("FileCache failed to clear cache", {
411
+ error: error instanceof Error ? error.message : error
412
+ });
413
+ }
414
+ }
415
+ async mget(keys) {
416
+ return Promise.all(keys.map((key) => this.get(key)));
417
+ }
418
+ async mset(entries) {
419
+ await Promise.all(entries.map(([key, value, ttl]) => this.set(key, value, ttl)));
420
+ }
421
+ async disconnect() {
422
+ if (this.cleanupTimer) {
423
+ clearInterval(this.cleanupTimer);
424
+ this.cleanupTimer = void 0;
425
+ }
426
+ }
427
+ /** Get cache statistics */
428
+ async getStats() {
429
+ try {
430
+ await this.ensureInitialized();
431
+ const files = await promises.readdir(this.cacheDir);
432
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
433
+ let totalSize = 0;
434
+ for (const file of jsonFiles) {
435
+ const filePath = path.join(this.cacheDir, file);
436
+ try {
437
+ const stats = await promises.stat(filePath);
438
+ totalSize += stats.size;
439
+ } catch {
440
+ }
441
+ }
442
+ return {
443
+ keys: jsonFiles.length,
444
+ maxKeys: this.maxKeys,
445
+ utilization: jsonFiles.length / this.maxKeys * 100,
446
+ sizeBytes: totalSize,
447
+ cacheDir: this.cacheDir
448
+ };
449
+ } catch (error) {
450
+ log.error("[FileCache] Failed to get stats:", error);
451
+ return {
452
+ keys: 0,
453
+ maxKeys: this.maxKeys,
454
+ utilization: 0,
455
+ sizeBytes: 0,
456
+ cacheDir: this.cacheDir
457
+ };
458
+ }
459
+ }
460
+ /** Manually trigger cleanup of expired entries */
461
+ async cleanup() {
462
+ await this.cleanExpired();
463
+ }
464
+ };
465
+
466
+ // src/auth/env.ts
467
+ var EnvAuthProvider = class {
468
+ static {
469
+ __name(this, "EnvAuthProvider");
470
+ }
471
+ name = "env";
472
+ prefix;
473
+ credentials;
474
+ constructor(options = {}) {
475
+ this.prefix = options.prefix || "ATP_";
476
+ this.credentials = /* @__PURE__ */ new Map();
477
+ if (options.credentials) {
478
+ for (const [key, value] of Object.entries(options.credentials)) {
479
+ this.credentials.set(key, value);
480
+ }
481
+ }
482
+ }
483
+ async getCredential(key) {
484
+ if (this.credentials.has(key)) {
485
+ return this.credentials.get(key) || null;
486
+ }
487
+ const envValue = process.env[key] || process.env[`${this.prefix}${key}`];
488
+ return envValue || null;
489
+ }
490
+ async setCredential(key, value, _ttl) {
491
+ this.credentials.set(key, value);
492
+ }
493
+ async deleteCredential(key) {
494
+ this.credentials.delete(key);
495
+ }
496
+ async listCredentials() {
497
+ const keys = /* @__PURE__ */ new Set();
498
+ for (const key of this.credentials.keys()) {
499
+ keys.add(key);
500
+ }
501
+ for (const key of Object.keys(process.env)) {
502
+ if (key.startsWith(this.prefix)) {
503
+ keys.add(key.substring(this.prefix.length));
504
+ }
505
+ }
506
+ return Array.from(keys);
507
+ }
508
+ async disconnect() {
509
+ this.credentials.clear();
510
+ }
511
+ };
512
+ var ScopeCheckerRegistry = class {
513
+ static {
514
+ __name(this, "ScopeCheckerRegistry");
515
+ }
516
+ checkers = /* @__PURE__ */ new Map();
517
+ scopeCache = /* @__PURE__ */ new Map();
518
+ cleanupInterval;
519
+ maxCacheSize = 1e4;
520
+ pendingChecks = /* @__PURE__ */ new Map();
521
+ constructor() {
522
+ this.startCleanup();
523
+ }
524
+ /**
525
+ * Start periodic cache cleanup
526
+ */
527
+ startCleanup() {
528
+ this.cleanupInterval = setInterval(() => {
529
+ this.cleanupExpiredCache();
530
+ }, 5 * 60 * 1e3);
531
+ if (this.cleanupInterval.unref) {
532
+ this.cleanupInterval.unref();
533
+ }
534
+ }
535
+ /**
536
+ * Stop periodic cache cleanup (for testing or shutdown)
537
+ */
538
+ stopCleanup() {
539
+ if (this.cleanupInterval) {
540
+ clearInterval(this.cleanupInterval);
541
+ this.cleanupInterval = void 0;
542
+ }
543
+ }
544
+ /**
545
+ * Remove expired entries from cache
546
+ */
547
+ cleanupExpiredCache() {
548
+ const now = Date.now();
549
+ let cleaned = 0;
550
+ for (const [key, value] of this.scopeCache.entries()) {
551
+ if (value.expiresAt <= now) {
552
+ this.scopeCache.delete(key);
553
+ cleaned++;
554
+ }
555
+ }
556
+ if (this.scopeCache.size > this.maxCacheSize) {
557
+ const entriesToRemove = this.scopeCache.size - this.maxCacheSize;
558
+ const entries = Array.from(this.scopeCache.entries());
559
+ entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
560
+ for (let i = 0; i < entriesToRemove; i++) {
561
+ const entry = entries[i];
562
+ if (entry) {
563
+ this.scopeCache.delete(entry[0]);
564
+ cleaned++;
565
+ }
566
+ }
567
+ }
568
+ if (cleaned > 0) {
569
+ log.debug(`Cleaned ${cleaned} expired/old scope cache entries`);
570
+ }
571
+ }
572
+ /**
573
+ * Register a custom scope checker
574
+ */
575
+ register(checker) {
576
+ this.checkers.set(checker.provider, checker);
577
+ }
578
+ /**
579
+ * Check if a scope checker is available for a provider
580
+ */
581
+ hasChecker(provider) {
582
+ return this.checkers.has(provider);
583
+ }
584
+ /**
585
+ * Get scope checker for a provider
586
+ */
587
+ getChecker(provider) {
588
+ return this.checkers.get(provider);
589
+ }
590
+ /**
591
+ * Check what scopes a token has (with caching and deduplication)
592
+ * @param provider - Provider name
593
+ * @param token - Access token
594
+ * @param cacheTTL - Cache TTL in seconds (default: 3600 = 1 hour)
595
+ */
596
+ async checkScopes(provider, token, cacheTTL = 3600) {
597
+ const cacheKey = `${provider}:${this.hashToken(token)}`;
598
+ const cached = this.scopeCache.get(cacheKey);
599
+ if (cached && cached.expiresAt > Date.now()) {
600
+ return cached.scopes;
601
+ }
602
+ const pending = this.pendingChecks.get(cacheKey);
603
+ if (pending) {
604
+ return pending;
605
+ }
606
+ const checker = this.checkers.get(provider);
607
+ if (!checker) {
608
+ throw new Error(`No scope checker registered for provider: ${provider}`);
609
+ }
610
+ const checkPromise = (async () => {
611
+ try {
612
+ const scopes = await checker.check(token);
613
+ this.scopeCache.set(cacheKey, {
614
+ scopes,
615
+ expiresAt: Date.now() + cacheTTL * 1e3
616
+ });
617
+ return scopes;
618
+ } finally {
619
+ this.pendingChecks.delete(cacheKey);
620
+ }
621
+ })();
622
+ this.pendingChecks.set(cacheKey, checkPromise);
623
+ return checkPromise;
624
+ }
625
+ /**
626
+ * Validate if a token is still valid
627
+ */
628
+ async validateToken(provider, token) {
629
+ const checker = this.checkers.get(provider);
630
+ if (!checker || !checker.validate) {
631
+ return true;
632
+ }
633
+ return await checker.validate(token);
634
+ }
635
+ /**
636
+ * Get complete token information
637
+ */
638
+ async getTokenInfo(provider, token) {
639
+ const checker = this.checkers.get(provider);
640
+ if (!checker) {
641
+ throw new Error(`No scope checker registered for provider: ${provider}`);
642
+ }
643
+ const [scopes, valid] = await Promise.all([
644
+ checker.check(token),
645
+ checker.validate ? checker.validate(token) : Promise.resolve(true)
646
+ ]);
647
+ return {
648
+ valid,
649
+ scopes
650
+ };
651
+ }
652
+ /**
653
+ * Clear cached scopes
654
+ */
655
+ clearCache(provider) {
656
+ if (provider) {
657
+ for (const key of this.scopeCache.keys()) {
658
+ if (key.startsWith(`${provider}:`)) {
659
+ this.scopeCache.delete(key);
660
+ }
661
+ }
662
+ } else {
663
+ this.scopeCache.clear();
664
+ }
665
+ }
666
+ /**
667
+ * Hash token for cache key (SHA-256, first 16 chars)
668
+ */
669
+ hashToken(token) {
670
+ return createHash("sha256").update(token).digest("hex").substring(0, 16);
671
+ }
672
+ };
673
+ var JSONLAuditSink = class {
674
+ static {
675
+ __name(this, "JSONLAuditSink");
676
+ }
677
+ name = "jsonl";
678
+ filePath;
679
+ sanitizeSecrets;
680
+ buffer = [];
681
+ flushInterval = null;
682
+ batchSize;
683
+ constructor(options) {
684
+ this.filePath = options.filePath;
685
+ this.sanitizeSecrets = options.sanitizeSecrets ?? true;
686
+ this.batchSize = options.batchSize || 10;
687
+ const dir = dirname(this.filePath);
688
+ if (!existsSync(dir)) {
689
+ mkdir(dir, {
690
+ recursive: true
691
+ }).catch((err) => {
692
+ log.error("Failed to create audit directory", {
693
+ error: err.message
694
+ });
695
+ });
696
+ }
697
+ if (options.flushIntervalMs) {
698
+ this.flushInterval = setInterval(() => {
699
+ if (this.buffer.length > 0) {
700
+ this.flush().catch((err) => {
701
+ log.error("Failed to flush audit buffer", {
702
+ error: err.message
703
+ });
704
+ });
705
+ }
706
+ }, options.flushIntervalMs);
707
+ }
708
+ }
709
+ async write(event) {
710
+ const sanitized = this.sanitizeSecrets ? this.sanitizeEvent(event) : event;
711
+ const line = JSON.stringify(sanitized) + "\n";
712
+ try {
713
+ await appendFile(this.filePath, line, "utf8");
714
+ } catch (error) {
715
+ log.error("Failed to write audit event", {
716
+ error: error.message
717
+ });
718
+ throw error;
719
+ }
720
+ }
721
+ async writeBatch(events) {
722
+ const sanitized = this.sanitizeSecrets ? events.map((e) => this.sanitizeEvent(e)) : events;
723
+ const lines = sanitized.map((e) => JSON.stringify(e)).join("\n") + "\n";
724
+ try {
725
+ await appendFile(this.filePath, lines, "utf8");
726
+ } catch (error) {
727
+ log.error("Failed to write audit batch", {
728
+ error: error.message
729
+ });
730
+ throw error;
731
+ }
732
+ }
733
+ async query(filter) {
734
+ try {
735
+ const content = await readFile(this.filePath, "utf8");
736
+ const lines = content.split("\n").filter((line) => line.trim());
737
+ const events = lines.map((line) => JSON.parse(line));
738
+ return events.filter((event) => {
739
+ if (filter.clientId && event.clientId !== filter.clientId) return false;
740
+ if (filter.userId && event.userId !== filter.userId) return false;
741
+ if (filter.from && event.timestamp < filter.from) return false;
742
+ if (filter.to && event.timestamp > filter.to) return false;
743
+ if (filter.resource && event.resource !== filter.resource) return false;
744
+ if (filter.eventType) {
745
+ const types = Array.isArray(filter.eventType) ? filter.eventType : [
746
+ filter.eventType
747
+ ];
748
+ if (!types.includes(event.eventType)) return false;
749
+ }
750
+ if (filter.status) {
751
+ const statuses = Array.isArray(filter.status) ? filter.status : [
752
+ filter.status
753
+ ];
754
+ if (!statuses.includes(event.status)) return false;
755
+ }
756
+ if (filter.minRiskScore && (event.riskScore || 0) < filter.minRiskScore) return false;
757
+ return true;
758
+ }).slice(filter.offset || 0, (filter.offset || 0) + (filter.limit || 100));
759
+ } catch (error) {
760
+ if (error.code === "ENOENT") {
761
+ return [];
762
+ }
763
+ throw error;
764
+ }
765
+ }
766
+ async disconnect() {
767
+ if (this.flushInterval) {
768
+ clearInterval(this.flushInterval);
769
+ }
770
+ if (this.buffer.length > 0) {
771
+ await this.flush();
772
+ }
773
+ }
774
+ async flush() {
775
+ if (this.buffer.length === 0) return;
776
+ await this.writeBatch([
777
+ ...this.buffer
778
+ ]);
779
+ this.buffer = [];
780
+ }
781
+ sanitizeEvent(event) {
782
+ const sanitized = {
783
+ ...event
784
+ };
785
+ if (sanitized.code) {
786
+ sanitized.code = this.sanitizeString(sanitized.code);
787
+ }
788
+ if (sanitized.input) {
789
+ sanitized.input = this.sanitizeObject(sanitized.input);
790
+ }
791
+ if (sanitized.output) {
792
+ sanitized.output = this.sanitizeObject(sanitized.output);
793
+ }
794
+ return sanitized;
795
+ }
796
+ sanitizeString(str) {
797
+ const patterns = [
798
+ /api[_-]?key/gi,
799
+ /secret/gi,
800
+ /token/gi,
801
+ /password/gi,
802
+ /bearer/gi,
803
+ /authorization/gi
804
+ ];
805
+ for (const pattern of patterns) {
806
+ str = str.replace(new RegExp(`(${pattern.source})\\s*[:=]\\s*['"]?([^'"\\s]+)`, "gi"), "$1: [REDACTED]");
807
+ }
808
+ return str;
809
+ }
810
+ sanitizeObject(obj) {
811
+ if (typeof obj !== "object" || obj === null) {
812
+ return obj;
813
+ }
814
+ if (Array.isArray(obj)) {
815
+ return obj.map((item) => this.sanitizeObject(item));
816
+ }
817
+ const sanitized = {};
818
+ const secretPatterns = [
819
+ "key",
820
+ "secret",
821
+ "token",
822
+ "password",
823
+ "bearer",
824
+ "auth"
825
+ ];
826
+ for (const [key, value] of Object.entries(obj)) {
827
+ const lowerKey = key.toLowerCase();
828
+ if (secretPatterns.some((pattern) => lowerKey.includes(pattern))) {
829
+ sanitized[key] = "[REDACTED]";
830
+ } else if (typeof value === "object") {
831
+ sanitized[key] = this.sanitizeObject(value);
832
+ } else {
833
+ sanitized[key] = value;
834
+ }
835
+ }
836
+ return sanitized;
837
+ }
838
+ };
839
+
840
+ // src/audit/otel-metrics.ts
841
+ var OTelCounter;
842
+ (function(OTelCounter2) {
843
+ OTelCounter2["EXECUTIONS_TOTAL"] = "atp.executions.total";
844
+ OTelCounter2["TOOLS_CALLS"] = "atp.tools.calls";
845
+ OTelCounter2["LLM_CALLS"] = "atp.llm.calls";
846
+ OTelCounter2["APPROVALS_TOTAL"] = "atp.approvals.total";
847
+ OTelCounter2["SECURITY_EVENTS"] = "atp.security.events";
848
+ })(OTelCounter || (OTelCounter = {}));
849
+ var OTelHistogram;
850
+ (function(OTelHistogram2) {
851
+ OTelHistogram2["EXECUTION_DURATION"] = "atp.execution.duration";
852
+ OTelHistogram2["TOOL_DURATION"] = "atp.tool.duration";
853
+ })(OTelHistogram || (OTelHistogram = {}));
854
+ var OTelSpan;
855
+ (function(OTelSpan2) {
856
+ OTelSpan2["EXECUTION_START"] = "atp.execution.start";
857
+ OTelSpan2["EXECUTION_COMPLETE"] = "atp.execution.complete";
858
+ OTelSpan2["EXECUTION_PAUSE"] = "atp.execution.pause";
859
+ OTelSpan2["EXECUTION_RESUME"] = "atp.execution.resume";
860
+ OTelSpan2["EXECUTION_ERROR"] = "atp.execution.error";
861
+ OTelSpan2["TOOL_CALL"] = "atp.tool_call";
862
+ OTelSpan2["LLM_CALL"] = "atp.llm_call";
863
+ OTelSpan2["APPROVAL_REQUEST"] = "atp.approval.request";
864
+ OTelSpan2["APPROVAL_RESPONSE"] = "atp.approval.response";
865
+ OTelSpan2["CLIENT_INIT"] = "atp.client_init";
866
+ OTelSpan2["ERROR"] = "atp.error";
867
+ })(OTelSpan || (OTelSpan = {}));
868
+ var OTelAttribute;
869
+ (function(OTelAttribute2) {
870
+ OTelAttribute2["EVENT_ID"] = "atp.event.id";
871
+ OTelAttribute2["EVENT_TYPE"] = "atp.event.type";
872
+ OTelAttribute2["EVENT_ACTION"] = "atp.event.action";
873
+ OTelAttribute2["TIMESTAMP"] = "atp.timestamp";
874
+ OTelAttribute2["CLIENT_ID"] = "atp.client.id";
875
+ OTelAttribute2["USER_ID"] = "atp.user.id";
876
+ OTelAttribute2["IP_ADDRESS"] = "atp.ip_address";
877
+ OTelAttribute2["USER_AGENT"] = "atp.user_agent";
878
+ OTelAttribute2["STATUS"] = "atp.status";
879
+ OTelAttribute2["RESOURCE"] = "atp.resource";
880
+ OTelAttribute2["RESOURCE_ID"] = "atp.resource.id";
881
+ OTelAttribute2["TOOL_NAME"] = "atp.tool.name";
882
+ OTelAttribute2["TOOL_INPUT_SIZE"] = "tool.input_size";
883
+ OTelAttribute2["TOOL_OUTPUT_SIZE"] = "tool.output_size";
884
+ OTelAttribute2["API_GROUP"] = "atp.api.group";
885
+ OTelAttribute2["DURATION_MS"] = "atp.duration_ms";
886
+ OTelAttribute2["MEMORY_BYTES"] = "atp.memory_bytes";
887
+ OTelAttribute2["LLM_CALLS"] = "atp.llm_calls";
888
+ OTelAttribute2["HTTP_CALLS"] = "atp.http_calls";
889
+ OTelAttribute2["RISK_SCORE"] = "atp.security.risk_score";
890
+ OTelAttribute2["SECURITY_EVENTS"] = "atp.security.events";
891
+ OTelAttribute2["SECURITY_EVENTS_COUNT"] = "atp.security.events_count";
892
+ OTelAttribute2["ERROR_MESSAGE"] = "atp.error.message";
893
+ OTelAttribute2["ERROR_CODE"] = "atp.error.code";
894
+ OTelAttribute2["ERROR_STACK"] = "atp.error.stack";
895
+ })(OTelAttribute || (OTelAttribute = {}));
896
+ var METRIC_CONFIGS = {
897
+ ["atp.executions.total"]: {
898
+ description: "Total number of executions",
899
+ unit: "1"
900
+ },
901
+ ["atp.tools.calls"]: {
902
+ description: "Tool call count",
903
+ unit: "1"
904
+ },
905
+ ["atp.llm.calls"]: {
906
+ description: "LLM call count",
907
+ unit: "1"
908
+ },
909
+ ["atp.approvals.total"]: {
910
+ description: "Approval request count",
911
+ unit: "1"
912
+ },
913
+ ["atp.security.events"]: {
914
+ description: "Security events count",
915
+ unit: "1"
916
+ },
917
+ ["atp.execution.duration"]: {
918
+ description: "Execution duration in milliseconds",
919
+ unit: "ms"
920
+ },
921
+ ["atp.tool.duration"]: {
922
+ description: "Tool execution duration",
923
+ unit: "ms"
924
+ }
925
+ };
926
+ var OTEL_SERVICE_NAME = "agent-tool-protocol";
927
+ var OTEL_TRACER_NAME = "agent-tool-protocol";
928
+ var OTEL_METER_NAME = "agent-tool-protocol";
929
+ var ATTRIBUTE_PREFIX_TOOL = "atp.tool";
930
+ var ATTRIBUTE_PREFIX_METADATA = "atp.metadata";
931
+
932
+ // src/audit/opentelemetry.ts
933
+ var OpenTelemetryAuditSink = class {
934
+ static {
935
+ __name(this, "OpenTelemetryAuditSink");
936
+ }
937
+ name = "opentelemetry";
938
+ tracer = trace.getTracer(OTEL_TRACER_NAME);
939
+ meter = metrics.getMeter(OTEL_METER_NAME);
940
+ executionCounter = this.meter.createCounter(OTelCounter.EXECUTIONS_TOTAL, METRIC_CONFIGS[OTelCounter.EXECUTIONS_TOTAL]);
941
+ toolCallCounter = this.meter.createCounter(OTelCounter.TOOLS_CALLS, METRIC_CONFIGS[OTelCounter.TOOLS_CALLS]);
942
+ llmCallCounter = this.meter.createCounter(OTelCounter.LLM_CALLS, METRIC_CONFIGS[OTelCounter.LLM_CALLS]);
943
+ approvalCounter = this.meter.createCounter(OTelCounter.APPROVALS_TOTAL, METRIC_CONFIGS[OTelCounter.APPROVALS_TOTAL]);
944
+ executionDuration = this.meter.createHistogram(OTelHistogram.EXECUTION_DURATION, METRIC_CONFIGS[OTelHistogram.EXECUTION_DURATION]);
945
+ toolDuration = this.meter.createHistogram(OTelHistogram.TOOL_DURATION, METRIC_CONFIGS[OTelHistogram.TOOL_DURATION]);
946
+ async write(event) {
947
+ const span = this.tracer.startSpan(`atp.${event.eventType}.${event.action}`, {
948
+ attributes: this.buildAttributes(event)
949
+ });
950
+ await context.with(trace.setSpan(context.active(), span), async () => {
951
+ try {
952
+ this.handleEvent(span, event);
953
+ this.recordMetrics(event);
954
+ span.setStatus({
955
+ code: SpanStatusCode.OK
956
+ });
957
+ } catch (error) {
958
+ span.setStatus({
959
+ code: SpanStatusCode.ERROR,
960
+ message: error.message
961
+ });
962
+ span.recordException(error);
963
+ } finally {
964
+ span.end();
965
+ }
966
+ });
967
+ }
968
+ async writeBatch(events) {
969
+ await Promise.all(events.map((event) => this.write(event)));
970
+ }
971
+ buildAttributes(event) {
972
+ const attrs = {
973
+ [OTelAttribute.EVENT_ID]: event.eventId,
974
+ [OTelAttribute.EVENT_TYPE]: event.eventType,
975
+ [OTelAttribute.EVENT_ACTION]: event.action,
976
+ [OTelAttribute.TIMESTAMP]: event.timestamp,
977
+ [OTelAttribute.CLIENT_ID]: event.clientId,
978
+ [OTelAttribute.STATUS]: event.status
979
+ };
980
+ if (event.userId) attrs[OTelAttribute.USER_ID] = event.userId;
981
+ if (event.ipAddress) attrs[OTelAttribute.IP_ADDRESS] = event.ipAddress;
982
+ if (event.userAgent) attrs[OTelAttribute.USER_AGENT] = event.userAgent;
983
+ if (event.resource) attrs[OTelAttribute.RESOURCE] = event.resource;
984
+ if (event.resourceId) attrs[OTelAttribute.RESOURCE_ID] = event.resourceId;
985
+ if (event.toolName) attrs[OTelAttribute.TOOL_NAME] = event.toolName;
986
+ if (event.apiGroup) attrs[OTelAttribute.API_GROUP] = event.apiGroup;
987
+ if (event.duration !== void 0) attrs[OTelAttribute.DURATION_MS] = event.duration;
988
+ if (event.memoryUsed !== void 0) attrs[OTelAttribute.MEMORY_BYTES] = event.memoryUsed;
989
+ if (event.llmCallsCount !== void 0) attrs[OTelAttribute.LLM_CALLS] = event.llmCallsCount;
990
+ if (event.httpCallsCount !== void 0) attrs[OTelAttribute.HTTP_CALLS] = event.httpCallsCount;
991
+ if (event.riskScore !== void 0) attrs[OTelAttribute.RISK_SCORE] = event.riskScore;
992
+ if (event.securityEvents && event.securityEvents.length > 0) {
993
+ attrs[OTelAttribute.SECURITY_EVENTS] = JSON.stringify(event.securityEvents);
994
+ attrs[OTelAttribute.SECURITY_EVENTS_COUNT] = event.securityEvents.length;
995
+ }
996
+ if (event.error) {
997
+ attrs[OTelAttribute.ERROR_MESSAGE] = event.error.message;
998
+ if (event.error.code) attrs[OTelAttribute.ERROR_CODE] = event.error.code;
999
+ if (event.error.stack) attrs[OTelAttribute.ERROR_STACK] = event.error.stack;
1000
+ }
1001
+ if (event.annotations) {
1002
+ Object.assign(attrs, this.flattenObject(event.annotations, ATTRIBUTE_PREFIX_TOOL));
1003
+ }
1004
+ if (event.metadata) {
1005
+ Object.assign(attrs, this.flattenObject(event.metadata, ATTRIBUTE_PREFIX_METADATA));
1006
+ }
1007
+ return attrs;
1008
+ }
1009
+ handleEvent(span, event) {
1010
+ switch (event.eventType) {
1011
+ case "execution":
1012
+ if (event.action === "start") {
1013
+ span.addEvent("Execution started", {
1014
+ "client.id": event.clientId,
1015
+ "resource.id": event.resourceId
1016
+ });
1017
+ } else if (event.action === "complete") {
1018
+ span.addEvent("Execution completed", {
1019
+ duration_ms: event.duration,
1020
+ status: event.status,
1021
+ llm_calls: event.llmCallsCount
1022
+ });
1023
+ } else if (event.action === "pause") {
1024
+ span.addEvent("Execution paused", {
1025
+ status: event.status
1026
+ });
1027
+ } else if (event.action === "resume") {
1028
+ span.addEvent("Execution resumed", {
1029
+ "resource.id": event.resourceId
1030
+ });
1031
+ }
1032
+ break;
1033
+ case "tool_call":
1034
+ span.addEvent(`Tool ${event.action}`, {
1035
+ "tool.name": event.toolName,
1036
+ "api.group": event.apiGroup,
1037
+ duration_ms: event.duration
1038
+ });
1039
+ if (event.input) {
1040
+ span.setAttribute(OTelAttribute.TOOL_INPUT_SIZE, JSON.stringify(event.input).length);
1041
+ }
1042
+ if (event.output) {
1043
+ span.setAttribute(OTelAttribute.TOOL_OUTPUT_SIZE, JSON.stringify(event.output).length);
1044
+ }
1045
+ break;
1046
+ case "llm_call":
1047
+ span.addEvent("LLM call", {
1048
+ duration_ms: event.duration
1049
+ });
1050
+ break;
1051
+ case "approval":
1052
+ span.addEvent(`Approval ${event.action}`, {
1053
+ "tool.name": event.toolName
1054
+ });
1055
+ break;
1056
+ case "error":
1057
+ if (event.error) {
1058
+ span.addEvent("Error occurred", {
1059
+ "error.message": event.error.message,
1060
+ "error.code": event.error.code
1061
+ });
1062
+ span.recordException(new Error(event.error.message));
1063
+ }
1064
+ break;
1065
+ case "client_init":
1066
+ span.addEvent("Client initialized", {
1067
+ "client.id": event.clientId
1068
+ });
1069
+ break;
1070
+ }
1071
+ if (event.securityEvents && event.securityEvents.length > 0) {
1072
+ for (const secEvent of event.securityEvents) {
1073
+ span.addEvent("Security event", {
1074
+ "security.event": secEvent,
1075
+ "security.risk_score": event.riskScore
1076
+ });
1077
+ }
1078
+ }
1079
+ }
1080
+ recordMetrics(event) {
1081
+ const commonAttrs = {
1082
+ client_id: event.clientId,
1083
+ event_type: event.eventType,
1084
+ status: event.status
1085
+ };
1086
+ switch (event.eventType) {
1087
+ case "execution":
1088
+ this.executionCounter.add(1, {
1089
+ ...commonAttrs,
1090
+ action: event.action
1091
+ });
1092
+ if (event.duration !== void 0) {
1093
+ this.executionDuration.record(event.duration, {
1094
+ ...commonAttrs,
1095
+ action: event.action
1096
+ });
1097
+ }
1098
+ break;
1099
+ case "tool_call":
1100
+ this.toolCallCounter.add(1, {
1101
+ ...commonAttrs,
1102
+ tool_name: event.toolName,
1103
+ api_group: event.apiGroup
1104
+ });
1105
+ if (event.duration !== void 0) {
1106
+ this.toolDuration.record(event.duration, {
1107
+ ...commonAttrs,
1108
+ tool_name: event.toolName
1109
+ });
1110
+ }
1111
+ break;
1112
+ case "llm_call":
1113
+ this.llmCallCounter.add(1, commonAttrs);
1114
+ break;
1115
+ case "approval":
1116
+ this.approvalCounter.add(1, {
1117
+ ...commonAttrs,
1118
+ action: event.action
1119
+ });
1120
+ break;
1121
+ }
1122
+ if (event.securityEvents && event.securityEvents.length > 0) {
1123
+ const securityEventCounter = this.meter.createCounter(OTelCounter.SECURITY_EVENTS, METRIC_CONFIGS[OTelCounter.SECURITY_EVENTS]);
1124
+ securityEventCounter.add(event.securityEvents.length, {
1125
+ ...commonAttrs,
1126
+ risk_score: event.riskScore
1127
+ });
1128
+ }
1129
+ }
1130
+ flattenObject(obj, prefix) {
1131
+ const result = {};
1132
+ if (!obj || typeof obj !== "object") return result;
1133
+ for (const [key, value] of Object.entries(obj)) {
1134
+ const fullKey = `${prefix}.${key}`;
1135
+ if (value === null || value === void 0) {
1136
+ continue;
1137
+ }
1138
+ if (typeof value === "object" && !Array.isArray(value)) {
1139
+ Object.assign(result, this.flattenObject(value, fullKey));
1140
+ } else if (Array.isArray(value)) {
1141
+ result[fullKey] = JSON.stringify(value);
1142
+ } else {
1143
+ result[fullKey] = value;
1144
+ }
1145
+ }
1146
+ return result;
1147
+ }
1148
+ };
1149
+
1150
+ export { ATTRIBUTE_PREFIX_METADATA, ATTRIBUTE_PREFIX_TOOL, EnvAuthProvider, FileCache, JSONLAuditSink, METRIC_CONFIGS, MemoryCache, OTEL_METER_NAME, OTEL_SERVICE_NAME, OTEL_TRACER_NAME, OTelAttribute, OTelCounter, OTelHistogram, OTelSpan, OpenTelemetryAuditSink, RedisCache, ScopeCheckerRegistry };
1151
+ //# sourceMappingURL=index.js.map
9
1152
  //# sourceMappingURL=index.js.map