@mondaydotcomorg/atp-providers 0.19.6 → 0.19.8

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