@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.
- package/dist/index.cjs +2305 -1876
- package/dist/index.d.cts +82 -8
- package/dist/index.d.ts +82 -8
- package/dist/index.module.js +2295 -1876
- package/package.json +1 -1
- package/src/action/getFileUrlTool.ts +2 -1
- package/src/action/triggerGroup.ts +65 -3
- package/src/auth/redact.test.ts +60 -2
- package/src/auth/redact.ts +59 -23
- package/src/axios/trace.test.ts +326 -1
- package/src/axios/trace.ts +334 -14
- package/src/error/api.test.ts +123 -1
- package/src/error/api.ts +148 -0
- package/src/error/axios.ts +48 -13
- package/src/error/utils.test.ts +654 -0
- package/src/error/utils.ts +268 -13
package/src/axios/trace.test.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
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
|
+
});
|