@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.
Files changed (72) hide show
  1. package/AUTHORING.md +16 -0
  2. package/CHANGELOG.md +4 -0
  3. package/bin/apifuse-dev.ts +2 -1
  4. package/bin/apifuse-perf.ts +41 -4
  5. package/bin/apifuse-record.ts +3 -2
  6. package/dist/config/loader.js +13 -12
  7. package/dist/engine.js +3 -2
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.js +1 -1
  10. package/dist/lint.js +36 -15
  11. package/dist/provider.d.ts +1 -1
  12. package/dist/provider.js +1 -1
  13. package/dist/runtime/browser.js +2 -1
  14. package/dist/runtime/cache.js +3 -2
  15. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  16. package/dist/runtime/diagnostic-env-values.js +57 -0
  17. package/dist/runtime/diagnostic-env.d.ts +11 -0
  18. package/dist/runtime/diagnostic-env.js +35 -0
  19. package/dist/runtime/diagnostic-redactor.d.ts +49 -0
  20. package/dist/runtime/diagnostic-redactor.js +569 -0
  21. package/dist/runtime/egress-lease.d.ts +2 -0
  22. package/dist/runtime/egress-lease.js +4 -1
  23. package/dist/runtime/env.js +2 -1
  24. package/dist/runtime/instrumentation.js +3 -0
  25. package/dist/runtime/ocr.js +2 -1
  26. package/dist/runtime/otlp.js +5 -4
  27. package/dist/runtime/proxy-nodemaven.js +3 -2
  28. package/dist/runtime/request-options.d.ts +7 -0
  29. package/dist/runtime/request-options.js +62 -22
  30. package/dist/runtime/resolver-vendors/browser.js +0 -1
  31. package/dist/runtime/resolver.js +3 -2
  32. package/dist/runtime/stt.js +3 -2
  33. package/dist/runtime/trace.d.ts +5 -0
  34. package/dist/runtime/trace.js +115 -20
  35. package/dist/schema.d.ts +11 -0
  36. package/dist/schema.js +56 -2
  37. package/dist/server/self-test-redaction.js +8 -33
  38. package/dist/server/sensitive-values.d.ts +26 -0
  39. package/dist/server/sensitive-values.js +131 -0
  40. package/dist/server/serve-implementation.js +219 -78
  41. package/dist/server/trace-output.d.ts +2 -1
  42. package/dist/server/trace-output.js +46 -14
  43. package/dist/trace-sanitization.d.ts +4 -3
  44. package/dist/trace-sanitization.js +45 -22
  45. package/package.json +2 -1
  46. package/src/config/loader.ts +13 -12
  47. package/src/engine.ts +3 -2
  48. package/src/index.ts +2 -0
  49. package/src/lint.ts +42 -16
  50. package/src/provider.ts +1 -0
  51. package/src/runtime/browser.ts +2 -1
  52. package/src/runtime/cache.ts +3 -2
  53. package/src/runtime/diagnostic-env-values.ts +56 -0
  54. package/src/runtime/diagnostic-env.ts +55 -0
  55. package/src/runtime/diagnostic-redactor.ts +662 -0
  56. package/src/runtime/egress-lease.ts +6 -1
  57. package/src/runtime/env.ts +2 -1
  58. package/src/runtime/instrumentation.ts +3 -0
  59. package/src/runtime/ocr.ts +2 -1
  60. package/src/runtime/otlp.ts +5 -4
  61. package/src/runtime/proxy-nodemaven.ts +3 -2
  62. package/src/runtime/request-options.ts +76 -28
  63. package/src/runtime/resolver-vendors/browser.ts +0 -1
  64. package/src/runtime/resolver.ts +3 -2
  65. package/src/runtime/stt.ts +3 -2
  66. package/src/runtime/trace.ts +168 -26
  67. package/src/schema.ts +63 -2
  68. package/src/server/self-test-redaction.ts +10 -40
  69. package/src/server/sensitive-values.ts +200 -0
  70. package/src/server/serve-implementation.ts +301 -96
  71. package/src/server/trace-output.ts +77 -17
  72. package/src/trace-sanitization.ts +70 -23
