autotel 3.1.1 → 3.2.0
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/attribute-redacting-processor.cjs +8 -8
- package/dist/attribute-redacting-processor.js +1 -1
- package/dist/attributes.cjs +21 -21
- package/dist/attributes.js +2 -2
- package/dist/auto.cjs +3 -3
- package/dist/auto.js +2 -2
- package/dist/{chunk-DDXIUZEG.js → chunk-454CH4OV.js} +3 -3
- package/dist/{chunk-DDXIUZEG.js.map → chunk-454CH4OV.js.map} +1 -1
- package/dist/{chunk-ZPERWNOP.cjs → chunk-4UUEGERM.cjs} +17 -17
- package/dist/{chunk-ZPERWNOP.cjs.map → chunk-4UUEGERM.cjs.map} +1 -1
- package/dist/{chunk-MXO6LXV5.cjs → chunk-5RZ3NZ2M.cjs} +5 -5
- package/dist/{chunk-MXO6LXV5.cjs.map → chunk-5RZ3NZ2M.cjs.map} +1 -1
- package/dist/{chunk-FTBBBPT6.js → chunk-7EVW3Z37.js} +13 -22
- package/dist/chunk-7EVW3Z37.js.map +1 -0
- package/dist/{chunk-KPDIEVVV.cjs → chunk-EEQHQKPP.cjs} +32 -32
- package/dist/chunk-EEQHQKPP.cjs.map +1 -0
- package/dist/{chunk-T7CPAGOI.js → chunk-FVA2YDEQ.js} +4 -4
- package/dist/chunk-FVA2YDEQ.js.map +1 -0
- package/dist/{chunk-PEEUMQ3R.js → chunk-IS2QJ44P.js} +3 -3
- package/dist/{chunk-PEEUMQ3R.js.map → chunk-IS2QJ44P.js.map} +1 -1
- package/dist/{chunk-45B2GD4P.cjs → chunk-KKIYPZOP.cjs} +6 -6
- package/dist/{chunk-45B2GD4P.cjs.map → chunk-KKIYPZOP.cjs.map} +1 -1
- package/dist/{chunk-DQ2SUROF.cjs → chunk-M3LFHHTN.cjs} +4 -4
- package/dist/{chunk-DQ2SUROF.cjs.map → chunk-M3LFHHTN.cjs.map} +1 -1
- package/dist/{chunk-NXLRY2CE.cjs → chunk-NEIB3TLD.cjs} +10 -8
- package/dist/chunk-NEIB3TLD.cjs.map +1 -0
- package/dist/{chunk-6TFJF7SS.js → chunk-NIDUQZIN.js} +3 -3
- package/dist/{chunk-6TFJF7SS.js.map → chunk-NIDUQZIN.js.map} +1 -1
- package/dist/{chunk-YPQMAE6U.cjs → chunk-NN2GODP4.cjs} +7 -7
- package/dist/{chunk-YPQMAE6U.cjs.map → chunk-NN2GODP4.cjs.map} +1 -1
- package/dist/{chunk-MHPYLMQS.js → chunk-QVLMGNQF.js} +4 -4
- package/dist/{chunk-MHPYLMQS.js.map → chunk-QVLMGNQF.js.map} +1 -1
- package/dist/{chunk-6X2GG65S.cjs → chunk-RRTFFAG3.cjs} +5 -5
- package/dist/{chunk-6X2GG65S.cjs.map → chunk-RRTFFAG3.cjs.map} +1 -1
- package/dist/{chunk-JVWJDHDB.js → chunk-RUPKBKUF.js} +10 -8
- package/dist/chunk-RUPKBKUF.js.map +1 -0
- package/dist/{chunk-52ALHU7T.js → chunk-RZI5XXAD.js} +3 -3
- package/dist/{chunk-52ALHU7T.js.map → chunk-RZI5XXAD.js.map} +1 -1
- package/dist/{chunk-LIYNUGML.cjs → chunk-UV64CWMA.cjs} +23 -32
- package/dist/chunk-UV64CWMA.cjs.map +1 -0
- package/dist/{chunk-MYWQELNY.js → chunk-ZKKJQS6R.js} +3 -3
- package/dist/{chunk-MYWQELNY.js.map → chunk-ZKKJQS6R.js.map} +1 -1
- package/dist/correlation-id.cjs +11 -11
- package/dist/correlation-id.js +3 -3
- package/dist/decorators.cjs +5 -5
- package/dist/decorators.js +4 -4
- package/dist/event-subscriber.d.cts +15 -1
- package/dist/event-subscriber.d.ts +15 -1
- package/dist/event.cjs +7 -7
- package/dist/event.js +4 -4
- package/dist/functional.cjs +12 -12
- package/dist/functional.js +4 -4
- package/dist/http.cjs +4 -4
- package/dist/http.js +3 -3
- package/dist/index.cjs +135 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +54 -14
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.cjs +9 -9
- package/dist/instrumentation.js +2 -2
- package/dist/messaging.cjs +8 -8
- package/dist/messaging.js +5 -5
- package/dist/semantic-helpers.cjs +9 -9
- package/dist/semantic-helpers.js +5 -5
- package/dist/webhook.cjs +6 -6
- package/dist/webhook.js +4 -4
- package/dist/workflow-distributed.cjs +6 -6
- package/dist/workflow-distributed.js +4 -4
- package/dist/workflow.cjs +9 -9
- package/dist/workflow.js +5 -5
- package/package.json +1 -1
- package/src/attribute-redacting-processor.ts +12 -9
- package/src/define-event.test.ts +41 -0
- package/src/define-event.ts +77 -0
- package/src/event-queue.ts +4 -0
- package/src/event-subscriber.ts +15 -0
- package/src/functional.ts +2 -1
- package/src/index.ts +6 -0
- package/src/track.ts +3 -0
- package/src/validation.test.ts +7 -3
- package/src/validation.ts +19 -21
- package/dist/chunk-FTBBBPT6.js.map +0 -1
- package/dist/chunk-JVWJDHDB.js.map +0 -1
- package/dist/chunk-KPDIEVVV.cjs.map +0 -1
- package/dist/chunk-LIYNUGML.cjs.map +0 -1
- package/dist/chunk-NXLRY2CE.cjs.map +0 -1
- package/dist/chunk-T7CPAGOI.js.map +0 -1
package/dist/workflow.cjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk5RZ3NZ2M_cjs = require('./chunk-5RZ3NZ2M.cjs');
|
|
4
4
|
require('./chunk-4P6ZOARG.cjs');
|
|
5
|
-
require('./chunk-
|
|
5
|
+
require('./chunk-EEQHQKPP.cjs');
|
|
6
6
|
require('./chunk-2GIBANLB.cjs');
|
|
7
7
|
require('./chunk-VQTCQKHQ.cjs');
|
|
8
|
-
require('./chunk-
|
|
9
|
-
require('./chunk-
|
|
8
|
+
require('./chunk-UV64CWMA.cjs');
|
|
9
|
+
require('./chunk-KKIYPZOP.cjs');
|
|
10
10
|
require('./chunk-FEEVB2GV.cjs');
|
|
11
11
|
require('./chunk-CEAQK2QY.cjs');
|
|
12
12
|
require('./chunk-ZNMBW67B.cjs');
|
|
13
13
|
require('./chunk-IOYFAFHJ.cjs');
|
|
14
|
-
require('./chunk-
|
|
14
|
+
require('./chunk-NEIB3TLD.cjs');
|
|
15
15
|
require('./chunk-CU6IDACR.cjs');
|
|
16
16
|
require('./chunk-6S5RUKU3.cjs');
|
|
17
17
|
require('./chunk-HR5YFXZW.cjs');
|
|
@@ -24,19 +24,19 @@ require('./chunk-YREV3LGG.cjs');
|
|
|
24
24
|
|
|
25
25
|
Object.defineProperty(exports, "getCurrentWorkflowContext", {
|
|
26
26
|
enumerable: true,
|
|
27
|
-
get: function () { return
|
|
27
|
+
get: function () { return chunk5RZ3NZ2M_cjs.getCurrentWorkflowContext; }
|
|
28
28
|
});
|
|
29
29
|
Object.defineProperty(exports, "isInWorkflow", {
|
|
30
30
|
enumerable: true,
|
|
31
|
-
get: function () { return
|
|
31
|
+
get: function () { return chunk5RZ3NZ2M_cjs.isInWorkflow; }
|
|
32
32
|
});
|
|
33
33
|
Object.defineProperty(exports, "traceStep", {
|
|
34
34
|
enumerable: true,
|
|
35
|
-
get: function () { return
|
|
35
|
+
get: function () { return chunk5RZ3NZ2M_cjs.traceStep; }
|
|
36
36
|
});
|
|
37
37
|
Object.defineProperty(exports, "traceWorkflow", {
|
|
38
38
|
enumerable: true,
|
|
39
|
-
get: function () { return
|
|
39
|
+
get: function () { return chunk5RZ3NZ2M_cjs.traceWorkflow; }
|
|
40
40
|
});
|
|
41
41
|
//# sourceMappingURL=workflow.cjs.map
|
|
42
42
|
//# sourceMappingURL=workflow.cjs.map
|
package/dist/workflow.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-
|
|
1
|
+
export { getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow } from './chunk-IS2QJ44P.js';
|
|
2
2
|
import './chunk-KIL5CUN6.js';
|
|
3
|
-
import './chunk-
|
|
3
|
+
import './chunk-FVA2YDEQ.js';
|
|
4
4
|
import './chunk-HLZ7H3VZ.js';
|
|
5
5
|
import './chunk-SEO6NAQT.js';
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import './chunk-7EVW3Z37.js';
|
|
7
|
+
import './chunk-ZKKJQS6R.js';
|
|
8
8
|
import './chunk-643PQG3Y.js';
|
|
9
9
|
import './chunk-A4E5AQFK.js';
|
|
10
10
|
import './chunk-WGWSHJ2N.js';
|
|
11
11
|
import './chunk-GYR5K654.js';
|
|
12
|
-
import './chunk-
|
|
12
|
+
import './chunk-RUPKBKUF.js';
|
|
13
13
|
import './chunk-6UQRVUN3.js';
|
|
14
14
|
import './chunk-3QXBFGKP.js';
|
|
15
15
|
import './chunk-KVDA4HX2.js';
|
package/package.json
CHANGED
|
@@ -424,19 +424,22 @@ function createRedactorFromConfig(
|
|
|
424
424
|
.map((vp) => [cloneRegex(vp.pattern), vp.mask!]);
|
|
425
425
|
|
|
426
426
|
return (key: string, value: AttributeValue): AttributeValue => {
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
// Key-pattern and path-based redaction only applies to string values.
|
|
428
|
+
// Numbers, booleans and other non-string attributes are not credentials;
|
|
429
|
+
// replacing them with the string '[REDACTED]' silently changes their
|
|
430
|
+
// type and corrupts downstream consumers (LLM token counters etc.).
|
|
431
|
+
if (typeof value === 'string') {
|
|
432
|
+
for (const pattern of keyPatterns) {
|
|
433
|
+
pattern.lastIndex = 0;
|
|
434
|
+
if (pattern.test(key)) {
|
|
435
|
+
return defaultReplacement;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (pathSet.has(key)) {
|
|
431
439
|
return defaultReplacement;
|
|
432
440
|
}
|
|
433
441
|
}
|
|
434
442
|
|
|
435
|
-
// Check if key matches any path-based redaction
|
|
436
|
-
if (pathSet.has(key)) {
|
|
437
|
-
return defaultReplacement;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
443
|
// For non-string values, return as-is
|
|
441
444
|
if (typeof value !== 'string') {
|
|
442
445
|
if (Array.isArray(value)) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { defineEvent } from './define-event';
|
|
3
|
+
|
|
4
|
+
describe('defineEvent', () => {
|
|
5
|
+
it('validates payload and exposes schema metadata when provided', () => {
|
|
6
|
+
const event = defineEvent(
|
|
7
|
+
'order.placed',
|
|
8
|
+
{
|
|
9
|
+
safeParse(input: unknown) {
|
|
10
|
+
if (
|
|
11
|
+
typeof input === 'object' &&
|
|
12
|
+
input !== null &&
|
|
13
|
+
'orderId' in input &&
|
|
14
|
+
typeof (input as Record<string, unknown>).orderId === 'string'
|
|
15
|
+
) {
|
|
16
|
+
return {
|
|
17
|
+
success: true as const,
|
|
18
|
+
data: input as { orderId: string },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { success: false as const, error: new Error('invalid') };
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
toJsonSchema: () => ({
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: { orderId: { type: 'string' } },
|
|
28
|
+
required: ['orderId'],
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(event.name).toBe('order.placed');
|
|
34
|
+
expect(event.schemaMetadata?.source).toBe('zod');
|
|
35
|
+
expect(event.schemaMetadata?.hash).toMatch(/^[a-f0-9]{64}$/);
|
|
36
|
+
expect(() => event.track({ orderId: 'o-1' })).not.toThrow();
|
|
37
|
+
expect(() => event.track({} as { orderId: string })).toThrow(
|
|
38
|
+
/Schema validation failed/,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { track } from './track';
|
|
3
|
+
import type { EventSchemaMetadata } from './event-subscriber';
|
|
4
|
+
|
|
5
|
+
type SafeParseResult<T> =
|
|
6
|
+
| { success: true; data: T }
|
|
7
|
+
| { success: false; error: unknown };
|
|
8
|
+
|
|
9
|
+
export interface SchemaLike<T> {
|
|
10
|
+
safeParse(input: unknown): SafeParseResult<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DefineEventOptions<S> {
|
|
14
|
+
toJsonSchema?: (schema: S) => unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DefinedEvent<Name extends string, Payload> {
|
|
18
|
+
readonly name: Name;
|
|
19
|
+
readonly schemaMetadata?: EventSchemaMetadata;
|
|
20
|
+
track(payload: Payload): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defineEvent<
|
|
24
|
+
Name extends string,
|
|
25
|
+
Payload,
|
|
26
|
+
S extends SchemaLike<Payload>,
|
|
27
|
+
>(
|
|
28
|
+
name: Name,
|
|
29
|
+
schema: S,
|
|
30
|
+
options: DefineEventOptions<S> = {},
|
|
31
|
+
): DefinedEvent<Name, Payload> {
|
|
32
|
+
const jsonSchema = options.toJsonSchema?.(schema);
|
|
33
|
+
const schemaMetadata = jsonSchema
|
|
34
|
+
? {
|
|
35
|
+
source: 'zod' as const,
|
|
36
|
+
jsonSchema,
|
|
37
|
+
hash: hashSchema(jsonSchema),
|
|
38
|
+
}
|
|
39
|
+
: undefined;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
schemaMetadata,
|
|
44
|
+
track(payload: Payload) {
|
|
45
|
+
const parsed = schema.safeParse(payload);
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Invalid payload for event "${name}". Schema validation failed.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
track(
|
|
52
|
+
name,
|
|
53
|
+
parsed.data,
|
|
54
|
+
schemaMetadata ? { schema: schemaMetadata } : undefined,
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hashSchema(schema: unknown): string {
|
|
61
|
+
return createHash('sha256').update(stableStringify(schema)).digest('hex');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function stableStringify(value: unknown): string {
|
|
65
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
66
|
+
return JSON.stringify(value);
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
return '[' + value.map((v) => stableStringify(v)).join(',') + ']';
|
|
70
|
+
}
|
|
71
|
+
const obj = value as Record<string, unknown>;
|
|
72
|
+
const body = Object.keys(obj)
|
|
73
|
+
.sort()
|
|
74
|
+
.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k]))
|
|
75
|
+
.join(',');
|
|
76
|
+
return '{' + body + '}';
|
|
77
|
+
}
|
package/src/event-queue.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
EventSubscriber,
|
|
23
23
|
EventAttributes,
|
|
24
24
|
AutotelEventContext,
|
|
25
|
+
EventSchemaMetadata,
|
|
25
26
|
} from './event-subscriber';
|
|
26
27
|
import { getLogger } from './init';
|
|
27
28
|
import { getConfig as getRuntimeConfig } from './config';
|
|
@@ -38,6 +39,8 @@ export interface EventData {
|
|
|
38
39
|
_traceId?: string;
|
|
39
40
|
/** Autotel context for trace correlation (passed to subscribers) */
|
|
40
41
|
autotel?: AutotelEventContext;
|
|
42
|
+
/** Optional schema metadata for contract-aware subscribers. */
|
|
43
|
+
schema?: EventSchemaMetadata;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
/**
|
|
@@ -591,6 +594,7 @@ export class EventQueue {
|
|
|
591
594
|
try {
|
|
592
595
|
await subscriber.trackEvent(event.name, event.attributes, {
|
|
593
596
|
autotel: event.autotel,
|
|
597
|
+
schema: event.schema,
|
|
594
598
|
});
|
|
595
599
|
this.recordDelivered(event, subscriberName, startTime);
|
|
596
600
|
return { subscriberName, success: true };
|
package/src/event-subscriber.ts
CHANGED
|
@@ -101,12 +101,27 @@ export interface AutotelEventContext {
|
|
|
101
101
|
linked_trace_ids?: string[];
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Optional machine-readable schema metadata attached to an event payload.
|
|
106
|
+
* Intended for contract-aware subscribers (e.g. architecture snapshot capture).
|
|
107
|
+
*/
|
|
108
|
+
export interface EventSchemaMetadata {
|
|
109
|
+
/** Schema source format used at the call site. */
|
|
110
|
+
source: 'zod';
|
|
111
|
+
/** JSON Schema representation of the payload contract. */
|
|
112
|
+
jsonSchema: unknown;
|
|
113
|
+
/** Stable schema hash for change detection and cache keys. */
|
|
114
|
+
hash: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
/**
|
|
105
118
|
* Options for event tracking methods
|
|
106
119
|
*/
|
|
107
120
|
export interface EventTrackingOptions {
|
|
108
121
|
/** Autotel trace context to include in the event */
|
|
109
122
|
autotel?: AutotelEventContext;
|
|
123
|
+
/** Optional event payload schema metadata */
|
|
124
|
+
schema?: EventSchemaMetadata;
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
/**
|
package/src/functional.ts
CHANGED
|
@@ -210,7 +210,8 @@ function hasImmediateExecutionMark(fn: unknown): boolean {
|
|
|
210
210
|
*/
|
|
211
211
|
export function markAsImmediate<F>(fn: F): F {
|
|
212
212
|
if (typeof fn === 'function') {
|
|
213
|
-
(fn as unknown as ImmediateExecutionFlag)[IMMEDIATE_EXECUTION_SYMBOL] =
|
|
213
|
+
(fn as unknown as ImmediateExecutionFlag)[IMMEDIATE_EXECUTION_SYMBOL] =
|
|
214
|
+
true;
|
|
214
215
|
}
|
|
215
216
|
return fn;
|
|
216
217
|
}
|
package/src/index.ts
CHANGED
|
@@ -106,6 +106,12 @@ export {
|
|
|
106
106
|
|
|
107
107
|
// Global track function
|
|
108
108
|
export { track, getEventQueue } from './track';
|
|
109
|
+
export {
|
|
110
|
+
defineEvent,
|
|
111
|
+
type SchemaLike,
|
|
112
|
+
type DefineEventOptions,
|
|
113
|
+
type DefinedEvent,
|
|
114
|
+
} from './define-event';
|
|
109
115
|
|
|
110
116
|
// Correlation ID utilities
|
|
111
117
|
export {
|
package/src/track.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from './init';
|
|
16
16
|
import { validateEvent } from './validation';
|
|
17
17
|
import { getOrCreateCorrelationId } from './correlation-id';
|
|
18
|
+
import type { EventTrackingOptions } from './event-subscriber';
|
|
18
19
|
import type { AutotelEventContext } from './event-subscriber';
|
|
19
20
|
|
|
20
21
|
// Global events queue (initialized on first track call)
|
|
@@ -167,6 +168,7 @@ function getOrCreateQueue(): EventQueue | null {
|
|
|
167
168
|
export function track<Events extends Record<string, any> = Record<string, any>>(
|
|
168
169
|
event: keyof Events & string,
|
|
169
170
|
data?: Events[typeof event],
|
|
171
|
+
options?: EventTrackingOptions,
|
|
170
172
|
): void {
|
|
171
173
|
const queue = getOrCreateQueue();
|
|
172
174
|
if (!queue) return; // No-op if not initialized or no subscribers
|
|
@@ -193,6 +195,7 @@ export function track<Events extends Record<string, any> = Record<string, any>>(
|
|
|
193
195
|
attributes: enrichedData,
|
|
194
196
|
timestamp: Date.now(),
|
|
195
197
|
autotel: autotelContext,
|
|
198
|
+
schema: options?.schema,
|
|
196
199
|
});
|
|
197
200
|
}
|
|
198
201
|
|
package/src/validation.test.ts
CHANGED
|
@@ -298,17 +298,21 @@ describe('Sensitive data patterns', () => {
|
|
|
298
298
|
expect(result?.API_KEY).toBe('[REDACTED]');
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
-
it('should redact auth fields', () => {
|
|
301
|
+
it('should redact auth fields (strings only)', () => {
|
|
302
302
|
const attrs = {
|
|
303
303
|
auth: 'abc123',
|
|
304
304
|
authorization: 'Bearer token',
|
|
305
|
-
authenticated
|
|
305
|
+
// `authenticated` matches the /auth/i key pattern, but `true` is a
|
|
306
|
+
// boolean status — not a credential — so it passes through unchanged.
|
|
307
|
+
// Redacting it to the string '[REDACTED]' would silently corrupt its
|
|
308
|
+
// type without protecting any secret.
|
|
309
|
+
authenticated: true,
|
|
306
310
|
};
|
|
307
311
|
|
|
308
312
|
const result = validateAttributes(attrs);
|
|
309
313
|
expect(result?.auth).toBe('[REDACTED]');
|
|
310
314
|
expect(result?.authorization).toBe('[REDACTED]');
|
|
311
|
-
expect(result?.authenticated).toBe(
|
|
315
|
+
expect(result?.authenticated).toBe(true);
|
|
312
316
|
});
|
|
313
317
|
|
|
314
318
|
it('should not redact non-sensitive fields with similar names', () => {
|
package/src/validation.ts
CHANGED
|
@@ -130,19 +130,22 @@ export function validateAttributes(
|
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
const value = attributes[key];
|
|
134
|
+
|
|
135
|
+
// Redact sensitive *strings* only. Numeric/boolean values are not
|
|
136
|
+
// credentials and replacing them with the literal string '[REDACTED]'
|
|
137
|
+
// both leaks no useful signal and breaks downstream type expectations
|
|
138
|
+
// (e.g. an LLM `promptTokens` counter becoming a string poisons every
|
|
139
|
+
// consumer that treats it as a number).
|
|
140
|
+
const isSensitive =
|
|
141
|
+
typeof value === 'string' &&
|
|
142
|
+
config.sensitivePatterns.some((pattern) => pattern.test(key));
|
|
137
143
|
|
|
138
144
|
if (isSensitive) {
|
|
139
|
-
// Redact sensitive data
|
|
140
145
|
sanitized[key] = '[REDACTED]';
|
|
141
146
|
continue;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
|
-
// Sanitize value
|
|
145
|
-
const value = attributes[key];
|
|
146
149
|
sanitized[key] = sanitizeValue(value, config, 1) as
|
|
147
150
|
| string
|
|
148
151
|
| number
|
|
@@ -196,20 +199,15 @@ function sanitizeValue(
|
|
|
196
199
|
const sanitized: Record<string, unknown> = {};
|
|
197
200
|
for (const key in value) {
|
|
198
201
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
(value as Record<string, unknown>)[key],
|
|
209
|
-
config,
|
|
210
|
-
depth + 1,
|
|
211
|
-
);
|
|
212
|
-
}
|
|
202
|
+
const nested = (value as Record<string, unknown>)[key];
|
|
203
|
+
// See top-level branch above: only string values are redacted.
|
|
204
|
+
const isSensitive =
|
|
205
|
+
typeof nested === 'string' &&
|
|
206
|
+
config.sensitivePatterns.some((pattern) => pattern.test(key));
|
|
207
|
+
|
|
208
|
+
sanitized[key] = isSensitive
|
|
209
|
+
? '[REDACTED]'
|
|
210
|
+
: sanitizeValue(nested, config, depth + 1);
|
|
213
211
|
}
|
|
214
212
|
}
|
|
215
213
|
return sanitized;
|