@apifuse/provider-sdk 2.2.0-beta.59 → 2.2.0-beta.60
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/AUTHORING.md +16 -0
- package/CHANGELOG.md +4 -0
- package/bin/apifuse-dev.ts +2 -1
- package/bin/apifuse-perf.ts +41 -4
- package/bin/apifuse-record.ts +3 -2
- package/dist/config/loader.js +13 -12
- package/dist/engine.js +3 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/lint.js +36 -15
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/browser.js +2 -1
- package/dist/runtime/cache.js +3 -2
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +11 -0
- package/dist/runtime/diagnostic-env.js +35 -0
- package/dist/runtime/diagnostic-redactor.d.ts +49 -0
- package/dist/runtime/diagnostic-redactor.js +569 -0
- package/dist/runtime/egress-lease.d.ts +2 -0
- package/dist/runtime/egress-lease.js +4 -1
- package/dist/runtime/env.js +2 -1
- package/dist/runtime/instrumentation.js +3 -0
- package/dist/runtime/ocr.js +2 -1
- package/dist/runtime/otlp.js +5 -4
- package/dist/runtime/proxy-nodemaven.js +3 -2
- package/dist/runtime/request-options.d.ts +7 -0
- package/dist/runtime/request-options.js +62 -22
- package/dist/runtime/resolver-vendors/browser.js +0 -1
- package/dist/runtime/resolver.js +3 -2
- package/dist/runtime/stt.js +3 -2
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +115 -20
- package/dist/schema.d.ts +11 -0
- package/dist/schema.js +56 -2
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +131 -0
- package/dist/server/serve-implementation.js +219 -78
- package/dist/server/trace-output.d.ts +2 -1
- package/dist/server/trace-output.js +46 -14
- package/dist/trace-sanitization.d.ts +4 -3
- package/dist/trace-sanitization.js +45 -22
- package/package.json +2 -1
- package/src/config/loader.ts +13 -12
- package/src/engine.ts +3 -2
- package/src/index.ts +2 -0
- package/src/lint.ts +42 -16
- package/src/provider.ts +1 -0
- package/src/runtime/browser.ts +2 -1
- package/src/runtime/cache.ts +3 -2
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +55 -0
- package/src/runtime/diagnostic-redactor.ts +662 -0
- package/src/runtime/egress-lease.ts +6 -1
- package/src/runtime/env.ts +2 -1
- package/src/runtime/instrumentation.ts +3 -0
- package/src/runtime/ocr.ts +2 -1
- package/src/runtime/otlp.ts +5 -4
- package/src/runtime/proxy-nodemaven.ts +3 -2
- package/src/runtime/request-options.ts +76 -28
- package/src/runtime/resolver-vendors/browser.ts +0 -1
- package/src/runtime/resolver.ts +3 -2
- package/src/runtime/stt.ts +3 -2
- package/src/runtime/trace.ts +168 -26
- package/src/schema.ts +63 -2
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/sensitive-values.ts +200 -0
- package/src/server/serve-implementation.ts +301 -96
- package/src/server/trace-output.ts +77 -17
- package/src/trace-sanitization.ts +70 -23
package/src/schema.ts
CHANGED
|
@@ -105,6 +105,8 @@ export type SensitiveFieldKind =
|
|
|
105
105
|
export interface SensitiveFieldOptions {
|
|
106
106
|
/**
|
|
107
107
|
* Mark this schema as sensitive. Defaults to true for the helper presets.
|
|
108
|
+
* An explicit `false` emits `x-apifuse-sensitive: false` (reviewed public
|
|
109
|
+
* data, never redacted) and drops any sensitivity kind.
|
|
108
110
|
*/
|
|
109
111
|
sensitive?: boolean;
|
|
110
112
|
/**
|
|
@@ -208,10 +210,27 @@ export function field<TSchema extends ZodType>(
|
|
|
208
210
|
? schema.describe(options.description)
|
|
209
211
|
: schema;
|
|
210
212
|
const metadata = described.meta() ?? {};
|
|
213
|
+
const isSensitive = options.sensitive ?? true;
|
|
214
|
+
// A public declaration only rewrites the meta of the node it receives;
|
|
215
|
+
// z.toJSONSchema folds that over the inner leaf, so `false` on a wrapper
|
|
216
|
+
// around a sensitive() leaf would publish `false` next to the leaf's kind
|
|
217
|
+
// while collectSensitivePaths() still redacts. Refuse the contradiction.
|
|
218
|
+
if (!isSensitive && wrapsSensitiveDeclaration(described)) {
|
|
219
|
+
throw new TypeError(
|
|
220
|
+
"publicField()/field({ sensitive: false }) wraps a sensitive() declaration; apply it to the leaf, before .optional(), .nullable(), .default(), .pipe(), z.lazy() or similar wrappers.",
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
// Zod merges meta with the parent chain, so an inherited kind can only be
|
|
224
|
+
// cleared by writing undefined over it (dropped from JSON Schema output).
|
|
225
|
+
const kind = isSensitive
|
|
226
|
+
? (options.kind ?? Reflect.get(metadata, APIFUSE_SENSITIVE_KIND_META_KEY))
|
|
227
|
+
: undefined;
|
|
211
228
|
return described.meta({
|
|
212
229
|
...metadata,
|
|
213
|
-
|
|
214
|
-
...(
|
|
230
|
+
[APIFUSE_SENSITIVE_META_KEY]: isSensitive,
|
|
231
|
+
...(kind !== undefined || APIFUSE_SENSITIVE_KIND_META_KEY in metadata
|
|
232
|
+
? { [APIFUSE_SENSITIVE_KIND_META_KEY]: kind }
|
|
233
|
+
: {}),
|
|
215
234
|
});
|
|
216
235
|
}
|
|
217
236
|
|
|
@@ -222,6 +241,18 @@ export function sensitive<TSchema extends ZodType>(
|
|
|
222
241
|
return field(schema, { sensitive: true, kind });
|
|
223
242
|
}
|
|
224
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Declare a field as reviewed public data (business, facility, product, or
|
|
246
|
+
* place names and contacts) so a key-based privacy classifier hit counts as
|
|
247
|
+
* declared instead of undeclared. Emits `x-apifuse-sensitive: false` on the
|
|
248
|
+
* JSON Schema leaf; nothing is redacted. Use `sensitive()` for anything that
|
|
249
|
+
* identifies a person. Apply it to the leaf, before `.optional()` or
|
|
250
|
+
* `.nullable()`; wrapping a `sensitive()` declaration throws at definition time.
|
|
251
|
+
*/
|
|
252
|
+
export function publicField<TSchema extends ZodType>(schema: TSchema): TSchema {
|
|
253
|
+
return field(schema, { sensitive: false });
|
|
254
|
+
}
|
|
255
|
+
|
|
225
256
|
function sensitiveString(
|
|
226
257
|
kind: SensitiveFieldKind,
|
|
227
258
|
description: string,
|
|
@@ -382,6 +413,36 @@ function readZodMetadata(schema: unknown): object | undefined {
|
|
|
382
413
|
return metadata && typeof metadata === "object" ? metadata : undefined;
|
|
383
414
|
}
|
|
384
415
|
|
|
416
|
+
// Same-path wrappers (optional, nullable, default, catch, readonly, pipe,
|
|
417
|
+
// lazy, ...) share the JSON Schema leaf with the schema they wrap.
|
|
418
|
+
function wrapsSensitiveDeclaration(schema: unknown, seen = new Set<unknown>()): boolean {
|
|
419
|
+
const def = readZodDef(schema);
|
|
420
|
+
if (!def || seen.has(schema)) return false;
|
|
421
|
+
seen.add(schema);
|
|
422
|
+
return sameLeafChildren(def).some(
|
|
423
|
+
(child) => isSensitiveSchema(child) || wrapsSensitiveDeclaration(child, seen),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function sameLeafChildren(def: object): unknown[] {
|
|
428
|
+
switch (Reflect.get(def, "type")) {
|
|
429
|
+
case "pipe":
|
|
430
|
+
return [Reflect.get(def, "in"), Reflect.get(def, "out")];
|
|
431
|
+
case "lazy": {
|
|
432
|
+
// A forward-reference getter can throw before its target is
|
|
433
|
+
// initialised; treat that as no declaration rather than failing.
|
|
434
|
+
const getter = Reflect.get(def, "getter");
|
|
435
|
+
try {
|
|
436
|
+
return typeof getter === "function" ? [getter()] : [];
|
|
437
|
+
} catch {
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
default:
|
|
442
|
+
return [Reflect.get(def, "innerType")];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
385
446
|
function readZodDef(schema: unknown): object | undefined {
|
|
386
447
|
if (!schema || typeof schema !== "object") return undefined;
|
|
387
448
|
const def = Reflect.get(schema, "def") ?? Reflect.get(schema, "_def");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ProviderDefinition } from "../types.js";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./
|
|
3
|
+
collectDiagnosticSensitiveValues,
|
|
4
|
+
SELF_TEST_SENSITIVE_SOURCE_KINDS,
|
|
5
|
+
} from "./sensitive-values.js";
|
|
6
6
|
|
|
7
7
|
export const SELF_TEST_REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
8
8
|
|
|
@@ -31,39 +31,12 @@ export function collectSelfTestSensitiveValues(
|
|
|
31
31
|
credentialInputs?: Readonly<Record<string, string>>;
|
|
32
32
|
} = {},
|
|
33
33
|
): string[] {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
envNames.add(secret.name);
|
|
41
|
-
}
|
|
42
|
-
const healthProbe = provider.healthProbe ?? provider.healthMonitor;
|
|
43
|
-
for (const name of healthProbe?.requiredSecrets ?? []) {
|
|
44
|
-
envNames.add(name);
|
|
45
|
-
}
|
|
46
|
-
for (const name of Object.values(healthProbe?.credentialInputs ?? {})) {
|
|
47
|
-
envNames.add(name);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const values = new Set<string>();
|
|
51
|
-
for (const name of envNames) {
|
|
52
|
-
const value = env[name];
|
|
53
|
-
if (typeof value === "string" && value.length >= MIN_SENSITIVE_VALUE_LENGTH) {
|
|
54
|
-
values.add(value);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
for (const value of Object.values(options.credentialInputs ?? {})) {
|
|
58
|
-
if (value.length >= MIN_SENSITIVE_VALUE_LENGTH) {
|
|
59
|
-
values.add(value);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return [...values];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function escapeRegExp(value: string): string {
|
|
66
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34
|
+
return collectDiagnosticSensitiveValues(provider, {
|
|
35
|
+
...options,
|
|
36
|
+
sourceKinds: SELF_TEST_SENSITIVE_SOURCE_KINDS,
|
|
37
|
+
minimumLength: MIN_SENSITIVE_VALUE_LENGTH,
|
|
38
|
+
includeResolvedEnvVariants: false,
|
|
39
|
+
});
|
|
67
40
|
}
|
|
68
41
|
|
|
69
42
|
/**
|
|
@@ -79,10 +52,7 @@ export function redactSelfTestText(
|
|
|
79
52
|
let redacted = text;
|
|
80
53
|
for (const value of sensitiveValues) {
|
|
81
54
|
if (value.length < MIN_SENSITIVE_VALUE_LENGTH) continue;
|
|
82
|
-
redacted = redacted.
|
|
83
|
-
new RegExp(escapeRegExp(value), "g"),
|
|
84
|
-
SELF_TEST_REDACTED_PLACEHOLDER,
|
|
85
|
-
);
|
|
55
|
+
redacted = redacted.replaceAll(value, SELF_TEST_REDACTED_PLACEHOLDER);
|
|
86
56
|
}
|
|
87
57
|
for (const pattern of HEADER_SHAPED_SECRETS) {
|
|
88
58
|
redacted = redacted.replace(
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolvedStringVariants,
|
|
3
|
+
urlCredentialComponents,
|
|
4
|
+
otlpHeaderCredentialValues,
|
|
5
|
+
} from "../runtime/diagnostic-env-values.js";
|
|
6
|
+
import {
|
|
7
|
+
PROVIDER_CACHE_REDIS_URL_ENV,
|
|
8
|
+
PROVIDER_STATE_REDIS_URL_ENV,
|
|
9
|
+
REDIS_URL_ENV,
|
|
10
|
+
} from "../config/loader.js";
|
|
11
|
+
import {
|
|
12
|
+
ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
|
|
13
|
+
ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES,
|
|
14
|
+
} from "../engine.js";
|
|
15
|
+
import { APIFUSE__CACHE__KEY_PEPPER_ENV } from "../runtime/cache.js";
|
|
16
|
+
import { PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV } from "../runtime/choice.js";
|
|
17
|
+
import { APIFUSE__ENGINE__CEREMONY_LEASE_KEY } from "../runtime/egress-lease.js";
|
|
18
|
+
import {
|
|
19
|
+
APIFUSE__OCR__API_KEY_ENV,
|
|
20
|
+
APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV,
|
|
21
|
+
} from "../runtime/ocr.js";
|
|
22
|
+
import {
|
|
23
|
+
OTEL_EXPORTER_OTLP_HEADERS,
|
|
24
|
+
OTEL_EXPORTER_OTLP_TRACES_HEADERS,
|
|
25
|
+
OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
26
|
+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
|
|
27
|
+
} from "../runtime/otlp.js";
|
|
28
|
+
import { APIFUSE__CDP_POOL__URL } from "../runtime/resolver-config.js";
|
|
29
|
+
import { APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV } from "../runtime/stt.js";
|
|
30
|
+
import type { ProviderDefinition } from "../types.js";
|
|
31
|
+
import {
|
|
32
|
+
PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV,
|
|
33
|
+
PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV,
|
|
34
|
+
} from "./self-test-token.js";
|
|
35
|
+
|
|
36
|
+
export const SELF_TEST_SENSITIVE_SOURCE_KINDS = [
|
|
37
|
+
"selfTestMasterSecrets",
|
|
38
|
+
"providerSecrets",
|
|
39
|
+
"healthProbeSecrets",
|
|
40
|
+
"requestCredentials",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
/** Explicit source allowlist: no environment-name or payload-shape secret sweep. */
|
|
44
|
+
export const DIAGNOSTIC_SENSITIVE_SOURCE_KINDS = [
|
|
45
|
+
...SELF_TEST_SENSITIVE_SOURCE_KINDS,
|
|
46
|
+
"engineProxyCredentials",
|
|
47
|
+
"engineSolverKeys",
|
|
48
|
+
"engineCeremonyLeaseCredentials",
|
|
49
|
+
"choiceMasterSecret",
|
|
50
|
+
"ocrCredentials",
|
|
51
|
+
"sttCredentials",
|
|
52
|
+
"cdpCredentials",
|
|
53
|
+
"statefulForwardingSecret",
|
|
54
|
+
"redisCredentials",
|
|
55
|
+
"otlpHeaders",
|
|
56
|
+
"cacheKeyPepper",
|
|
57
|
+
"sensitiveParams",
|
|
58
|
+
] as const;
|
|
59
|
+
export type DiagnosticSensitiveSourceKind = (typeof DIAGNOSTIC_SENSITIVE_SOURCE_KINDS)[number];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Startup inventory; request-scoped readers additionally register live values on access.
|
|
63
|
+
*/
|
|
64
|
+
export function collectStaticDiagnosticSensitiveValues(
|
|
65
|
+
provider: ProviderDefinition,
|
|
66
|
+
options: {
|
|
67
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
68
|
+
statefulForwardingSecret?: string;
|
|
69
|
+
} = {},
|
|
70
|
+
): string[] {
|
|
71
|
+
return collectDiagnosticSensitiveValues(provider, {
|
|
72
|
+
...options,
|
|
73
|
+
sourceKinds: DIAGNOSTIC_SENSITIVE_SOURCE_KINDS.filter(
|
|
74
|
+
(kind) => kind !== "requestCredentials" && kind !== "sensitiveParams",
|
|
75
|
+
),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Generalised self-test collector, also used to seed and enrich ordinary request scopes. */
|
|
80
|
+
export function collectDiagnosticSensitiveValues(
|
|
81
|
+
provider: ProviderDefinition,
|
|
82
|
+
options: {
|
|
83
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
84
|
+
credentialInputs?: Readonly<Record<string, unknown>>;
|
|
85
|
+
connectionSecrets?: Readonly<Record<string, string | undefined>>;
|
|
86
|
+
sensitiveParams?: readonly string[];
|
|
87
|
+
statefulForwardingSecret?: string;
|
|
88
|
+
sourceKinds?: readonly DiagnosticSensitiveSourceKind[];
|
|
89
|
+
minimumLength?: number;
|
|
90
|
+
/** Preserve the legacy self-test response collector when false. */
|
|
91
|
+
includeResolvedEnvVariants?: boolean;
|
|
92
|
+
} = {},
|
|
93
|
+
): string[] {
|
|
94
|
+
const env = options.env ?? process.env;
|
|
95
|
+
const envValues = (names: readonly string[]) => names.map((name) => env[name]);
|
|
96
|
+
const resolvedEnvValues = (names: readonly string[]) =>
|
|
97
|
+
envValues(names).flatMap((value) =>
|
|
98
|
+
value === undefined
|
|
99
|
+
? []
|
|
100
|
+
: options.includeResolvedEnvVariants === false
|
|
101
|
+
? [value]
|
|
102
|
+
: [value, value.trim()],
|
|
103
|
+
);
|
|
104
|
+
const healthProbe = provider.healthProbe ?? provider.healthMonitor;
|
|
105
|
+
const sources: Record<DiagnosticSensitiveSourceKind, () => readonly unknown[]> = {
|
|
106
|
+
selfTestMasterSecrets: () =>
|
|
107
|
+
resolvedEnvValues([
|
|
108
|
+
PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV,
|
|
109
|
+
PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV,
|
|
110
|
+
]),
|
|
111
|
+
providerSecrets: () => resolvedEnvValues((provider.secrets ?? []).map((secret) => secret.name)),
|
|
112
|
+
healthProbeSecrets: () =>
|
|
113
|
+
resolvedEnvValues([
|
|
114
|
+
...(healthProbe?.requiredSecrets ?? []),
|
|
115
|
+
...Object.values(healthProbe?.credentialInputs ?? {}),
|
|
116
|
+
]),
|
|
117
|
+
requestCredentials: () => [
|
|
118
|
+
...Object.values(options.credentialInputs ?? {}),
|
|
119
|
+
...Object.values(options.connectionSecrets ?? {}),
|
|
120
|
+
],
|
|
121
|
+
engineProxyCredentials: () => resolvedEnvValues(ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES),
|
|
122
|
+
// These are the resolver-config API keys after #251's engine credential projection.
|
|
123
|
+
engineSolverKeys: () => resolvedEnvValues(ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES),
|
|
124
|
+
engineCeremonyLeaseCredentials: () => resolvedEnvValues([APIFUSE__ENGINE__CEREMONY_LEASE_KEY]),
|
|
125
|
+
choiceMasterSecret: () => resolvedEnvValues([PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV]),
|
|
126
|
+
ocrCredentials: () =>
|
|
127
|
+
resolvedEnvValues([APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__OCR__API_KEY_ENV]),
|
|
128
|
+
sttCredentials: () => resolvedEnvValues([APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV]),
|
|
129
|
+
cdpCredentials: () =>
|
|
130
|
+
resolvedStringVariants(env[APIFUSE__CDP_POOL__URL]).flatMap((url) => [
|
|
131
|
+
url,
|
|
132
|
+
...urlCredentialComponents(url, { includePathAndQuery: true }),
|
|
133
|
+
]),
|
|
134
|
+
statefulForwardingSecret: () => resolvedStringVariants(options.statefulForwardingSecret),
|
|
135
|
+
redisCredentials: () =>
|
|
136
|
+
[PROVIDER_CACHE_REDIS_URL_ENV, PROVIDER_STATE_REDIS_URL_ENV, REDIS_URL_ENV]
|
|
137
|
+
.flatMap((name) => resolvedStringVariants(env[name]))
|
|
138
|
+
.flatMap((url) => urlCredentialComponents(url, { includePathAndQuery: false })),
|
|
139
|
+
otlpHeaders: () => [
|
|
140
|
+
...[OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_EXPORTER_OTLP_HEADERS].flatMap((name) =>
|
|
141
|
+
env[name] === undefined ? [] : otlpHeaderCredentialValues(env[name]),
|
|
142
|
+
),
|
|
143
|
+
...[OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT].flatMap((name) =>
|
|
144
|
+
resolvedStringVariants(env[name]).flatMap((url) =>
|
|
145
|
+
urlCredentialComponents(url, { includePathAndQuery: false }),
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
],
|
|
149
|
+
cacheKeyPepper: () => resolvedEnvValues([APIFUSE__CACHE__KEY_PEPPER_ENV]),
|
|
150
|
+
sensitiveParams: () => options.sensitiveParams ?? [],
|
|
151
|
+
};
|
|
152
|
+
const values = new Set<string>();
|
|
153
|
+
for (const kind of options.sourceKinds ?? DIAGNOSTIC_SENSITIVE_SOURCE_KINDS) {
|
|
154
|
+
for (const value of sources[kind]()) {
|
|
155
|
+
if (typeof value === "string" && value.length >= (options.minimumLength ?? 1))
|
|
156
|
+
values.add(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return [...values];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const diagnosticEnvNames = new WeakMap<ProviderDefinition, Set<string>>();
|
|
163
|
+
|
|
164
|
+
/** Derive the live-read allowlist from the same collector as the startup inventory. */
|
|
165
|
+
export function createDiagnosticEnvObserver(
|
|
166
|
+
provider: ProviderDefinition,
|
|
167
|
+
register: (values: readonly string[]) => void,
|
|
168
|
+
has?: (value: string) => boolean,
|
|
169
|
+
): (name: string, value: string) => void {
|
|
170
|
+
let names = diagnosticEnvNames.get(provider);
|
|
171
|
+
if (!names) {
|
|
172
|
+
names = new Set<string>();
|
|
173
|
+
const captured = names;
|
|
174
|
+
collectStaticDiagnosticSensitiveValues(provider, {
|
|
175
|
+
env: new Proxy(
|
|
176
|
+
{},
|
|
177
|
+
{
|
|
178
|
+
get: (_target, key) => {
|
|
179
|
+
if (typeof key === "string") captured.add(key);
|
|
180
|
+
return undefined;
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
),
|
|
184
|
+
});
|
|
185
|
+
diagnosticEnvNames.set(provider, names);
|
|
186
|
+
}
|
|
187
|
+
const seen = new Map<string, string>();
|
|
188
|
+
return (name, value) => {
|
|
189
|
+
if (!names.has(name) || seen.get(name) === value) return;
|
|
190
|
+
if (has?.(value) && has(value.trim())) return;
|
|
191
|
+
// Includes URL userinfo/CDP components and OTLP auth values, using the same rules.
|
|
192
|
+
register([
|
|
193
|
+
...resolvedStringVariants(value),
|
|
194
|
+
...collectStaticDiagnosticSensitiveValues(provider, {
|
|
195
|
+
env: { [name]: value },
|
|
196
|
+
}),
|
|
197
|
+
]);
|
|
198
|
+
seen.set(name, value);
|
|
199
|
+
};
|
|
200
|
+
}
|