package/AUTHORING.md CHANGED
@@ -226,6 +226,22 @@ const utcInput = resolveHealthCheckInputDateTokens({ date: "+45d" }, new Date(),
226
226
  Do not re-resolve a health assertion's dates in UTC when its case input used the
227
227
  default KST calendar.
228
228
 
229
+ ### Sensitive vs public fields
230
+
231
+ The platform fixture-privacy ratchet flags name-, phone-, and address-shaped keys
232
+ unless the schema at that path declares them one way or the other. Declare every hit:
233
+
234
+ - `sensitive(schema, kind)` / `fields.*()` — anything about a person: user, profile,
235
+ passenger, driver, or member names, emails, member ids, personal phone numbers.
236
+ Emits `x-apifuse-sensitive: true`; the value is redacted in published artifacts.
237
+ - `publicField(schema)` — reviewed public data: business, shop, product, artist, or
238
+ facility names, hospital/hotel/station phones, timezone names. Emits
239
+ `x-apifuse-sensitive: false`; nothing is redacted. It is a review verdict, not a mute.
240
+ Credential-shaped keys (token, password, apiKey, …) cannot be declared public.
241
+
242
+ Apply either helper to the leaf, before `.optional()`/`.nullable()`/`.default()`;
243
+ `publicField()` over a wrapper that hides a `sensitive()` leaf throws at definition time.
244
+
229
245
  ### Real-handler E2E in standard tests
230
246
 
231
247
  `runStandardTests(provider)` validates declarations and fixtures but reports a
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.60
4
+
5
+ - Release candidate for main commit 5f417c6282b3a4d92bf31c8258ba913b5e53dc8b.
6
+
3
7
  ## 2.2.0-beta.59
4
8
 
5
9
  - Release candidate for main commit 55ae556fcf33e28a228c2d54ce76e7afa41e5dc7.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ import { readDiagnosticEnv } from "../src/runtime/diagnostic-env.js";
2
3
 
3
4
  import { existsSync } from "node:fs";
4
5
  import { dirname, relative, resolve } from "node:path";
@@ -87,7 +88,7 @@ export function createProviderContext(provider: ProviderDefinition): {
87
88
  process.env,
88
89
  provider.secrets?.map((secret) => secret.name) ?? [],
89
90
  );
90
- const providerEnv = { get: (key: string) => providerEnvironment[key] };
91
+ const providerEnv = { get: (key: string) => readDiagnosticEnv(key, providerEnvironment) };
91
92
  const engineEnv = createEnvContext([PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV]);
92
93
  const engineCredentials = readEngineProxyCredentials();
93
94
  const credential = createCredentialContext();
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @ts-nocheck
3
+ import { readDiagnosticEnv } from "../src/runtime/diagnostic-env.js";
3
4
 
4
5
  import { existsSync } from "node:fs";
5
6
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -25,11 +26,20 @@ import {
25
26
  type StealthResponse,
26
27
  wrapWithInstrumentation,
27
28
  } from "../src/index.js";
29
+ import {
30
+ bindDiagnosticSensitiveRegistry,
31
+ type CompiledDiagnosticSensitiveValues,
32
+ compileProcessDiagnosticSensitiveValues,
33
+ createDiagnosticRedactor,
34
+ redactDiagnosticText,
35
+ } from "../src/runtime/diagnostic-redactor.js";
28
36
  import { computeStats, groupSpansByName, type PerfStats } from "../src/runtime/perf.js";
29
37
  import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
30
38
  import { createStealthClient } from "../src/runtime/stealth.js";
31
39
  import { createTraceContext, resolveTraceContextOptions } from "../src/runtime/trace.js";
32
40
  import { renderWaterfall } from "../src/runtime/waterfall.js";
41
+ import { collectStaticDiagnosticSensitiveValues } from "../src/server/sensitive-values.js";
42
+ import { sanitizeSpanForOutput } from "../src/trace-sanitization.js";
33
43
  import type { BrowserClient } from "../src/types.js";
34
44
 
35
45
  type CliArgs = {
@@ -100,6 +110,9 @@ export async function main() {
100
110
  const providerDirectory = resolve(process.cwd(), args.providerPath);
101
111
  const providerEntry = resolveProviderEntry(providerDirectory);
102
112
  const provider = await loadProvider(providerEntry);
113
+ const staticSensitiveValues = compileProcessDiagnosticSensitiveValues(
114
+ collectStaticDiagnosticSensitiveValues(provider),
115
+ );
103
116
  const providerId = basename(providerDirectory);
104
117
  const config = await loadApiFuseConfig(process.cwd());
105
118
  const operation = getOperation(provider, args.operation);
@@ -119,6 +132,7 @@ export async function main() {
119
132
  operationName: args.operation,
120
133
  outputSchema,
121
134
  proxyEnabled: false,
135
+ staticSensitiveValues,
122
136
  });
123
137
 
124
138
  let proxySuite: ProfileSuite | undefined;
@@ -135,6 +149,7 @@ export async function main() {
135
149
  operationName: args.operation,
136
150
  outputSchema,
137
151
  proxyEnabled: true,
152
+ staticSensitiveValues,
138
153
  });
139
154
  }
140
155
 
@@ -170,8 +185,8 @@ export async function main() {
170
185
  runs: args.runs,
171
186
  warmup: args.warmup,
172
187
  concurrency: args.concurrency,
173
- direct: directSuite,
174
- proxy: proxySuite,
188
+ direct: sanitizeProfileSuiteForExport(directSuite),
189
+ proxy: proxySuite ? sanitizeProfileSuiteForExport(proxySuite) : undefined,
175
190
  flamePath,
176
191
  });
