@opengeni/db 0.28.1 → 0.28.9
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/{chunk-HT5T62CC.js → chunk-P6CUHXQZ.js} +651 -206
- package/dist/chunk-P6CUHXQZ.js.map +1 -0
- package/dist/{chunk-DBS5HPEW.js → chunk-VHBFHMPC.js} +7 -1
- package/dist/chunk-VHBFHMPC.js.map +1 -0
- package/dist/environment-crypto.d.ts +8 -4
- package/dist/index.d.ts +265 -20
- package/dist/index.js +3966 -2185
- package/dist/index.js.map +1 -1
- package/dist/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- package/dist/persistence-errors.d.ts +7 -14
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +1419 -271
- package/dist/schema.js +9 -1
- package/dist/session-realtime-terminal.d.ts +7 -0
- package/dist/transcription-recordings-schema.d.ts +44 -6
- package/dist/transcription-recordings.d.ts +1 -1
- package/drizzle/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
- package/package.json +4 -4
- package/src/database.ts +7 -1
- package/src/environment-crypto.ts +22 -9
- package/src/index.ts +4253 -1601
- package/src/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- package/src/persistence-errors.ts +29 -34
- package/src/runtime-posture.ts +6 -0
- package/src/schema.ts +260 -38
- package/src/session-control.ts +125 -76
- package/src/session-queue-commands.ts +325 -234
- package/src/session-realtime-context.ts +18 -5
- package/src/session-realtime-ledger.ts +29 -7
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +3 -2
- package/src/session-tool-call-settlement.ts +29 -15
- package/src/transcription-recordings-schema.ts +5 -2
- package/src/transcription-recordings.ts +70 -40
- package/dist/chunk-DBS5HPEW.js.map +0 -1
- package/dist/chunk-HT5T62CC.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- package/src/event-payload-sanitizer.ts +0 -377
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type SecretForRedaction } from "@opengeni/contracts";
|
|
2
|
-
/**
|
|
3
|
-
* Strip NUL and repair invalid/lone UTF-16 surrogates in a single string.
|
|
4
|
-
* Returns the input unchanged (same reference) when it is already clean, so the
|
|
5
|
-
* common case allocates nothing.
|
|
6
|
-
*/
|
|
7
|
-
export declare function sanitizeEventString(value: string): string;
|
|
8
|
-
/**
|
|
9
|
-
* Deep-walk a session event payload and sanitize every string value. Mirrors the
|
|
10
|
-
* shape of the worker redaction deep-walk: objects, arrays, and nested
|
|
11
|
-
* combinations are traversed; non-string leaves pass through untouched. Object
|
|
12
|
-
* keys are sanitized too -- they are jsonb-constrained the same as values.
|
|
13
|
-
*/
|
|
14
|
-
export type SanitizeEventPayloadOptions = {
|
|
15
|
-
/**
|
|
16
|
-
* Separately trusted, server-created retained-output evidence. Never populate
|
|
17
|
-
* this from a producer-controlled payload field.
|
|
18
|
-
*/
|
|
19
|
-
fullEvidence?: unknown;
|
|
20
|
-
/** Exact runtime credential provenance to remove from keys and values. */
|
|
21
|
-
knownSecrets?: readonly SecretForRedaction[];
|
|
22
|
-
};
|
|
23
|
-
export declare function sanitizeEventPayload<T>(payload: T, options?: SanitizeEventPayloadOptions): T;
|
|
24
|
-
/**
|
|
25
|
-
* Make model-facing conversation data safe for Postgres and secret-redacted.
|
|
26
|
-
*
|
|
27
|
-
* Conversation history is the replay source for the model, so this boundary
|
|
28
|
-
* retains protocol structure and non-sensitive diagnostics while removing
|
|
29
|
-
* credential-bearing fields and text before durable storage. It also performs
|
|
30
|
-
* the database-safety repair (NUL removal and UTF-16 repair).
|
|
31
|
-
*/
|
|
32
|
-
export declare function sanitizeModelPayload<T>(payload: T, knownSecrets?: readonly SecretForRedaction[]): T;
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
boundSessionEventPayload,
|
|
3
|
-
isCredentialHeaderName,
|
|
4
|
-
isSensitiveFieldName,
|
|
5
|
-
redactSensitiveKey,
|
|
6
|
-
redactSensitiveText,
|
|
7
|
-
type SecretForRedaction,
|
|
8
|
-
} from "@opengeni/contracts";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Last line of defense against a session event crashing a whole turn.
|
|
12
|
-
*
|
|
13
|
-
* Postgres `text`/`jsonb` cannot store a NUL byte (U+0000) nor lone UTF-16
|
|
14
|
-
* surrogates. Raw exec output routinely carries both -- chrome/crashpad logs,
|
|
15
|
-
* `cat` of a binary, random bytes -- and the worker persists that output verbatim
|
|
16
|
-
* inside `agent.toolCall.output` / `sandbox.command.output` event payloads. When
|
|
17
|
-
* such a payload reaches `INSERT INTO session_events`, the driver rejects it
|
|
18
|
-
* ("Failed query: insert into session_events") and the turn dies.
|
|
19
|
-
*
|
|
20
|
-
* `sanitizeEventPayload` deep-walks any payload value (objects, arrays, nested),
|
|
21
|
-
* repairs every string, redacts sensitive fields, then applies the canonical
|
|
22
|
-
* byte-bounded human/audit preview. Conversation truth uses
|
|
23
|
-
* `sanitizeModelPayload` below and remains a separate representation.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
const REPLACEMENT = "�";
|
|
27
|
-
const REDACTED = "[redacted]";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Strip NUL and repair invalid/lone UTF-16 surrogates in a single string.
|
|
31
|
-
* Returns the input unchanged (same reference) when it is already clean, so the
|
|
32
|
-
* common case allocates nothing.
|
|
33
|
-
*/
|
|
34
|
-
export function sanitizeEventString(value: string): string {
|
|
35
|
-
// Fast path: no NUL and no surrogate code unit at all -> nothing to do.
|
|
36
|
-
// Surrogates live in U+D800..U+DFFF; a quick scan avoids the rebuild cost.
|
|
37
|
-
let needsWork = false;
|
|
38
|
-
for (let i = 0; i < value.length; i++) {
|
|
39
|
-
const code = value.charCodeAt(i);
|
|
40
|
-
if (code === 0x0000 || (code >= 0xd800 && code <= 0xdfff)) {
|
|
41
|
-
needsWork = true;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (!needsWork) {
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let out = "";
|
|
50
|
-
for (let i = 0; i < value.length; i++) {
|
|
51
|
-
const code = value.charCodeAt(i);
|
|
52
|
-
if (code === 0x0000) {
|
|
53
|
-
// Drop NUL entirely.
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
57
|
-
// High surrogate: valid only when immediately followed by a low surrogate.
|
|
58
|
-
const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
|
|
59
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
60
|
-
out += value[i]! + value[i + 1]!;
|
|
61
|
-
i += 1;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
out += REPLACEMENT;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (code >= 0xdc00 && code <= 0xdfff) {
|
|
68
|
-
// Lone low surrogate (a valid pair would have been consumed above).
|
|
69
|
-
out += REPLACEMENT;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
out += value[i]!;
|
|
73
|
-
}
|
|
74
|
-
return out;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Deep-walk a session event payload and sanitize every string value. Mirrors the
|
|
79
|
-
* shape of the worker redaction deep-walk: objects, arrays, and nested
|
|
80
|
-
* combinations are traversed; non-string leaves pass through untouched. Object
|
|
81
|
-
* keys are sanitized too -- they are jsonb-constrained the same as values.
|
|
82
|
-
*/
|
|
83
|
-
export type SanitizeEventPayloadOptions = {
|
|
84
|
-
/**
|
|
85
|
-
* Separately trusted, server-created retained-output evidence. Never populate
|
|
86
|
-
* this from a producer-controlled payload field.
|
|
87
|
-
*/
|
|
88
|
-
fullEvidence?: unknown;
|
|
89
|
-
/** Exact runtime credential provenance to remove from keys and values. */
|
|
90
|
-
knownSecrets?: readonly SecretForRedaction[];
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export function sanitizeEventPayload<T>(payload: T, options: SanitizeEventPayloadOptions = {}): T {
|
|
94
|
-
// Bound first. The preview walker caps depth/container fan-out and replaces
|
|
95
|
-
// inline media before this sanitizer allocates a deep clone. Reversing this
|
|
96
|
-
// order lets a cyclic, deeply nested, or multi-megabyte tool result exhaust
|
|
97
|
-
// the stack/heap before the durable 64 KiB event boundary can protect it.
|
|
98
|
-
const bounded = boundSessionEventPayload(payload, {
|
|
99
|
-
fullEvidence: options.fullEvidence,
|
|
100
|
-
});
|
|
101
|
-
return sanitizeEventPayloadDeep(
|
|
102
|
-
bounded === payload ? removeProducerTruncationMetadata(bounded) : bounded,
|
|
103
|
-
options.knownSecrets ?? [],
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* `truncation` is reserved durable-boundary metadata. An ordinary payload that
|
|
109
|
-
* already fits the envelope otherwise returns by reference, so remove a
|
|
110
|
-
* producer-supplied value before persistence rather than allowing it to forge
|
|
111
|
-
* byte accounting or an available retained-artifact receipt. A payload changed
|
|
112
|
-
* by `boundSessionEventPayload` already carries freshly computed metadata and
|
|
113
|
-
* never reaches this helper.
|
|
114
|
-
*/
|
|
115
|
-
function removeProducerTruncationMetadata<T>(payload: T): T {
|
|
116
|
-
if (!isPlainObject(payload)) return payload;
|
|
117
|
-
const descriptor = Object.getOwnPropertyDescriptor(payload, "truncation");
|
|
118
|
-
if (!descriptor?.enumerable) return payload;
|
|
119
|
-
const cleaned = { ...payload };
|
|
120
|
-
delete cleaned.truncation;
|
|
121
|
-
return cleaned as T;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function sanitizeEventPayloadDeep<T>(
|
|
125
|
-
payload: T,
|
|
126
|
-
knownSecrets: readonly SecretForRedaction[] = [],
|
|
127
|
-
): T {
|
|
128
|
-
if (typeof payload === "string") {
|
|
129
|
-
return sanitizeEventString(redactSensitiveText(payload, knownSecrets)) as unknown as T;
|
|
130
|
-
}
|
|
131
|
-
if (Array.isArray(payload)) {
|
|
132
|
-
return payload.map((item) => sanitizeEventPayloadDeep(item, knownSecrets)) as unknown as T;
|
|
133
|
-
}
|
|
134
|
-
if (payload instanceof Date) {
|
|
135
|
-
return safeDateIso(payload) as unknown as T;
|
|
136
|
-
}
|
|
137
|
-
if (payload && typeof payload === "object") {
|
|
138
|
-
const usedKeys = new Set<string>();
|
|
139
|
-
const entries = Object.entries(payload as Record<string, unknown>).map(([key, value]) => {
|
|
140
|
-
const safeKey = nextUniqueKey(
|
|
141
|
-
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
142
|
-
usedKeys,
|
|
143
|
-
);
|
|
144
|
-
return [safeKey, sanitizeSensitiveEventField(key, value, knownSecrets)] as const;
|
|
145
|
-
});
|
|
146
|
-
return Object.fromEntries(entries) as unknown as T;
|
|
147
|
-
}
|
|
148
|
-
return payload;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Make model-facing conversation data safe for Postgres and secret-redacted.
|
|
153
|
-
*
|
|
154
|
-
* Conversation history is the replay source for the model, so this boundary
|
|
155
|
-
* retains protocol structure and non-sensitive diagnostics while removing
|
|
156
|
-
* credential-bearing fields and text before durable storage. It also performs
|
|
157
|
-
* the database-safety repair (NUL removal and UTF-16 repair).
|
|
158
|
-
*/
|
|
159
|
-
export function sanitizeModelPayload<T>(
|
|
160
|
-
payload: T,
|
|
161
|
-
knownSecrets: readonly SecretForRedaction[] = [],
|
|
162
|
-
): T {
|
|
163
|
-
return sanitizeModelPayloadDeep(payload, new WeakSet<object>(), 0, knownSecrets);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const MODEL_PAYLOAD_SANITIZE_MAX_DEPTH = 64;
|
|
167
|
-
const MODEL_PAYLOAD_CYCLE_MARKER = "[OpenGeni omitted cyclic model payload]";
|
|
168
|
-
const MODEL_PAYLOAD_DEPTH_MARKER = "[OpenGeni omitted model payload beyond database-safety depth]";
|
|
169
|
-
|
|
170
|
-
function sanitizeModelPayloadDeep<T>(
|
|
171
|
-
payload: T,
|
|
172
|
-
seen: WeakSet<object>,
|
|
173
|
-
depth: number,
|
|
174
|
-
knownSecrets: readonly SecretForRedaction[] = [],
|
|
175
|
-
): T {
|
|
176
|
-
if (typeof payload === "string") {
|
|
177
|
-
return sanitizeEventString(redactSensitiveText(payload, knownSecrets)) as unknown as T;
|
|
178
|
-
}
|
|
179
|
-
if (!payload || typeof payload !== "object") return payload;
|
|
180
|
-
if (payload instanceof Date) {
|
|
181
|
-
return safeDateIso(payload) as unknown as T;
|
|
182
|
-
}
|
|
183
|
-
if (depth >= MODEL_PAYLOAD_SANITIZE_MAX_DEPTH) {
|
|
184
|
-
return MODEL_PAYLOAD_DEPTH_MARKER as unknown as T;
|
|
185
|
-
}
|
|
186
|
-
if (seen.has(payload)) return MODEL_PAYLOAD_CYCLE_MARKER as unknown as T;
|
|
187
|
-
seen.add(payload);
|
|
188
|
-
try {
|
|
189
|
-
if (Array.isArray(payload)) {
|
|
190
|
-
return payload.map((item) =>
|
|
191
|
-
sanitizeModelPayloadDeep(item, seen, depth + 1, knownSecrets),
|
|
192
|
-
) as unknown as T;
|
|
193
|
-
}
|
|
194
|
-
const usedKeys = new Set<string>();
|
|
195
|
-
return Object.fromEntries(
|
|
196
|
-
Object.entries(payload as Record<string, unknown>).map(([key, value]) => {
|
|
197
|
-
const safeKey = nextUniqueKey(
|
|
198
|
-
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
199
|
-
usedKeys,
|
|
200
|
-
);
|
|
201
|
-
if (isSensitiveFieldName(key)) {
|
|
202
|
-
return [safeKey, REDACTED] as const;
|
|
203
|
-
}
|
|
204
|
-
if (normalizeFieldName(key) === "headers") {
|
|
205
|
-
return [safeKey, sanitizeModelHeaders(value, seen, depth + 1, knownSecrets)] as const;
|
|
206
|
-
}
|
|
207
|
-
return [safeKey, sanitizeModelPayloadDeep(value, seen, depth + 1, knownSecrets)] as const;
|
|
208
|
-
}),
|
|
209
|
-
) as unknown as T;
|
|
210
|
-
} finally {
|
|
211
|
-
seen.delete(payload);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function safeDateIso(value: Date): string | null {
|
|
216
|
-
try {
|
|
217
|
-
const epoch = Date.prototype.getTime.call(value);
|
|
218
|
-
return Number.isFinite(epoch) ? Date.prototype.toISOString.call(value) : null;
|
|
219
|
-
} catch {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function sanitizeSensitiveEventField(
|
|
225
|
-
key: string,
|
|
226
|
-
value: unknown,
|
|
227
|
-
knownSecrets: readonly SecretForRedaction[],
|
|
228
|
-
): unknown {
|
|
229
|
-
if (key === "mcpServers") {
|
|
230
|
-
return sanitizeSessionMcpServerList(value, knownSecrets);
|
|
231
|
-
}
|
|
232
|
-
if (key === "mcpCredentialUpdates") {
|
|
233
|
-
return sanitizeMcpCredentialUpdateList(value, knownSecrets);
|
|
234
|
-
}
|
|
235
|
-
if (normalizeFieldName(key) === "headers") {
|
|
236
|
-
return sanitizeEventHeaders(value, knownSecrets);
|
|
237
|
-
}
|
|
238
|
-
if (isSensitiveFieldName(key)) {
|
|
239
|
-
return REDACTED;
|
|
240
|
-
}
|
|
241
|
-
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function sanitizeEventHeaders(
|
|
245
|
-
value: unknown,
|
|
246
|
-
knownSecrets: readonly SecretForRedaction[],
|
|
247
|
-
): unknown {
|
|
248
|
-
if (!isPlainObject(value)) {
|
|
249
|
-
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
250
|
-
}
|
|
251
|
-
const usedKeys = new Set<string>();
|
|
252
|
-
return Object.fromEntries(
|
|
253
|
-
Object.entries(value).map(([key, child]) => {
|
|
254
|
-
const safeKey = nextUniqueKey(
|
|
255
|
-
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
256
|
-
usedKeys,
|
|
257
|
-
);
|
|
258
|
-
return [
|
|
259
|
-
safeKey,
|
|
260
|
-
isCredentialHeaderName(key) ? REDACTED : sanitizeEventPayloadDeep(child, knownSecrets),
|
|
261
|
-
];
|
|
262
|
-
}),
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function sanitizeModelHeaders(
|
|
267
|
-
value: unknown,
|
|
268
|
-
seen: WeakSet<object>,
|
|
269
|
-
depth: number,
|
|
270
|
-
knownSecrets: readonly SecretForRedaction[],
|
|
271
|
-
): unknown {
|
|
272
|
-
if (!isPlainObject(value)) {
|
|
273
|
-
return sanitizeModelPayloadDeep(value, seen, depth, knownSecrets);
|
|
274
|
-
}
|
|
275
|
-
if (depth >= MODEL_PAYLOAD_SANITIZE_MAX_DEPTH) {
|
|
276
|
-
return MODEL_PAYLOAD_DEPTH_MARKER;
|
|
277
|
-
}
|
|
278
|
-
if (seen.has(value)) return MODEL_PAYLOAD_CYCLE_MARKER;
|
|
279
|
-
seen.add(value);
|
|
280
|
-
try {
|
|
281
|
-
const usedKeys = new Set<string>();
|
|
282
|
-
return Object.fromEntries(
|
|
283
|
-
Object.entries(value).map(([key, child]) => {
|
|
284
|
-
const safeKey = nextUniqueKey(
|
|
285
|
-
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
286
|
-
usedKeys,
|
|
287
|
-
);
|
|
288
|
-
return [
|
|
289
|
-
safeKey,
|
|
290
|
-
isCredentialHeaderName(key)
|
|
291
|
-
? REDACTED
|
|
292
|
-
: sanitizeModelPayloadDeep(child, seen, depth + 1, knownSecrets),
|
|
293
|
-
];
|
|
294
|
-
}),
|
|
295
|
-
);
|
|
296
|
-
} finally {
|
|
297
|
-
seen.delete(value);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function sanitizeSessionMcpServerList(
|
|
302
|
-
value: unknown,
|
|
303
|
-
knownSecrets: readonly SecretForRedaction[],
|
|
304
|
-
): unknown {
|
|
305
|
-
if (!Array.isArray(value)) {
|
|
306
|
-
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
307
|
-
}
|
|
308
|
-
return value.map((item) => {
|
|
309
|
-
if (!isPlainObject(item)) {
|
|
310
|
-
return sanitizeEventPayloadDeep(item, knownSecrets);
|
|
311
|
-
}
|
|
312
|
-
const { headers, headersEncrypted, ...rest } = item;
|
|
313
|
-
const cleaned = sanitizeEventPayloadDeep(rest, knownSecrets) as Record<string, unknown>;
|
|
314
|
-
const headerNames =
|
|
315
|
-
safeHeaderNames(headers, knownSecrets) ?? safeHeaderNames(headersEncrypted, knownSecrets);
|
|
316
|
-
if (headerNames) {
|
|
317
|
-
cleaned.headerNames = headerNames;
|
|
318
|
-
}
|
|
319
|
-
return cleaned;
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function sanitizeMcpCredentialUpdateList(
|
|
324
|
-
value: unknown,
|
|
325
|
-
knownSecrets: readonly SecretForRedaction[],
|
|
326
|
-
): unknown {
|
|
327
|
-
if (!Array.isArray(value)) {
|
|
328
|
-
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
329
|
-
}
|
|
330
|
-
return value.map((item) => {
|
|
331
|
-
if (!isPlainObject(item)) {
|
|
332
|
-
return sanitizeEventPayloadDeep(item, knownSecrets);
|
|
333
|
-
}
|
|
334
|
-
const { headers, headersEncrypted, ...rest } = item;
|
|
335
|
-
const cleaned = sanitizeEventPayloadDeep(rest, knownSecrets) as Record<string, unknown>;
|
|
336
|
-
const headerNames =
|
|
337
|
-
safeHeaderNames(headers, knownSecrets) ?? safeHeaderNames(headersEncrypted, knownSecrets);
|
|
338
|
-
if (headerNames) {
|
|
339
|
-
cleaned.headerNames = headerNames;
|
|
340
|
-
}
|
|
341
|
-
return cleaned;
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function safeHeaderNames(
|
|
346
|
-
value: unknown,
|
|
347
|
-
knownSecrets: readonly SecretForRedaction[],
|
|
348
|
-
): string[] | null {
|
|
349
|
-
if (!isPlainObject(value)) {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
const usedKeys = new Set<string>();
|
|
353
|
-
return Object.keys(value)
|
|
354
|
-
.map((key) =>
|
|
355
|
-
nextUniqueKey(sanitizeEventString(redactSensitiveKey(key, knownSecrets)), usedKeys),
|
|
356
|
-
)
|
|
357
|
-
.sort();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function nextUniqueKey(base: string, usedKeys: Set<string>): string {
|
|
361
|
-
let candidate = base;
|
|
362
|
-
let suffix = 2;
|
|
363
|
-
while (usedKeys.has(candidate)) {
|
|
364
|
-
candidate = `${base}#${suffix}`;
|
|
365
|
-
suffix += 1;
|
|
366
|
-
}
|
|
367
|
-
usedKeys.add(candidate);
|
|
368
|
-
return candidate;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
372
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function normalizeFieldName(key: string): string {
|
|
376
|
-
return key.toLowerCase().replace(/[-_]/g, "");
|
|
377
|
-
}
|