@slates/provider 1.0.0-rc.27 → 1.0.0-rc.29

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.
@@ -1,5 +1,10 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { __traceInternals } from './trace';
2
+ import { AuthConfigSecretRedactor } from '../auth/redact';
3
+ import {
4
+ __traceInternals,
5
+ redactAuthConfigSecretsInText,
6
+ sanitizeSecretValuesInText
7
+ } from './trace';
3
8
 
4
9
  let {
5
10
  sanitizeFreeText,
@@ -7,6 +12,8 @@ let {
7
12
  sanitizeHeaders,
8
13
  isSafeHeaderName,
9
14
  isSecretKeyName,
15
+ isSecretFieldName,
16
+ isAuthConfigSecretValue,
10
17
  isUrlFormSecretKeyName,
11
18
  redactStructuredValue
12
19
  } = __traceInternals;
@@ -245,6 +252,53 @@ describe('sanitizeFreeText', () => {
245
252
  });
246
253
  });
247
254
 
255
+ describe('sanitizeSecretValuesInText', () => {
256
+ it('redacts credential header values regardless of length', () => {
257
+ expect(sanitizeSecretValuesInText('Authorization: Bearer abc')).toBe(
258
+ 'Authorization: Bearer [redacted]'
259
+ );
260
+ expect(sanitizeSecretValuesInText('proxy-authorization=Basic xyz')).toBe(
261
+ 'proxy-authorization=Basic [redacted]'
262
+ );
263
+ expect(sanitizeSecretValuesInText('sent X-API-Key: k1 to server')).toBe(
264
+ 'sent X-API-Key: [redacted] to server'
265
+ );
266
+ expect(sanitizeSecretValuesInText('apiKey=short&page=2')).toBe('apiKey=[redacted]&page=2');
267
+ expect(sanitizeSecretValuesInText('Cookie: sid=1; theme=dark next')).toBe(
268
+ 'Cookie: [redacted] next'
269
+ );
270
+ expect(sanitizeSecretValuesInText('set-cookie: "sess=abc"')).toBe(
271
+ 'set-cookie: "[redacted]"'
272
+ );
273
+ });
274
+
275
+ it('redacts 8+ character scheme credentials but keeps prose', () => {
276
+ expect(sanitizeSecretValuesInText('got Bearer abc12345 back')).toBe(
277
+ 'got Bearer [redacted] back'
278
+ );
279
+ expect(sanitizeSecretValuesInText('Basic dXNlcjpwYXNz')).toBe('Basic [redacted]');
280
+ expect(sanitizeSecretValuesInText('token a1b2c3d4e5')).toBe('token [redacted]');
281
+
282
+ for (let prose of [
283
+ 'Invalid token provided',
284
+ 'token expired',
285
+ 'Basic authentication failed',
286
+ 'Bearer Authentication required',
287
+ 'Token verification failed',
288
+ 'Request failed with error code=404',
289
+ 'Invalid token code=404'
290
+ ]) {
291
+ expect(sanitizeSecretValuesInText(prose)).toBe(prose);
292
+ }
293
+ });
294
+
295
+ it('redacts single-quoted secret pairs', () => {
296
+ expect(
297
+ sanitizeSecretValuesInText("{'access_token': 'abc', 'token_type': 'bearer', 'ok': 'x'}")
298
+ ).toBe("{'access_token': '[redacted]', 'token_type': 'bearer', 'ok': 'x'}");
299
+ });
300
+ });
301
+
248
302
  describe('sanitizeUrl', () => {
249
303
  it('redacts common secret query params', () => {
250
304
  expect(
@@ -272,6 +326,26 @@ describe('sanitizeUrl', () => {
272
326
  expect(out).toContain('expires_in=3600');
273
327
  });
274
328
 
329
+ it('redacts long auth-config secrets embedded in the path', () => {
330
+ let redactor = new AuthConfigSecretRedactor({
331
+ token: '123456:ABCdefGHIjklMNOpqrSTU',
332
+ botId: '123456',
333
+ applicationId: '1503442063478362305'
334
+ });
335
+ expect(
336
+ sanitizeUrl('https://api.telegram.org/bot123456:ABCdefGHIjklMNOpqrSTU/getMe', redactor)
337
+ ).toBe('https://api.telegram.org/bot$$MT$secret$authConfig$token$$/getMe');
338
+ expect(sanitizeUrl('https://example.com/bots/123456/info', redactor)).toBe(
339
+ 'https://example.com/bots/123456/info'
340
+ );
341
+ expect(
342
+ sanitizeUrl(
343
+ 'https://discord.com/api/applications/1503442063478362305/commands',
344
+ redactor
345
+ )
346
+ ).toBe('https://discord.com/api/applications/1503442063478362305/commands');
347
+ });
348
+
275
349
  it('leaves innocuous URLs unchanged', () => {
276
350
  expect(sanitizeUrl('https://example.com/api?page=2&limit=10')).toBe(
277
351
  'https://example.com/api?page=2&limit=10'
@@ -375,3 +449,254 @@ describe('redactStructuredValue', () => {
375
449
  expect(out).toEqual([1, 'two', { three: 3 }]);
376
450
  });
377
451
  });
452
+
453
+ describe('isSecretFieldName (split-camelCase classifier)', () => {
454
+ it('matches raw and split camelCase secret keys', () => {
455
+ for (let key of [
456
+ 'apiSecret',
457
+ 'api_secret',
458
+ 'accessKey',
459
+ 'access_key',
460
+ 'botToken',
461
+ 'bot_token',
462
+ 'githubToken',
463
+ 'github_token',
464
+ 'clientSecret',
465
+ 'sessionToken',
466
+ 'hapikey',
467
+ 'JSESSIONID',
468
+ 'PHPSESSID',
469
+ 'userSessionId',
470
+ 'secret_id'
471
+ ]) {
472
+ expect(isSecretFieldName(key), key).toBe(true);
473
+ }
474
+ });
475
+
476
+ it('skips descriptors, cursors, and ordinary ids', () => {
477
+ for (let key of [
478
+ 'tokenType',
479
+ 'tokenCount',
480
+ 'tokenExpiresAt',
481
+ 'pageToken',
482
+ 'accountId',
483
+ 'apiKeyId',
484
+ 'email',
485
+ 'subdomain'
486
+ ]) {
487
+ expect(isSecretFieldName(key), key).toBe(false);
488
+ }
489
+ });
490
+ });
491
+
492
+ describe('isAuthConfigSecretValue (auth-config paths)', () => {
493
+ it('accepts secret-named and credential-role fields', () => {
494
+ expect(isAuthConfigSecretValue('x', 'token')).toBe(true);
495
+ expect(isAuthConfigSecretValue('x', 'oauth.accessToken')).toBe(true);
496
+ expect(isAuthConfigSecretValue('x', 'apiKeys.0')).toBe(true);
497
+ expect(isAuthConfigSecretValue('x', 'subscriptionKey')).toBe(true);
498
+ expect(isAuthConfigSecretValue('x', 'writeKey')).toBe(true);
499
+ expect(isAuthConfigSecretValue('x', 'provisioningHash')).toBe(true);
500
+ expect(isAuthConfigSecretValue('Basic dXNlcjpw', 'authorizationHeader')).toBe(true);
501
+ });
502
+
503
+ it('rejects non-secret fields', () => {
504
+ for (let path of [
505
+ 'accountId',
506
+ 'email',
507
+ 'tokenType',
508
+ 'subdomain',
509
+ 'scope',
510
+ 'expiresAt',
511
+ 'clientId',
512
+ 'region',
513
+ 'oauth.tenantId'
514
+ ]) {
515
+ expect(isAuthConfigSecretValue('x', path), path).toBe(false);
516
+ }
517
+ expect(isAuthConfigSecretValue('authorization', 'tokenHeader')).toBe(false);
518
+ expect(isAuthConfigSecretValue('project-access-token', 'tokenHeader')).toBe(false);
519
+ });
520
+ });
521
+
522
+ describe('auth-config literal redaction in traces', () => {
523
+ let redactor = new AuthConfigSecretRedactor({
524
+ token: 'tg-secret-123456',
525
+ pin: '1234',
526
+ accountId: '123456',
527
+ email: 'me@acme.com',
528
+ tokenType: 'Bearer',
529
+ subdomain: 'acme',
530
+ scope: 'read',
531
+ expiresAt: '2026-10-13T12:00:00.000Z'
532
+ });
533
+
534
+ it('replaces only secret-named whole values in structured bodies', () => {
535
+ expect(
536
+ redactStructuredValue(
537
+ {
538
+ id: '123456',
539
+ email: 'me@acme.com',
540
+ type: 'Bearer',
541
+ subdomain: 'acme',
542
+ scope: 'read',
543
+ expiresAt: '2026-10-13T12:00:00.000Z',
544
+ echoed: 'tg-secret-123456',
545
+ short: '1234'
546
+ },
547
+ 0,
548
+ redactor
549
+ )
550
+ ).toEqual({
551
+ id: '123456',
552
+ email: 'me@acme.com',
553
+ type: 'Bearer',
554
+ subdomain: 'acme',
555
+ scope: 'read',
556
+ expiresAt: '2026-10-13T12:00:00.000Z',
557
+ echoed: '$$MT$secret$authConfig$token',
558
+ short: '$$MT$secret$authConfig$pin'
559
+ });
560
+ expect(sanitizeFreeText('acme', redactor)).toBe('acme');
561
+ });
562
+
563
+ it('keeps non-secret query and fragment values but replaces secret ones', () => {
564
+ expect(
565
+ sanitizeUrl(
566
+ 'https://acme.example.com/v1?account_id=123456&scope=read&echo=tg-secret-123456#sub=acme',
567
+ redactor
568
+ )
569
+ ).toBe(
570
+ 'https://acme.example.com/v1?account_id=123456&scope=read&echo=%24%24MT%24secret%24authConfig%24token#sub=acme'
571
+ );
572
+ });
573
+
574
+ it('replaces camelCase secret auth fields embedded in text', () => {
575
+ let camel = new AuthConfigSecretRedactor({
576
+ apiSecret: 'api-secret-value',
577
+ accessKey: 'access-key-value',
578
+ botToken: '123:bot-token-value',
579
+ accountName: 'acme-corporation'
580
+ });
581
+ expect(
582
+ redactAuthConfigSecretsInText(
583
+ 'api-secret-value access-key-value /bot123:bot-token-value/ acme-corporation',
584
+ camel
585
+ )
586
+ ).toBe(
587
+ '$$MT$secret$authConfig$apiSecret$$ $$MT$secret$authConfig$accessKey$$ ' +
588
+ '/bot$$MT$secret$authConfig$botToken$$/ acme-corporation'
589
+ );
590
+ });
591
+ });
592
+
593
+ describe('sanitizeSecretValuesInText header schemes', () => {
594
+ it('redacts credentials after any authorization scheme', () => {
595
+ let cases: [string, string][] = [
596
+ ['Authorization: OAuth 00D5g000004XyZ!AQ4AQ', 'Authorization: OAuth [redacted]'],
597
+ ['Authorization: Bot MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ', 'Authorization: Bot [redacted]'],
598
+ ['Authorization: SSWS 00QCjAl4MlV-WPXM', 'Authorization: SSWS [redacted]'],
599
+ [
600
+ 'Authorization: Zoho-oauthtoken 1000.abc.123 rejected',
601
+ 'Authorization: Zoho-oauthtoken [redacted] rejected'
602
+ ],
603
+ [
604
+ 'authorization: Klaviyo-API-Key pk_abc123',
605
+ 'authorization: Klaviyo-API-Key [redacted]'
606
+ ],
607
+ [
608
+ 'Authorization: Digest username="Mufasa", realm="x", nonce="dcd98b", response="6629fae49393a05397450978507c4ef1"',
609
+ 'Authorization: Digest [redacted]'
610
+ ],
611
+ [
612
+ 'Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=host, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7',
613
+ 'Authorization: AWS4-HMAC-SHA256 [redacted]'
614
+ ],
615
+ ['Proxy-Authorization: "Custom abc123def"', 'Proxy-Authorization: "Custom [redacted]"'],
616
+ ['Authorization: abc123def please retry', 'Authorization: [redacted] please retry']
617
+ ];
618
+
619
+ for (let [input, expected] of cases) {
620
+ expect(sanitizeSecretValuesInText(input), input).toBe(expected);
621
+ }
622
+ });
623
+
624
+ it('keeps header-name prose that holds no credential', () => {
625
+ for (let prose of [
626
+ 'Authorization: missing or invalid',
627
+ 'Authorization: Basic',
628
+ 'Missing cookie: please log in',
629
+ 'X-API-Key: invalid',
630
+ 'api_key: required'
631
+ ]) {
632
+ expect(sanitizeSecretValuesInText(prose), prose).toBe(prose);
633
+ }
634
+ });
635
+ });
636
+
637
+ describe('sanitizeSecretValuesInText prose key: value pairs', () => {
638
+ it('redacts secret-named pairs with credential values', () => {
639
+ let cases: [string, string][] = [
640
+ ['password: Hunter2', 'password: [redacted]'],
641
+ ['access_token: abc', 'access_token: [redacted]'],
642
+ ['refresh_token: 1//0gabc', 'refresh_token: [redacted]'],
643
+ ['sessionToken: FwoGZXIvYXdzEJr', 'sessionToken: [redacted]'],
644
+ ['secret: s3cr3tvalue', 'secret: [redacted]'],
645
+ ['Invalid token: ya29.a0AfH6SMBx.', 'Invalid token: [redacted].'],
646
+ [
647
+ "{ access_token: 'abc', token_type: 'bearer' }",
648
+ "{ access_token: '[redacted]', token_type: 'bearer' }"
649
+ ],
650
+ ['client_secret:"xyz"', 'client_secret:"[redacted]"']
651
+ ];
652
+
653
+ for (let [input, expected] of cases) {
654
+ expect(sanitizeSecretValuesInText(input), input).toBe(expected);
655
+ }
656
+ });
657
+
658
+ it('keeps state words, non-secret keys, and URLs', () => {
659
+ for (let prose of [
660
+ 'token: expired',
661
+ 'token: expired.',
662
+ 'Error fetching access token: invalid_grant',
663
+ 'refresh_token: invalid_grant',
664
+ 'password: required',
665
+ 'access_token: missing',
666
+ 'refresh_token: revoked',
667
+ 'Error: Request failed',
668
+ 'see https://api.example.com:443/v1',
669
+ 'token_type: bearer',
670
+ 'tokenCount: 12'
671
+ ]) {
672
+ expect(sanitizeSecretValuesInText(prose), prose).toBe(prose);
673
+ }
674
+ });
675
+ });
676
+
677
+ describe('sanitizeUrl credential query values', () => {
678
+ it('redacts key and hapikey params and known credential formats', () => {
679
+ let googleKey = `AIza${'b'.repeat(35)}`;
680
+ expect(sanitizeUrl(`https://maps.googleapis.com/api?key=${googleKey}&q=x`)).toBe(
681
+ 'https://maps.googleapis.com/api?key=[redacted]&q=x'
682
+ );
683
+ expect(sanitizeUrl('https://api.hubapi.com/contacts?hapikey=abc-123')).toBe(
684
+ 'https://api.hubapi.com/contacts?hapikey=[redacted]'
685
+ );
686
+ expect(sanitizeUrl(`https://example.com/x?q=sk_live_${'a'.repeat(24)}&page=2`)).toBe(
687
+ 'https://example.com/x?q=[redacted]&page=2'
688
+ );
689
+ expect(sanitizeUrl(`https://example.com/x#q=sk_live_${'a'.repeat(24)}`)).toBe(
690
+ 'https://example.com/x#q=[redacted]'
691
+ );
692
+ expect(sanitizeUrl(`not a url?q=sk_live_${'a'.repeat(24)}`)).toBe(
693
+ 'not a url?q=[redacted]'
694
+ );
695
+ });
696
+
697
+ it('keeps compound key params', () => {
698
+ expect(sanitizeUrl('https://example.com/x?sort_key=name&partition_key=p1')).toBe(
699
+ 'https://example.com/x?sort_key=name&partition_key=p1'
700
+ );
701
+ });
702
+ });