177
192
  console.log(`Exported JSON: ${resolve(process.cwd(), args.exportPath)}`);
@@ -480,6 +495,7 @@ async function runProfileSuite(options: {
480
495
  operationName: string;
481
496
  outputSchema: { parse(input: unknown): unknown };
482
497
  proxyEnabled: boolean;
498
+ staticSensitiveValues: CompiledDiagnosticSensitiveValues;
483
499
  }): Promise<ProfileSuite> {
484
500
  for (let index = 0; index < options.args.warmup; index += 1) {
485
501
  await profileRun({
@@ -492,6 +508,7 @@ async function runProfileSuite(options: {
492
508
  operationName: options.operationName,
493
509
  outputSchema: options.outputSchema,
494
510
  proxyEnabled: options.proxyEnabled,
511
+ staticSensitiveValues: options.staticSensitiveValues,
495
512
  });
496
513
  }
497
514
 
@@ -509,6 +526,7 @@ async function runProfileSuite(options: {
509
526
  operationName: options.operationName,
510
527
  outputSchema: options.outputSchema,
511
528
  proxyEnabled: options.proxyEnabled,
529
+ staticSensitiveValues: options.staticSensitiveValues,
512
530
  }),
513
531
  );
514
532
  runs.push(...(await Promise.all(batch)));
@@ -545,6 +563,7 @@ async function profileRun(options: {
545
563
  operationName: string;
546
564
  outputSchema: { parse(input: unknown): unknown };
547
565
  proxyEnabled: boolean;
566
+ staticSensitiveValues: CompiledDiagnosticSensitiveValues;
548
567
  }): Promise<RunResult> {
549
568
  try {
550
569
  return await executeProfileRun({ ...options, forceFixtureReplay: false });
@@ -576,9 +595,16 @@ async function executeProfileRun(options: {
576
595
  operationName: string;
577
596
  outputSchema: { parse(input: unknown): unknown };
578
597
  proxyEnabled: boolean;
598
+ staticSensitiveValues: CompiledDiagnosticSensitiveValues;
579
599
  }): Promise<RunResult> {
580
600
  const _operation = getOperation(options.provider, options.operationName);
581
- const traceContext = createTraceContext(resolveTraceContextOptions(options.config.trace));
601
+ const sensitiveRegistry = createDiagnosticRedactor([], options.staticSensitiveValues);
602
+ const redact = (text: string) => redactDiagnosticText(text, sensitiveRegistry.redact);
603
+ const traceContext = createTraceContext({
604
+ ...resolveTraceContextOptions(options.config.trace),
605
+ redact,
606
+ });
607
+ bindDiagnosticSensitiveRegistry(traceContext, sensitiveRegistry);
582
608
  const baseContext = createBaseContext({
583
609
  config: options.config,
584
610
  provider: options.provider,
@@ -650,7 +676,7 @@ function createBaseContext(options: {
650
676
  });
651
677
 
652
678
  const env = {
653
- get: (key: string) => process.env[key],
679
+ get: (key: string) => readDiagnosticEnv(key),
654
680
  };
655
681
  const credential = {
656
682
  mode: "none" as const,
@@ -925,6 +951,17 @@ function selectRepresentativeRun(runs: RunResult[]): RunResult {
925
951
  return first;
926
952
  }
927
953
 
954
+ function sanitizeProfileSuiteForExport(suite: ProfileSuite): ProfileSuite {
955
+ // The perf CLI has no server request scope; retain the shared output policy on disk.
956
+ return {
957
+ ...suite,
958
+ runs: suite.runs.map((run) => ({
959
+ ...run,
960
+ spans: run.spans.map((span) => sanitizeSpanForOutput(span)),
961
+ })),
962
+ };
963
+ }
964
+
928
965
  async function writeExport(filePath: string, payload: unknown): Promise<void> {
929
966
  const absolutePath = resolve(process.cwd(), filePath);
930
967
  await mkdir(dirname(absolutePath), { recursive: true });
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ import { readDiagnosticEnv } from "../src/runtime/diagnostic-env.js";
2
3
 
3
4
  import { existsSync, readFileSync, statSync } from "node:fs";
4
5
  import { mkdir, writeFile } from "node:fs/promises";
@@ -519,8 +520,8 @@ export function createCaptureContext(
519
520
  process.env,
520
521
  provider.secrets?.map((secret) => secret.name) ?? [],
521
522
  );
522
- const env = { get: (key: string) => providerEnvironment[key] };
523
- const engineEnv = { get: (key: string) => process.env[key] };
523
+ const env = { get: (key: string) => readDiagnosticEnv(key, providerEnvironment) };
524
+ const engineEnv = { get: (key: string) => readDiagnosticEnv(key) };
524
525
  const engineCredentials = readEngineProxyCredentials();
525
526
  const credential = {
526
527
  mode: "none" as const,
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "../runtime/diagnostic-env.js";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import { existsSync } from "node:fs";
3
4
  import { createRequire } from "node:module";
@@ -63,17 +64,17 @@ const SMARTPROXY_ALLOCATOR_RETRY_BASE_MS = 25;
63
64
  const SMARTPROXY_EXTRACTION_CACHE_TTL_MS = 15_000;
64
65
  const SMARTPROXY_EXTRACTION_SOFT_REFRESH_MS = 10_000;
65
66
  function redisUrlFromEnv() {
66
- return (process.env.APIFUSE__PROVIDER__CACHE_REDIS_URL?.trim() ||
67
- process.env[REDIS_URL_ENV]?.trim() ||
67
+ return (readDiagnosticEnv(PROVIDER_CACHE_REDIS_URL_ENV)?.trim() ||
68
+ readDiagnosticEnv(REDIS_URL_ENV)?.trim() ||
68
69
  undefined);
69
70
  }
70
71
  export function providerCacheRedisUrlFromEnv() {
71
72
  return redisUrlFromEnv();
72
73
  }
73
74
  export function providerStateRedisUrlFromEnv() {
74
- return (process.env[PROVIDER_STATE_REDIS_URL_ENV]?.trim() ||
75
- process.env[PROVIDER_CACHE_REDIS_URL_ENV]?.trim() ||
76
- process.env[REDIS_URL_ENV]?.trim() ||
75
+ return (readDiagnosticEnv(PROVIDER_STATE_REDIS_URL_ENV)?.trim() ||
76
+ readDiagnosticEnv(PROVIDER_CACHE_REDIS_URL_ENV)?.trim() ||
77
+ readDiagnosticEnv(REDIS_URL_ENV)?.trim() ||
77
78
  undefined);
78
79
  }
79
80
  /** @internal Test-only hook for exercising shared proxy-cache behavior. */
@@ -407,10 +408,10 @@ export async function resolveWithVendor(vendor, policy, options, context) {
407
408
  if (vendor === "nodemaven") {
408
409
  const startedAt = Date.now();
409
410
  const username = (context.credentials === undefined
410
- ? process.env[NODEMAVEN_USERNAME_ENV]
411
+ ? readDiagnosticEnv(NODEMAVEN_USERNAME_ENV)
411
412
  : context.credentials[NODEMAVEN_USERNAME_ENV])?.trim();
412
413
  const password = (context.credentials === undefined
413
- ? process.env[NODEMAVEN_PASSWORD_ENV]
414
+ ? readDiagnosticEnv(NODEMAVEN_PASSWORD_ENV)
414
415
  : context.credentials[NODEMAVEN_PASSWORD_ENV])?.trim();
415
416
  const filter = context.credentials === undefined
416
417
  ? process.env[NODEMAVEN_FILTER_ENV]
@@ -453,7 +454,7 @@ export async function resolveWithVendor(vendor, policy, options, context) {
453
454
  }
454
455
  // smartproxy allocation-style vendor.
455
456
  const appKey = (context.credentials === undefined
456
- ? process.env[SMARTPROXY_APP_KEY_ENV]
457
+ ? readDiagnosticEnv(SMARTPROXY_APP_KEY_ENV)
457
458
  : context.credentials[SMARTPROXY_APP_KEY_ENV])?.trim();
458
459
  if (!appKey) {
459
460
  // Guarded by vendorHasCredentials; treated as a vendor-internal failure.
@@ -520,14 +521,14 @@ function envDefaultProvider() {
520
521
  function vendorHasCredentials(vendor, credentials) {
521
522
  if (vendor === "nodemaven") {
522
523
  return Boolean((credentials === undefined
523
- ? process.env[NODEMAVEN_USERNAME_ENV]
524
+ ? readDiagnosticEnv(NODEMAVEN_USERNAME_ENV)
524
525
  : credentials[NODEMAVEN_USERNAME_ENV])?.trim() &&
525
526
  (credentials === undefined
526
- ? process.env[NODEMAVEN_PASSWORD_ENV]
527
+ ? readDiagnosticEnv(NODEMAVEN_PASSWORD_ENV)
527
528
  : credentials[NODEMAVEN_PASSWORD_ENV])?.trim());
528
529
  }
529
530
  return Boolean((credentials === undefined
530
- ? process.env[SMARTPROXY_APP_KEY_ENV]
531
+ ? readDiagnosticEnv(SMARTPROXY_APP_KEY_ENV)
531
532
  : credentials[SMARTPROXY_APP_KEY_ENV])?.trim());
532
533
  }
533
534
  function missingCredentialEnv(vendor) {
@@ -1178,7 +1179,7 @@ function markSmartproxyCacheInvalidated(options = {}) {
1178
1179
  }
1179
1180
  const lifetimeMinutes = resolveSmartproxyLifetime(policy);
1180
1181
  const appKey = (options.engineCredentials === undefined
1181
- ? process.env[SMARTPROXY_APP_KEY_ENV]
1182
+ ? readDiagnosticEnv(SMARTPROXY_APP_KEY_ENV)
1182
1183
  : options.engineCredentials[SMARTPROXY_APP_KEY_ENV])?.trim();
1183
1184
  if (!appKey)
1184
1185
  return undefined;
package/dist/engine.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./runtime/diagnostic-env.js";
1
2
  import { ProviderError } from "./errors.js";
2
3
  import { APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY, } from "./runtime/resolver-config.js";
3
4
  import { OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME, } from "./runtime/otlp.js";
@@ -99,7 +100,7 @@ export function isEngineOwnedEnvName(name) {
99
100
  /** Capture credentials in the engine host before constructing provider bindings. */
100
101
  export function readEngineProxyCredentials(environment = process.env) {
101
102
  return Object.fromEntries(ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES.flatMap((name) => {
102
- const value = environment[name]?.trim();
103
+ const value = readDiagnosticEnv(name, environment)?.trim();
103
104
  return value ? [[name, value]] : [];
104
105
  }));
105
106
  }
@@ -108,7 +109,7 @@ export function createProviderEnvironment(environment, declaredNames) {
108
109
  return Object.fromEntries(declaredNames.flatMap((name) => {
109
110
  if (isEngineOwnedEnvName(name))
110
111
  return [];
111
- const value = environment[name];
112
+ const value = readDiagnosticEnv(name, environment);
112
113
  return value === undefined ? [] : [[name, value]];
113
114
  }));
114
115
  }
package/dist/index.d.ts CHANGED
@@ -45,7 +45,8 @@ export { createUnsupportedProviderRuntimeState, UnsupportedProviderStateError, }
45
45
  export { APIFUSE__OCR__API_KEY_ENV, APIFUSE__OCR__BACKEND_ENV, APIFUSE__OCR__BASE_URL_ENV, APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__OCR__MODEL_ENV, CLOUDFLARE_ACCOUNT_ID_ENV, CLOUDFLARE_WORKERS_AI_OCR_BACKEND, createCloudflareWorkersAiOcrClient, createOcrClientFromEnv, createOpenAiCompatibleOcrClient, createUnsupportedOcrClient, DEFAULT_CLOUDFLARE_WORKERS_AI_OCR_MODEL, extractCaptchaCandidates, OPENAI_COMPATIBLE_OCR_BACKEND, } from "./runtime/ocr.js";
46
46
  export { APIFUSE__STT__BACKEND_ENV, APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__STT__MODEL_ENV, createSttClientFromEnv, createUnsupportedSttClient, extractVerificationCode, resolveSttPrompt, } from "./runtime/stt.js";
47
47
  export { type CreateTraceContextOptions, createTraceContext, type Span, type TraceContext, } from "./runtime/trace.js";
48
- export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
48
+ export type { DiagnosticRedactor } from "./runtime/diagnostic-redactor.js";
49
+ export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
49
50
  export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, type ProviderErrorCauseFrame, type ServeOptions, serve, } from "./server/serve.js";
50
51
  export { DEFAULT_STEALTH_BROWSER, DEFAULT_STEALTH_OS, getStealthProfile, listStealthProfiles, } from "./stealth/profiles.js";
51
52
  export * from "./stream.js";
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ export { createUnsupportedProviderRuntimeState, UnsupportedProviderStateError, }
36
36
  export { APIFUSE__OCR__API_KEY_ENV, APIFUSE__OCR__BACKEND_ENV, APIFUSE__OCR__BASE_URL_ENV, APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__OCR__MODEL_ENV, CLOUDFLARE_ACCOUNT_ID_ENV, CLOUDFLARE_WORKERS_AI_OCR_BACKEND, createCloudflareWorkersAiOcrClient, createOcrClientFromEnv, createOpenAiCompatibleOcrClient, createUnsupportedOcrClient, DEFAULT_CLOUDFLARE_WORKERS_AI_OCR_MODEL, extractCaptchaCandidates, OPENAI_COMPATIBLE_OCR_BACKEND, } from "./runtime/ocr.js";
37
37
  export { APIFUSE__STT__BACKEND_ENV, APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__STT__MODEL_ENV, createSttClientFromEnv, createUnsupportedSttClient, extractVerificationCode, resolveSttPrompt, } from "./runtime/stt.js";
38
38
  export { createTraceContext, } from "./runtime/trace.js";
39
- export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
39
+ export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
40
40
  export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, serve, } from "./server/serve.js";
41
41
  export { DEFAULT_STEALTH_BROWSER, DEFAULT_STEALTH_OS, getStealthProfile, listStealthProfiles, } from "./stealth/profiles.js";
42
42
  export * from "./stream.js";
package/dist/lint.js CHANGED
@@ -380,17 +380,28 @@ function getChildSchemas(schema) {
380
380
  function uniqueFields(fields) {
381
381
  return Array.from(new Set(fields));
382
382
  }
383
- function isSensitiveSchema(schema) {
384
- if (!schema || typeof schema !== "object" || !("meta" in schema)) {
385
- return false;
383
+ function readSensitivityDeclaration(schema, seen = new Set()) {
384
+ // Declarations sit on the leaf; same-path wrappers such as .optional(),
385
+ // .nullable(), .pipe() or z.lazy() carry no metadata of their own, so look
386
+ // through them.
387
+ if (!isSchema(schema) || seen.has(schema))
388
+ return undefined;
389
+ seen.add(schema);
390
+ const value = Reflect.get(getSchemaMetadata(schema), APIFUSE_SENSITIVE_META_KEY);
391
+ if (typeof value === "boolean")
392
+ return value;
393
+ const def = getSchemaDef(schema);
394
+ const children = def.type === "pipe"
395
+ ? [def.in, def.out]
396
+ : def.type === "lazy" && typeof def.getter === "function"
397
+ ? [def.getter()]
398
+ : [def.innerType];
399
+ for (const child of children) {
400
+ const declaration = readSensitivityDeclaration(child, seen);
401
+ if (declaration !== undefined)
402
+ return declaration;
386
403
  }
387
- const meta = schema.meta;
388
- if (typeof meta !== "function")
389
- return false;
390
- const metadata = meta.call(schema);
391
- return (!!metadata &&
392
- typeof metadata === "object" &&
393
- Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true);
404
+ return undefined;
394
405
  }
395
406
  function getSchemaMetadata(schema) {
396
407
  return schema.meta?.() ?? {};
@@ -416,9 +427,19 @@ const SENSITIVE_FIELD_NAMES = new Set([
416
427
  "phonenumber",
417
428
  "paymenturl",
418
429
  ]);
430
+ // Only phone-shaped keys can hold reviewed public data (facility phones); the
431
+ // rest are credentials, which a public declaration must not launder.
432
+ const PUBLIC_DECLARABLE_FIELD_NAMES = new Set(["phone", "phonenumber"]);
433
+ function normalizeFieldName(name) {
434
+ return name.toLowerCase().replace(/[-_\s]/g, "");
435
+ }
419
436
  function isSensitiveFieldName(name) {
420
- const normalized = name.toLowerCase().replace(/[-_\s]/g, "");
421
- return SENSITIVE_FIELD_NAMES.has(normalized);
437
+ return SENSITIVE_FIELD_NAMES.has(normalizeFieldName(name));
438
+ }
439
+ function isDeclaredSensitiveField(key, schema) {
440
+ const declaration = readSensitivityDeclaration(schema);
441
+ return (declaration === true ||
442
+ (declaration === false && PUBLIC_DECLARABLE_FIELD_NAMES.has(normalizeFieldName(key))));
422
443
  }
423
444
  function collectUnmarkedSensitiveFields(schema, basePath, seen = new Set()) {
424
445
  if (!isSchema(schema) || seen.has(schema)) {
@@ -428,7 +449,7 @@ function collectUnmarkedSensitiveFields(schema, basePath, seen = new Set()) {
428
449
  const out = [];
429
450
  for (const [key, child] of Object.entries(getObjectShape(schema))) {
430
451
  const childPath = basePath ? `${basePath}.${key}` : key;
431
- if (isSensitiveFieldName(key) && !isSensitiveSchema(child)) {
452
+ if (isSensitiveFieldName(key) && !isDeclaredSensitiveField(key, child)) {
432
453
  out.push(childPath);
433
454
  }
434
455
  out.push(...collectUnmarkedSensitiveFields(child, childPath, seen));
@@ -1086,7 +1107,7 @@ export function lintOperation(op) {
1086
1107
  rule: "sensitive-field-unmarked",
1087
1108
  level: "warn",
1088
1109
  field,
1089
- message: `Schema field "${field}" looks sensitive; mark it with fields.*(), field(..., { sensitive: true }), or sensitive(...).`,
1110
+ message: `Schema field "${field}" looks sensitive; mark it with fields.*() or sensitive(...); publicField(...) is accepted only for reviewed public phone-shaped fields.`,
1090
1111
  });
1091
1112
  }
1092
1113
  for (const field of uniqueFields(collectUnmarkedSensitiveFields(op.output, "output"))) {
@@ -1094,7 +1115,7 @@ export function lintOperation(op) {
1094
1115
  rule: "sensitive-field-unmarked",
1095
1116
  level: "warn",
1096
1117
  field,
1097
- message: `Schema field "${field}" looks sensitive; mark it with fields.*(), field(..., { sensitive: true }), or sensitive(...).`,
1118
+ message: `Schema field "${field}" looks sensitive; mark it with fields.*() or sensitive(...); publicField(...) is accepted only for reviewed public phone-shaped fields.`,
1098
1119
  });
1099
1120
  }
1100
1121
  return diagnostics;
@@ -12,7 +12,7 @@ export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_
12
12
  export type { ProviderCapabilityKey, ProviderEngine, ProviderEngineAttachmentInput, ProviderEngineBindingCandidates, ProviderEngineCapabilitySurface, ProviderEngineRequest, ProviderEngineResidentSurface, ProviderEngineSession, ProviderEngineTransport, } from "./engine.js";
13
13
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
14
14
  export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
15
- export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
15
+ export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
16
16
  export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationExample, OperationErrorCode, ProviderErrorStatus, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceConsumeMode, ProviderChoiceConsumeResult, ProviderChoiceContext, ProviderChoiceExplicitParseResult, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderContextFor, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeTarget, ProviderRuntimeState, ProviderStealthConfig, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateNamespaceScope, StateValue, StateWriteOptions, StealthChallengeClassification, } from "./types.js";
17
17
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
18
18
  export type { NativeGatewayProxy, NativeGatewayProxyResolutionInput, NativeGatewayProxySkipReason, NativeGatewayProxySynthesizer, NativeGatewayProxySynthesisResult, NativeGatewayProxySynthesisInput, NativeNetworkClientOptions, NativeNetworkErrorCode, VendorCredentialLookup, VendorCredentialResolver, } from "./runtime/native-network.js";
package/dist/provider.js CHANGED
@@ -7,6 +7,6 @@ export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, i
7
7
  export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RUNTIME_ENV_NAMES, ENGINE_OWNED_TELEMETRY_ENV_NAMES, isEngineOwnedEnvName, isEngineOwnedProxyCredentialName, isEngineOwnedResolverCredentialName, isEngineOwnedRuntimeEnvName, isEngineOwnedTelemetryEnvName, PROVIDER_CAPABILITY_KEYS, PROVIDER_ENGINE_PROTOCOL_VERSION, readEngineProxyCredentials, } from "./engine.js";
8
8
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
9
9
  export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
10
- export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
10
+ export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
11
11
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
12
12
  export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
1
2
  import { createRequire } from "node:module";
2
3
  import { ProviderError } from "../errors.js";
3
4
  const require = createRequire(import.meta.url);
@@ -289,7 +290,7 @@ function createResourcePolicyHandler(policy, allowedMethods) {
289
290
  };
290
291
  }
291
292
  function getDefaultCdpPoolUrl(env = process.env) {
292
- return env.APIFUSE__CDP_POOL__URL;
293
+ return readDiagnosticEnv("APIFUSE__CDP_POOL__URL", env);
293
294
  }
294
295
  async function importOptionalModule(moduleName) {
295
296
  return (await import(moduleName));
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
1
2
  import { createHash, createHmac } from "node:crypto";
2
3
  import { providerCacheRedisUrlFromEnv } from "../config/loader.js";
3
4
  import { ProviderError } from "../errors.js";
@@ -279,7 +280,7 @@ async function withRedisFallback(operation) {
279
280
  }
280
281
  export function createProviderCache(options) {
281
282
  const redisUrl = options.redisUrl ?? providerCacheRedisUrlFromEnv();
282
- const configuredPepper = process.env[APIFUSE__CACHE__KEY_PEPPER_ENV];
283
+ const configuredPepper = readDiagnosticEnv(APIFUSE__CACHE__KEY_PEPPER_ENV);
283
284
  const pepper = configuredPepper && configuredPepper.length > 0 ? configuredPepper : undefined;
284
285
  const backend = getSharedBackend(redisUrl);
285
286
  const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
@@ -471,7 +472,7 @@ export function isProviderCacheBypassed(cache) {
471
472
  return bypassCaches.has(cache);
472
473
  }
473
474
  export function createBypassProviderCache(options) {
474
- const configuredPepper = process.env[APIFUSE__CACHE__KEY_PEPPER_ENV];
475
+ const configuredPepper = readDiagnosticEnv(APIFUSE__CACHE__KEY_PEPPER_ENV);
475
476
  const pepper = configuredPepper && configuredPepper.length > 0 ? configuredPepper : undefined;
476
477
  const events = [];
477
478
  const secretScopedKeys = new Set();
@@ -0,0 +1,5 @@
1
+ export declare function resolvedStringVariants(value: string | undefined): string[];
2
+ export declare function urlCredentialComponents(value: string, options: {
3
+ includePathAndQuery: boolean;
4
+ }): string[];
5
+ export declare function otlpHeaderCredentialValues(value: string): string[];
@@ -0,0 +1,57 @@
1
+ export function resolvedStringVariants(value) {
2
+ if (value === undefined)
3
+ return [];
4
+ return [...new Set([value, value.trim()])].filter((candidate) => candidate.length > 0);
5
+ }
6
+ function decodedComponent(value) {
7
+ try {
8
+ return decodeURIComponent(value);
9
+ }
10
+ catch {
11
+ return value;
12
+ }
13
+ }
14
+ export function urlCredentialComponents(value, options) {
15
+ let url;
16
+ try {
17
+ url = new URL(value.trim());
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ const username = decodedComponent(url.username);
23
+ const password = decodedComponent(url.password);
24
+ const components = [username, password, username && password ? `${username}:${password}` : ""];
25
+ if (options.includePathAndQuery) {
26
+ for (const segment of url.pathname.split("/")) {
27
+ if (segment)
28
+ components.push(decodedComponent(segment));
29
+ }
30
+ for (const [key, entryValue] of url.searchParams) {
31
+ if (key)
32
+ components.push(key);
33
+ if (entryValue) {
34
+ components.push(entryValue, `${key}=${entryValue}`);
35
+ }
36
+ }
37
+ }
38
+ return [...new Set(components.flatMap((component) => resolvedStringVariants(component)))];
39
+ }
40
+ export function otlpHeaderCredentialValues(value) {
41
+ const variants = resolvedStringVariants(value);
42
+ for (const member of value.split(",")) {
43
+ const separator = member.indexOf("=");
44
+ if (separator <= 0)
45
+ continue;
46
+ const headerValue = decodedComponent(member.slice(separator + 1).trim());
47
+ variants.push(...resolvedStringVariants(headerValue));
48
+ const headerName = decodedComponent(member.slice(0, separator).trim()).toLowerCase();
49
+ if (headerName === "authorization" || headerName === "proxy-authorization") {
50
+ // A transport may echo just the credential without its HTTP auth scheme.
51
+ const credential = /^(?:Bearer|Basic)\s+(.+)$/i.exec(headerValue)?.[1];
52
+ if (credential)
53
+ variants.push(...resolvedStringVariants(credential));
54
+ }
55
+ }
56
+ return [...new Set(variants)];
57
+ }
@@ -0,0 +1,11 @@
1
+ import { type CompiledDiagnosticSensitiveValues } from "./diagnostic-redactor.js";
2
+ type EnvObserver = (name: string, value: string) => void;
3
+ export interface DiagnosticEnvScope {
4
+ observe: EnvObserver;
5
+ finished: boolean;
6
+ staticValues?: CompiledDiagnosticSensitiveValues;
7
+ }
8
+ /** Resolve and register atomically, before any consumer can use a live env credential. */
9
+ export declare function readDiagnosticEnv(name: string, env?: Readonly<Record<string, string | undefined>>): string | undefined;
10
+ export declare function withDiagnosticEnv<T>(observer: EnvObserver | DiagnosticEnvScope, fn: () => T): T;
11
+ export {};
@@ -0,0 +1,35 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { resolvedStringVariants, urlCredentialComponents, otlpHeaderCredentialValues, } from "./diagnostic-env-values.js";
3
+ import { registerProcessDiagnosticValues, suppressProcessDiagnosticValues, withAppDiagnosticFallback, } from "./diagnostic-redactor.js";
4
+ const observers = new AsyncLocalStorage();
5
+ /** Resolve and register atomically, before any consumer can use a live env credential. */
6
+ export function readDiagnosticEnv(name, env = process.env) {
7
+ const value = env[name];
8
+ if (value !== undefined) {
9
+ const scope = observers.getStore();
10
+ if (scope && !scope.finished)
11
+ scope.observe(name, value);
12
+ else {
13
+ // ALS survives the response. Finished requests register into the app's
14
+ // inherited fallback lifetime, never a dead request's private inventory.
15
+ if (Buffer.byteLength(value) > 64 * 1024)
16
+ suppressProcessDiagnosticValues();
17
+ else
18
+ registerProcessDiagnosticValues([
19
+ ...resolvedStringVariants(value),
20
+ ...urlCredentialComponents(value, {
21
+ includePathAndQuery: name === "APIFUSE__CDP_POOL__URL",
22
+ }),
23
+ ...(name === "OTEL_EXPORTER_OTLP_HEADERS" || name === "OTEL_EXPORTER_OTLP_TRACES_HEADERS"
24
+ ? otlpHeaderCredentialValues(value)
25
+ : []),
26
+ ]);
27
+ }
28
+ }
29
+ return value;
30
+ }
31
+ export function withDiagnosticEnv(observer, fn) {
32
+ const scope = typeof observer === "function" ? { observe: observer, finished: false } : observer;
33
+ const run = () => observers.run(scope, fn);
34
+ return scope.staticValues ? withAppDiagnosticFallback(scope.staticValues, run) : run();
35
+ }