@slates/provider 1.0.0-rc.1 → 1.0.0-rc.10

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.
@@ -0,0 +1,378 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __traceInternals } from './trace';
3
+
4
+ let {
5
+ sanitizeFreeText,
6
+ sanitizeUrl,
7
+ sanitizeHeaders,
8
+ isSafeHeaderName,
9
+ isSecretKeyName,
10
+ isUrlFormSecretKeyName,
11
+ redactStructuredValue
12
+ } = __traceInternals;
13
+
14
+ describe('isSecretKeyName (structured keys)', () => {
15
+ let secretLike = [
16
+ 'authorization',
17
+ 'Authorization',
18
+ 'proxy-authorization',
19
+ 'x-api-key',
20
+ 'X-Api-Key',
21
+ 'apikey',
22
+ 'api_key',
23
+ 'api.key',
24
+ 'access_token',
25
+ 'refresh-token',
26
+ 'id_token',
27
+ 'client_secret',
28
+ 'client-secret',
29
+ 'consumer_secret',
30
+ 'private_key',
31
+ 'privateKey',
32
+ 'webhook-signing-secret',
33
+ 'auth_code',
34
+ 'authorization_code',
35
+ 'code_verifier',
36
+ 'code_challenge',
37
+ 'password',
38
+ 'passwd',
39
+ 'pwd',
40
+ 'passphrase',
41
+ 'pin',
42
+ 'passcode',
43
+ 'secret',
44
+ 'credentials',
45
+ 'creds',
46
+ 'x-auth',
47
+ 'x-csrf-token',
48
+ 'x-xsrf-token',
49
+ 'jwt',
50
+ 'session-id',
51
+ 'sid',
52
+ 'cookie',
53
+ 'set-cookie',
54
+ 'x-hub-signature',
55
+ 'signature',
56
+ 'x-sig',
57
+ 'hmac',
58
+ 'salt',
59
+ 'nonce',
60
+ 'otp',
61
+ 'totp',
62
+ 'mfa',
63
+ '2fa',
64
+ 'verification-code',
65
+ 'sms_code',
66
+ 'mnemonic',
67
+ 'recovery-key',
68
+ 'card_number',
69
+ 'cvv',
70
+ 'cvc',
71
+ 'ssn',
72
+ 'orgname_access_token',
73
+ 'my.private.key'
74
+ ];
75
+
76
+ let nonSecretLike = [
77
+ 'accept',
78
+ 'content-type',
79
+ 'user-agent',
80
+ 'etag',
81
+ 'location',
82
+ 'retry-after',
83
+ 'x-request-id',
84
+ 'configure',
85
+ 'config',
86
+ 'design',
87
+ 'signal',
88
+ 'assignment',
89
+ 'condescend',
90
+ 'code',
91
+ 'status-code',
92
+ 'error-code',
93
+ 'http-code',
94
+ 'status',
95
+ 'encode',
96
+ 'decoder',
97
+ 'object-key',
98
+ 'partition-key',
99
+ 'sort-key',
100
+ 'first-name',
101
+ 'last-name',
102
+ 'email-address',
103
+ 'phone',
104
+ 'timestamp',
105
+ 'created-at',
106
+ 'token_type',
107
+ 'token-type',
108
+ 'grant_type',
109
+ 'grant-type',
110
+ 'auth_type',
111
+ 'auth_method',
112
+ 'signature_method',
113
+ 'signature_version',
114
+ 'key_id',
115
+ 'kid',
116
+ 'key_name',
117
+ 'password_strength'
118
+ ];
119
+
120
+ for (let key of secretLike) {
121
+ it(`treats "${key}" as secret`, () => {
122
+ expect(isSecretKeyName(key)).toBe(true);
123
+ });
124
+ }
125
+
126
+ for (let key of nonSecretLike) {
127
+ it(`leaves "${key}" alone`, () => {
128
+ expect(isSecretKeyName(key)).toBe(false);
129
+ });
130
+ }
131
+ });
132
+
133
+ describe('isUrlFormSecretKeyName (URL/form keys)', () => {
134
+ it('flags OAuth auth code key', () => {
135
+ expect(isUrlFormSecretKeyName('code')).toBe(true);
136
+ });
137
+
138
+ it('does not flag grant_type or token_type', () => {
139
+ expect(isUrlFormSecretKeyName('grant_type')).toBe(false);
140
+ expect(isUrlFormSecretKeyName('token_type')).toBe(false);
141
+ });
142
+
143
+ it('does not flag common non-secret keys', () => {
144
+ expect(isUrlFormSecretKeyName('page')).toBe(false);
145
+ expect(isUrlFormSecretKeyName('limit')).toBe(false);
146
+ expect(isUrlFormSecretKeyName('q')).toBe(false);
147
+ });
148
+ });
149
+
150
+ describe('sanitizeFreeText', () => {
151
+ it('redacts Bearer tokens', () => {
152
+ expect(sanitizeFreeText('Authorization: Bearer abc123XYZ==')).toBe(
153
+ 'Authorization: Bearer [redacted]'
154
+ );
155
+ });
156
+
157
+ it('redacts Basic/Digest/Negotiate schemes', () => {
158
+ expect(sanitizeFreeText('Basic dXNlcjpwdw==')).toBe('Basic [redacted]');
159
+ expect(sanitizeFreeText('Digest abcdef123456')).toBe('Digest [redacted]');
160
+ expect(sanitizeFreeText('Negotiate YIIFigYGKwYB')).toBe('Negotiate [redacted]');
161
+ });
162
+
163
+ it('redacts JWT tokens by value', () => {
164
+ let jwt =
165
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
166
+ expect(sanitizeFreeText(`token=${jwt}`)).not.toContain(jwt);
167
+ expect(sanitizeFreeText(`user said: ${jwt} ok`)).toContain('[redacted]');
168
+ expect(sanitizeFreeText(`user said: ${jwt} ok`)).not.toContain(jwt);
169
+ });
170
+
171
+ it('redacts AWS access key IDs', () => {
172
+ let key = 'AKIAIOSFODNN7EXAMPLE';
173
+ expect(sanitizeFreeText(`AWS key ${key} embedded`)).not.toContain(key);
174
+ });
175
+
176
+ it('redacts GitHub tokens', () => {
177
+ let token = 'ghp_' + 'a'.repeat(40);
178
+ expect(sanitizeFreeText(token)).toBe('[redacted]');
179
+ });
180
+
181
+ it('redacts Slack tokens', () => {
182
+ let token = 'xoxb-1234567890-abcdefghij';
183
+ expect(sanitizeFreeText(`token=${token}`)).not.toContain(token);
184
+ });
185
+
186
+ it('redacts Stripe secret keys', () => {
187
+ let key = 'sk_live_' + 'a'.repeat(24);
188
+ expect(sanitizeFreeText(`{"key":"${key}"}`)).not.toContain(key);
189
+ });
190
+
191
+ it('redacts OpenAI API keys', () => {
192
+ let key = 'sk-proj-' + 'a'.repeat(40);
193
+ expect(sanitizeFreeText(key)).toBe('[redacted]');
194
+ });
195
+
196
+ it('redacts Anthropic API keys', () => {
197
+ let key = 'sk-ant-' + 'a'.repeat(40);
198
+ expect(sanitizeFreeText(key)).toBe('[redacted]');
199
+ });
200
+
201
+ it('redacts Google API keys', () => {
202
+ let key = 'AIza' + 'a'.repeat(35);
203
+ expect(sanitizeFreeText(key)).toBe('[redacted]');
204
+ });
205
+
206
+ it('redacts PEM private key blocks', () => {
207
+ let pem =
208
+ '-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----';
209
+ let input = `key: ${pem}`;
210
+ let redacted = sanitizeFreeText(input);
211
+ expect(redacted).not.toContain('MIIEpAIBAAK');
212
+ expect(redacted).toContain('[redacted]');
213
+ });
214
+
215
+ it('redacts form-encoded OAuth auth code', () => {
216
+ expect(
217
+ sanitizeFreeText('grant_type=authorization_code&code=abc123&client_secret=xyz')
218
+ ).toBe('grant_type=authorization_code&code=[redacted]&client_secret=[redacted]');
219
+ });
220
+
221
+ it('redacts form-encoded client_secret without touching grant_type', () => {
222
+ expect(sanitizeFreeText('grant_type=password&password=hunter2')).toBe(
223
+ 'grant_type=password&password=[redacted]'
224
+ );
225
+ });
226
+
227
+ it('redacts JSON api_key / secret / token values in stringified JSON', () => {
228
+ let input = JSON.stringify({
229
+ api_key: 'abc',
230
+ access_token: 'tkn',
231
+ refresh_token: 'ref',
232
+ signature: 'sig',
233
+ nested: 'ok'
234
+ });
235
+ let out = sanitizeFreeText(input);
236
+ expect(out).toContain('"api_key":"[redacted]"');
237
+ expect(out).toContain('"access_token":"[redacted]"');
238
+ expect(out).toContain('"refresh_token":"[redacted]"');
239
+ expect(out).toContain('"signature":"[redacted]"');
240
+ expect(out).toContain('"nested":"ok"');
241
+ });
242
+
243
+ it('does not redact innocuous key=value pairs', () => {
244
+ expect(sanitizeFreeText('page=2&limit=10')).toBe('page=2&limit=10');
245
+ });
246
+ });
247
+
248
+ describe('sanitizeUrl', () => {
249
+ it('redacts common secret query params', () => {
250
+ expect(
251
+ sanitizeUrl('https://example.com/cb?api_key=abc&client_secret=def&visible=yes')
252
+ ).toBe('https://example.com/cb?api_key=[redacted]&client_secret=[redacted]&visible=yes');
253
+ });
254
+
255
+ it('redacts OAuth auth code in query', () => {
256
+ expect(sanitizeUrl('https://example.com/cb?code=abc&state=xyz')).toBe(
257
+ 'https://example.com/cb?code=[redacted]&state=xyz'
258
+ );
259
+ });
260
+
261
+ it('strips basic auth userinfo', () => {
262
+ expect(sanitizeUrl('https://user:pass@example.com/api?foo=bar')).toBe(
263
+ 'https://example.com/api?foo=bar'
264
+ );
265
+ });
266
+
267
+ it('redacts secrets embedded in URL fragment (OAuth implicit flow)', () => {
268
+ let input =
269
+ 'https://example.com/cb#access_token=xyz&token_type=bearer&expires_in=3600';
270
+ let out = sanitizeUrl(input);
271
+ expect(out).toContain('access_token=[redacted]');
272
+ expect(out).toContain('token_type=bearer');
273
+ expect(out).toContain('expires_in=3600');
274
+ });
275
+
276
+ it('leaves innocuous URLs unchanged', () => {
277
+ expect(sanitizeUrl('https://example.com/api?page=2&limit=10')).toBe(
278
+ 'https://example.com/api?page=2&limit=10'
279
+ );
280
+ });
281
+ });
282
+
283
+ describe('sanitizeHeaders', () => {
284
+ it('drops headers with secret-like names', () => {
285
+ let sanitized = sanitizeHeaders({
286
+ authorization: 'Bearer xyz',
287
+ 'x-api-key': 'key',
288
+ 'x-auth-token': 'tkn',
289
+ 'x-csrf-token': 'csrf',
290
+ cookie: 'session=abc',
291
+ 'set-cookie': 'session=abc',
292
+ 'x-hub-signature': 'sig',
293
+ 'x-auth-code': 'code'
294
+ });
295
+ expect(sanitized).toBeUndefined();
296
+ });
297
+
298
+ it('keeps safe headers and redacts tokens in their values', () => {
299
+ let sanitized = sanitizeHeaders({
300
+ 'content-type': 'application/json',
301
+ accept: 'application/json',
302
+ 'user-agent': 'test-agent',
303
+ 'x-request-id': 'req-123'
304
+ });
305
+ expect(sanitized).toEqual({
306
+ 'content-type': 'application/json',
307
+ accept: 'application/json',
308
+ 'user-agent': 'test-agent',
309
+ 'x-request-id': 'req-123'
310
+ });
311
+ });
312
+
313
+ it('redacts JWTs/secret tokens that leak into safe header values', () => {
314
+ let jwt =
315
+ 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
316
+ let sanitized = sanitizeHeaders({
317
+ 'user-agent': `my-app/${jwt}`
318
+ });
319
+ expect(sanitized?.['user-agent']).not.toContain(jwt);
320
+ expect(sanitized?.['user-agent']).toContain('[redacted]');
321
+ });
322
+ });
323
+
324
+ describe('isSafeHeaderName', () => {
325
+ it('rejects common compound secret header names', () => {
326
+ expect(isSafeHeaderName('x-amz-security-token')).toBe(false);
327
+ expect(isSafeHeaderName('x-org-api-key')).toBe(false);
328
+ expect(isSafeHeaderName('authorization')).toBe(false);
329
+ expect(isSafeHeaderName('x-auth-code')).toBe(false);
330
+ expect(isSafeHeaderName('x-webhook-secret')).toBe(false);
331
+ });
332
+
333
+ it('does not falsely reject look-alike names', () => {
334
+ expect(isSafeHeaderName('x-correlation-id')).toBe(true);
335
+ expect(isSafeHeaderName('content-type')).toBe(true);
336
+ });
337
+
338
+ it('allows ratelimit-* families', () => {
339
+ expect(isSafeHeaderName('ratelimit-remaining')).toBe(true);
340
+ expect(isSafeHeaderName('x-ratelimit-reset')).toBe(true);
341
+ });
342
+ });
343
+
344
+ describe('redactStructuredValue', () => {
345
+ it('replaces secret-named fields with [redacted]', () => {
346
+ let out = redactStructuredValue({
347
+ api_key: 'abc',
348
+ accessToken: 'xyz',
349
+ nested: { client_secret: 'nested-secret', other: 'visible' },
350
+ list: ['a', 'b']
351
+ });
352
+
353
+ expect(out).toEqual({
354
+ api_key: '[redacted]',
355
+ accessToken: '[redacted]',
356
+ nested: { client_secret: '[redacted]', other: 'visible' },
357
+ list: ['a', 'b']
358
+ });
359
+ });
360
+
361
+ it('scrubs JWT values inside unrelated fields', () => {
362
+ let jwt =
363
+ 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
364
+ let out = redactStructuredValue({ message: `user: ${jwt}` }) as { message: string };
365
+ expect(out.message).not.toContain(jwt);
366
+ expect(out.message).toContain('[redacted]');
367
+ });
368
+
369
+ it('does not redact HTTP status code integers', () => {
370
+ let out = redactStructuredValue({ error: 'not_found', code: 404 });
371
+ expect(out).toEqual({ error: 'not_found', code: 404 });
372
+ });
373
+
374
+ it('preserves arrays and numerics', () => {
375
+ let out = redactStructuredValue([1, 'two', { three: 3 }]);
376
+ expect(out).toEqual([1, 'two', { three: 3 }]);
377
+ });
378
+ });