@apifuse/provider-sdk 2.2.0-beta.58 → 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 +25 -0
- package/CHANGELOG.md +8 -0
- package/README.md +18 -2
- package/bin/apifuse-dev.ts +2 -1
- package/bin/apifuse-pack-types.ts +49 -0
- package/bin/apifuse-perf.ts +41 -4
- package/bin/apifuse-record.ts +9 -2
- package/dist/cli/templates/provider/README.md.tpl +15 -3
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.js +43 -18
- package/dist/declaration-validation.d.ts +2 -1
- package/dist/declaration-validation.js +39 -0
- package/dist/define.d.ts +4 -4
- package/dist/define.js +25 -1
- package/dist/engine.d.ts +16 -0
- package/dist/engine.js +59 -3
- package/dist/error-resolution.js +13 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/lint.js +38 -17
- package/dist/provider.d.ts +3 -3
- package/dist/provider.js +2 -2
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser.js +2 -1
- package/dist/runtime/cache.d.ts +2 -0
- package/dist/runtime/cache.js +11 -3
- 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 +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/env.js +2 -1
- package/dist/runtime/http.js +1 -0
- 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/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/request-options.d.ts +7 -0
- package/dist/runtime/request-options.js +62 -22
- package/dist/runtime/request-telemetry.d.ts +7 -9
- package/dist/runtime/resolver-config.d.ts +7 -0
- package/dist/runtime/resolver-config.js +8 -0
- package/dist/runtime/resolver-public.d.ts +3 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-shared.d.ts +9 -0
- package/dist/runtime/resolver-shared.js +57 -0
- package/dist/runtime/resolver-telemetry.d.ts +134 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +24 -0
- package/dist/runtime/resolver-usage.js +60 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +5 -0
- package/dist/runtime/resolver-vendors/bindings.js +5 -0
- package/dist/runtime/resolver-vendors/browser.js +0 -1
- package/dist/runtime/resolver-vendors/capsolver.d.ts +2 -2
- package/dist/runtime/resolver-vendors/capsolver.js +21 -9
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -2
- package/dist/runtime/resolver-vendors/twocaptcha.js +26 -10
- package/dist/runtime/resolver-vendors/types.d.ts +35 -5
- package/dist/runtime/resolver-vendors/types.js +20 -10
- package/dist/runtime/resolver.d.ts +5 -1
- package/dist/runtime/resolver.js +400 -111
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth.js +547 -53
- 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 +20 -0
- package/dist/schema.js +72 -2
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/self-test.d.ts +4 -0
- package/dist/server/self-test.js +10 -1
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +131 -0
- package/dist/server/serve-implementation.d.ts +7 -1
- package/dist/server/serve-implementation.js +394 -125
- package/dist/server/trace-output.d.ts +2 -1
- package/dist/server/trace-output.js +46 -14
- package/dist/server/types.d.ts +6 -0
- package/dist/server/types.js +14 -0
- package/dist/stealth/profiles.d.ts +7 -1
- package/dist/stealth/profiles.js +9 -0
- package/dist/testing/run.js +6 -0
- package/dist/trace-sanitization.d.ts +4 -3
- package/dist/trace-sanitization.js +45 -22
- package/dist/types.d.ts +106 -10
- package/package.json +2 -1
- package/src/cli/templates/provider/README.md.tpl +15 -3
- package/src/config/loader.ts +47 -18
- package/src/declaration-validation.ts +47 -1
- package/src/define.ts +45 -3
- package/src/engine.ts +78 -3
- package/src/error-resolution.ts +13 -0
- package/src/index.ts +34 -1
- package/src/lint.ts +44 -18
- package/src/provider.ts +10 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser.ts +2 -1
- package/src/runtime/cache.ts +13 -3
- 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 +341 -0
- package/src/runtime/env.ts +2 -1
- package/src/runtime/http.ts +1 -0
- 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/proxy-retry-policy.ts +12 -1
- package/src/runtime/request-options.ts +76 -28
- package/src/runtime/request-telemetry.ts +5 -7
- package/src/runtime/resolver-config.ts +12 -0
- package/src/runtime/resolver-public.ts +21 -0
- package/src/runtime/resolver-shared.ts +75 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +96 -0
- package/src/runtime/resolver-vendors/bindings.ts +5 -0
- package/src/runtime/resolver-vendors/browser.ts +0 -1
- package/src/runtime/resolver-vendors/capsolver.ts +22 -3
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +26 -12
- package/src/runtime/resolver-vendors/types.ts +60 -12
- package/src/runtime/resolver.ts +468 -126
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth.ts +727 -95
- package/src/runtime/stt.ts +3 -2
- package/src/runtime/trace.ts +168 -26
- package/src/schema.ts +80 -2
- package/src/server/index.ts +21 -0
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +13 -0
- package/src/server/sensitive-values.ts +200 -0
- package/src/server/serve-implementation.ts +561 -136
- package/src/server/trace-output.ts +77 -17
- package/src/server/types.ts +14 -0
- package/src/stealth/profiles.ts +10 -0
- package/src/testing/run.ts +6 -0
- package/src/trace-sanitization.ts +70 -23
- package/src/types.ts +124 -19
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type DiagnosticRedactor } from "../runtime/diagnostic-redactor.js";
|
|
1
2
|
import { type CreateTraceContextOptions } from "../runtime/trace.js";
|
|
2
3
|
import type { TraceConfig } from "../types.js";
|
|
3
4
|
type EnvLike = Record<string, string | undefined>;
|
|
4
5
|
/** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
|
|
5
|
-
export declare function resolveServerTraceContextOptions(config: TraceConfig, resourceAttributes: Record<string, string>, env?: EnvLike): CreateTraceContextOptions;
|
|
6
|
+
export declare function resolveServerTraceContextOptions(config: TraceConfig, resourceAttributes: Record<string, string>, env?: EnvLike, redact?: DiagnosticRedactor): CreateTraceContextOptions;
|
|
6
7
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { isSensitiveFixtureKey, REDACTED_FIXTURE_VALUE } from "../fixture-sanitization.js";
|
|
2
|
+
import { redactedKeyAllocator, isRedactedKey, REDACTION_FAILED, redactDiagnosticText, } from "../runtime/diagnostic-redactor.js";
|
|
2
3
|
import { OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, resolveOTLPExportOptions, resolveOTLPResourceAttributes, } from "../runtime/otlp.js";
|
|
3
4
|
import { resolveTraceContextOptions, } from "../runtime/trace.js";
|
|
4
5
|
import { sanitizeSpanForOutput, sanitizeSpanNameForOutput, sanitizeTraceAttributes, } from "../trace-sanitization.js";
|
|
6
|
+
const TRACE_RESOURCE_ATTRIBUTE_SANITIZER = Symbol.for("@apifuse/provider-sdk/runtime/trace-resource-attribute-sanitizer");
|
|
5
7
|
// Warn once per environment object: process.env in production, each injected env in tests.
|
|
6
8
|
const warnedEnvironments = new WeakSet();
|
|
7
9
|
const warnedResourceEnvironments = new WeakSet();
|
|
@@ -36,25 +38,48 @@ function resolveServerOTLPExportOptions(config, env) {
|
|
|
36
38
|
* environment are identifiers rather than request data: their values are kept, with control
|
|
37
39
|
* characters neutralized, length bounded, and secret-named keys redacted.
|
|
38
40
|
*/
|
|
39
|
-
function resolveExportResourceAttributes(requestAttributes, env) {
|
|
41
|
+
function resolveExportResourceAttributes(requestAttributes, env, redact) {
|
|
40
42
|
const resolution = resolveOTLPResourceAttributes({}, env);
|
|
41
43
|
warnDiscardedResourceAttributes(env, resolution);
|
|
42
|
-
const operatorAttributes =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
const operatorAttributes = resolution.attributes;
|
|
45
|
+
const requestKeys = new Set(Object.keys(requestAttributes));
|
|
46
|
+
return {
|
|
47
|
+
// Keep the raw snapshot private until export. A credential registered after
|
|
48
|
+
// trace creation must be matched before any key or value is bounded.
|
|
49
|
+
attributes: { ...operatorAttributes, ...requestAttributes },
|
|
50
|
+
sanitize(attributes) {
|
|
51
|
+
const nextKey = redactedKeyAllocator(Object.keys(attributes));
|
|
52
|
+
const sanitizedOperatorAttributes = {};
|
|
53
|
+
const rawRequestAttributes = {};
|
|
54
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
55
|
+
const unchangedOperatorValue = !requestKeys.has(key) && operatorAttributes[key] === value;
|
|
56
|
+
if (unchangedOperatorValue) {
|
|
57
|
+
const redactedKey = isRedactedKey(key) ? key : redactDiagnosticText(key, redact);
|
|
58
|
+
const keyChanged = redactedKey !== key;
|
|
59
|
+
const sanitizedKey = keyChanged ? nextKey() : sanitizeSpanNameForOutput(key);
|
|
60
|
+
sanitizedOperatorAttributes[sanitizedKey] = keyChanged
|
|
61
|
+
? redactedKey === REDACTION_FAILED
|
|
62
|
+
? REDACTION_FAILED
|
|
63
|
+
: REDACTED_FIXTURE_VALUE
|
|
64
|
+
: isSensitiveFixtureKey(key)
|
|
65
|
+
? REDACTED_FIXTURE_VALUE
|
|
66
|
+
: sanitizeSpanNameForOutput(value, redact);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
rawRequestAttributes[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const sanitizedRequestAttributes = Object.fromEntries(Object.entries(sanitizeTraceAttributes(rawRequestAttributes, redact, Object.keys(sanitizedOperatorAttributes))).map(([key, value]) => [key, String(value)]));
|
|
73
|
+
return { ...sanitizedOperatorAttributes, ...sanitizedRequestAttributes };
|
|
74
|
+
},
|
|
75
|
+
};
|
|
51
76
|
}
|
|
52
77
|
/** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
|
|
53
|
-
export function resolveServerTraceContextOptions(config, resourceAttributes, env = process.env) {
|
|
78
|
+
export function resolveServerTraceContextOptions(config, resourceAttributes, env = process.env, redact) {
|
|
54
79
|
const resolved = resolveTraceContextOptions(config);
|
|
55
80
|
const outputEnabled = config.enabled !== false && config.exporter !== "none";
|
|
56
81
|
const consoleHook = outputEnabled && (config.exporter === "console" || config.exporter === "json")
|
|
57
|
-
? (span) => console.log(JSON.stringify(sanitizeSpanForOutput(span, resourceAttributes)))
|
|
82
|
+
? (span) => console.log(JSON.stringify(sanitizeSpanForOutput(span, resourceAttributes, redact)))
|
|
58
83
|
: undefined;
|
|
59
84
|
const onSpan = consoleHook && resolved.onSpan
|
|
60
85
|
? (span) => {
|
|
@@ -65,14 +90,21 @@ export function resolveServerTraceContextOptions(config, resourceAttributes, env
|
|
|
65
90
|
const exportOptions = outputEnabled && config.exporter === "otlp"
|
|
66
91
|
? resolveServerOTLPExportOptions(config, env)
|
|
67
92
|
: undefined;
|
|
93
|
+
const exportResources = exportOptions
|
|
94
|
+
? resolveExportResourceAttributes(resourceAttributes, env, redact)
|
|
95
|
+
: undefined;
|
|
96
|
+
if (exportResources) {
|
|
97
|
+
Object.defineProperty(exportResources.attributes, TRACE_RESOURCE_ATTRIBUTE_SANITIZER, { value: exportResources.sanitize });
|
|
98
|
+
}
|
|
68
99
|
return {
|
|
69
100
|
maxSpans: resolved.maxSpans,
|
|
70
101
|
onSpan,
|
|
102
|
+
...(redact ? { redact } : {}),
|
|
71
103
|
...(exportOptions
|
|
72
104
|
? {
|
|
73
105
|
exportOptions,
|
|
74
|
-
resourceAttributes:
|
|
75
|
-
sanitizeSpanForExport: (span) => sanitizeSpanForOutput(span),
|
|
106
|
+
resourceAttributes: exportResources?.attributes,
|
|
107
|
+
sanitizeSpanForExport: (span) => sanitizeSpanForOutput(span, undefined, redact),
|
|
76
108
|
}
|
|
77
109
|
: {}),
|
|
78
110
|
};
|
package/dist/server/types.d.ts
CHANGED
|
@@ -115,6 +115,9 @@ export declare const AuthFlowRequestSchema: z.ZodObject<{
|
|
|
115
115
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
116
116
|
input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
117
117
|
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
118
|
+
engine: z.ZodOptional<z.ZodObject<{
|
|
119
|
+
egressLease: z.ZodOptional<z.ZodString>;
|
|
120
|
+
}, z.core.$strict>>;
|
|
118
121
|
connection: z.ZodOptional<z.ZodObject<{
|
|
119
122
|
id: z.ZodString;
|
|
120
123
|
mode: z.ZodEnum<{
|
|
@@ -133,6 +136,9 @@ export declare const AuthFlowRequestSchema: z.ZodObject<{
|
|
|
133
136
|
export declare const AuthFlowSuccessResponseSchema: z.ZodObject<{
|
|
134
137
|
data: z.ZodUnknown;
|
|
135
138
|
contextPatch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodUnknown>>>;
|
|
139
|
+
engine: z.ZodOptional<z.ZodObject<{
|
|
140
|
+
egressLease: z.ZodOptional<z.ZodString>;
|
|
141
|
+
}, z.core.$strict>>;
|
|
136
142
|
}, z.core.$strip>;
|
|
137
143
|
export declare const AuthFlowErrorResponseSchema: z.ZodObject<{
|
|
138
144
|
error: z.ZodObject<{
|
package/dist/server/types.js
CHANGED
|
@@ -85,10 +85,24 @@ export const AuthFlowRequestSchema = z.object({
|
|
|
85
85
|
headers: z.record(z.string(), z.string()).optional(),
|
|
86
86
|
input: z.record(z.string(), z.unknown()).optional(),
|
|
87
87
|
context: z.record(z.string(), z.unknown()).optional(),
|
|
88
|
+
/** Opaque engine state. This is never projected into FlowContext or provider contextPatch. */
|
|
89
|
+
engine: z
|
|
90
|
+
.object({
|
|
91
|
+
egressLease: z.string().min(1).max(16_384).optional(),
|
|
92
|
+
})
|
|
93
|
+
.strict()
|
|
94
|
+
.optional(),
|
|
88
95
|
connection: OperationConnectionSchema.optional(),
|
|
89
96
|
});
|
|
90
97
|
export const AuthFlowSuccessResponseSchema = z.object({
|
|
91
98
|
data: z.unknown(),
|
|
92
99
|
contextPatch: z.record(z.string(), z.unknown().nullable()).optional(),
|
|
100
|
+
/** Opaque engine state to carry verbatim to the next auth turn. */
|
|
101
|
+
engine: z
|
|
102
|
+
.object({
|
|
103
|
+
egressLease: z.string().min(1).max(16_384).optional(),
|
|
104
|
+
})
|
|
105
|
+
.strict()
|
|
106
|
+
.optional(),
|
|
93
107
|
});
|
|
94
108
|
export const AuthFlowErrorResponseSchema = OperationErrorResponseSchema;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StealthProfile, StealthProfileDescriptor, StealthProfileSelection } from "../types.js";
|
|
1
|
+
import type { StealthBrowser, StealthProfile, StealthProfileDescriptor, StealthProfileSelection } from "../types.js";
|
|
2
2
|
export declare const DEFAULT_STEALTH_BROWSER: "chrome";
|
|
3
3
|
export declare const DEFAULT_STEALTH_OS: "macos";
|
|
4
4
|
/** Internal detector used to keep version-pinned string failures actionable. */
|
|
@@ -6,4 +6,10 @@ export declare function getVersionPinnedStealthReplacement(name: string): string
|
|
|
6
6
|
/** Resolve omitted axes and validate that the resulting pair is supported. */
|
|
7
7
|
export declare function resolveStealthProfileSelection(selection: StealthProfileSelection | undefined, base?: StealthProfileDescriptor): StealthProfileDescriptor;
|
|
8
8
|
export declare function getStealthProfile(selection?: StealthProfileSelection): StealthProfile;
|
|
9
|
+
/**
|
|
10
|
+
* The transport owns the browser version (Chromium follows the pinned wreq-js build), so a
|
|
11
|
+
* resolver `clientProfile` such as `safari17_0` or `chrome_149` only has to name the same
|
|
12
|
+
* browser family as the stealth session it runs on.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolverClientProfileFamily(clientProfile: string): StealthBrowser | undefined;
|
|
9
15
|
export declare function listStealthProfiles(): StealthProfileDescriptor[];
|
package/dist/stealth/profiles.js
CHANGED
|
@@ -194,6 +194,15 @@ export function getStealthProfile(selection = {}) {
|
|
|
194
194
|
h2Settings: profile.h2Settings ? { ...profile.h2Settings } : undefined,
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* The transport owns the browser version (Chromium follows the pinned wreq-js build), so a
|
|
199
|
+
* resolver `clientProfile` such as `safari17_0` or `chrome_149` only has to name the same
|
|
200
|
+
* browser family as the stealth session it runs on.
|
|
201
|
+
*/
|
|
202
|
+
export function resolverClientProfileFamily(clientProfile) {
|
|
203
|
+
const family = /^[a-z]+/u.exec(clientProfile.trim().toLowerCase())?.[0];
|
|
204
|
+
return family === "chrome" || family === "firefox" || family === "safari" ? family : undefined;
|
|
205
|
+
}
|
|
197
206
|
export function listStealthProfiles() {
|
|
198
207
|
return SUPPORTED_STEALTH_PROFILES.map((profile) => ({ ...profile }));
|
|
199
208
|
}
|
package/dist/testing/run.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { ProviderError } from "../errors.js";
|
|
2
3
|
import { createProviderCache } from "../runtime/cache.js";
|
|
3
4
|
import { createTestProviderChoiceContext } from "../runtime/choice.js";
|
|
4
5
|
import { createMemoryProviderRuntimeState } from "../runtime/state.js";
|
|
@@ -274,6 +275,11 @@ function createUpstreamContext(provider, operationName, upstreamStub) {
|
|
|
274
275
|
};
|
|
275
276
|
const createStealthSession = () => ({
|
|
276
277
|
fetch: stealthCall,
|
|
278
|
+
async replayChallenged() {
|
|
279
|
+
throw new ProviderError("Recorded transports cannot replay an engine-owned challenge", {
|
|
280
|
+
code: "REPLAY_SESSION_MISMATCH",
|
|
281
|
+
});
|
|
282
|
+
},
|
|
277
283
|
cookies: {
|
|
278
284
|
...emptyCookieJar,
|
|
279
285
|
has: () => false,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type DiagnosticRedactor } from "./runtime/diagnostic-redactor.js";
|
|
1
2
|
import type { TraceSpan } from "./types.js";
|
|
2
3
|
/** Span names are SDK-authored identifiers; retain them while preventing log injection. */
|
|
3
|
-
export declare function sanitizeSpanNameForOutput(value: string): string;
|
|
4
|
-
export declare function sanitizeTraceAttributes(attributes: Record<string, unknown>): Record<string, string | number | boolean>;
|
|
5
|
-
export declare function sanitizeSpanForOutput(span: TraceSpan, additionalAttributes?: Record<string, string
|
|
4
|
+
export declare function sanitizeSpanNameForOutput(value: string, redact?: DiagnosticRedactor): string;
|
|
5
|
+
export declare function sanitizeTraceAttributes(attributes: Record<string, unknown>, redact?: DiagnosticRedactor, reservedKeys?: Iterable<string>): Record<string, string | number | boolean>;
|
|
6
|
+
export declare function sanitizeSpanForOutput(span: TraceSpan, additionalAttributes?: Record<string, string>, redact?: DiagnosticRedactor): TraceSpan;
|
|
@@ -1,45 +1,68 @@
|
|
|
1
1
|
import { encodeDiagnosticControls, isSensitiveFixtureKey, REDACTED_FIXTURE_VALUE, sanitizeDiagnosticText, } from "./fixture-sanitization.js";
|
|
2
|
+
import { diagnosticStructuredRedactor, redactDiagnosticAttributeKey, copyDiagnosticAttributeTypes, isDiagnosticBigintAttribute, redactedKeyAllocator, isRedactedKey, redactCriticalDiagnosticText, diagnosticAttributeRedactor, REDACTION_FAILED, redactDiagnosticText, } from "./runtime/diagnostic-redactor.js";
|
|
2
3
|
const MAX_TRACE_TEXT_LENGTH = 300;
|
|
3
|
-
function sanitizeTraceText(value) {
|
|
4
|
-
const encoded = sanitizeDiagnosticText(value);
|
|
4
|
+
function sanitizeTraceText(value, redact) {
|
|
5
|
+
const encoded = sanitizeDiagnosticText(redactDiagnosticText(value, redact));
|
|
5
6
|
return encoded.length > MAX_TRACE_TEXT_LENGTH
|
|
6
7
|
? `${encoded.slice(0, MAX_TRACE_TEXT_LENGTH)}… [truncated]`
|
|
7
8
|
: encoded;
|
|
8
9
|
}
|
|
9
10
|
/** Span names are SDK-authored identifiers; retain them while preventing log injection. */
|
|
10
|
-
export function sanitizeSpanNameForOutput(value) {
|
|
11
|
-
const encoded = encodeDiagnosticControls(value);
|
|
11
|
+
export function sanitizeSpanNameForOutput(value, redact) {
|
|
12
|
+
const encoded = encodeDiagnosticControls(redactDiagnosticText(value, redact));
|
|
12
13
|
return encoded.length > MAX_TRACE_TEXT_LENGTH
|
|
13
14
|
? `${encoded.slice(0, MAX_TRACE_TEXT_LENGTH)}… [truncated]`
|
|
14
15
|
: encoded;
|
|
15
16
|
}
|
|
16
|
-
export function sanitizeTraceAttributes(attributes) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
export function sanitizeTraceAttributes(attributes, redact, reservedKeys = []) {
|
|
18
|
+
const nextKey = redactedKeyAllocator([...Object.keys(attributes), ...reservedKeys]);
|
|
19
|
+
return copyDiagnosticAttributeTypes(attributes, Object.fromEntries(Object.entries(attributes).map(([key, value]) => {
|
|
20
|
+
const redactedKey = isRedactedKey(key) ? key : redactDiagnosticAttributeKey(key, redact);
|
|
21
|
+
const keyChanged = redactedKey !== key;
|
|
22
|
+
const sanitizedKey = keyChanged ? nextKey() : sanitizeTraceText(key);
|
|
23
|
+
const valueRedactor = typeof value === "bigint" || isDiagnosticBigintAttribute(attributes, key)
|
|
24
|
+
? diagnosticStructuredRedactor(redact)
|
|
25
|
+
: diagnosticAttributeRedactor(key, redact);
|
|
26
|
+
const sanitizedValue = keyChanged
|
|
27
|
+
? redactedKey === REDACTION_FAILED
|
|
28
|
+
? REDACTION_FAILED
|
|
29
|
+
: REDACTED_FIXTURE_VALUE
|
|
30
|
+
: isSensitiveFixtureKey(key)
|
|
31
|
+
? REDACTED_FIXTURE_VALUE
|
|
32
|
+
: typeof value === "bigint" || isDiagnosticBigintAttribute(attributes, key)
|
|
33
|
+
? String(value).length < 8
|
|
34
|
+
? String(value)
|
|
35
|
+
: sanitizeTraceText(String(value), valueRedactor)
|
|
36
|
+
: typeof value === "string"
|
|
37
|
+
? sanitizeTraceText(value, valueRedactor)
|
|
38
|
+
: typeof value === "number" || typeof value === "boolean"
|
|
39
|
+
? (() => {
|
|
40
|
+
if (String(value).length < 8)
|
|
41
|
+
return value;
|
|
42
|
+
const redacted = redactDiagnosticText(String(value), diagnosticStructuredRedactor(valueRedactor));
|
|
43
|
+
return redacted === String(value) ? value : sanitizeTraceText(redacted);
|
|
44
|
+
})()
|
|
45
|
+
: sanitizeTraceText(String(value), valueRedactor);
|
|
46
|
+
return [sanitizedKey, sanitizedValue];
|
|
47
|
+
})));
|
|
27
48
|
}
|
|
28
|
-
export function sanitizeSpanForOutput(span, additionalAttributes) {
|
|
49
|
+
export function sanitizeSpanForOutput(span, additionalAttributes, redact) {
|
|
29
50
|
// Keep this schema explicit so future fields are not silently added to a
|
|
30
51
|
// process output path before their trust boundary has been reviewed.
|
|
31
52
|
return {
|
|
32
|
-
id: span.id,
|
|
33
|
-
name: sanitizeSpanNameForOutput(span.name),
|
|
53
|
+
id: redactCriticalDiagnosticText(span.id, redact),
|
|
54
|
+
name: sanitizeSpanNameForOutput(redactCriticalDiagnosticText(span.name, redact)),
|
|
34
55
|
startedAt: span.startedAt,
|
|
35
56
|
endedAt: span.endedAt,
|
|
36
57
|
duration_ms: span.duration_ms,
|
|
37
58
|
status: span.status,
|
|
38
|
-
attributes: sanitizeTraceAttributes({
|
|
59
|
+
attributes: sanitizeTraceAttributes(copyDiagnosticAttributeTypes(span.attributes, {
|
|
39
60
|
...span.attributes,
|
|
40
61
|
...(additionalAttributes ?? {}),
|
|
41
|
-
}),
|
|
42
|
-
...(span.error !== undefined ? { error: sanitizeTraceText(span.error) } : {}),
|
|
43
|
-
...(span.parentId !== undefined
|
|
62
|
+
}), redact),
|
|
63
|
+
...(span.error !== undefined ? { error: sanitizeTraceText(span.error, redact) } : {}),
|
|
64
|
+
...(span.parentId !== undefined
|
|
65
|
+
? { parentId: redactCriticalDiagnosticText(span.parentId, redact) }
|
|
66
|
+
: {}),
|
|
44
67
|
};
|
|
45
68
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -219,7 +219,7 @@ export interface ProviderSttConfig {
|
|
|
219
219
|
*
|
|
220
220
|
* `ProviderResolverConfig.vendors` overrides the SDK policy when declared.
|
|
221
221
|
*/
|
|
222
|
-
export type ProviderResolverVendor = "browser" | "capsolver" | "capmonster" | "2captcha" | "custom";
|
|
222
|
+
export type ProviderResolverVendor = "browser" | "capsolver" | "capmonster" | "2captcha" | "hypersolutions" | "custom";
|
|
223
223
|
/**
|
|
224
224
|
* Token-family kinds resolve to `{ form: "token" }`. Cookie-family kinds resolve
|
|
225
225
|
* to `{ form: "cookies" }`; network-identity binding is defined per kind. `aws_waf`
|
|
@@ -275,6 +275,22 @@ export type ProviderChallenge = {
|
|
|
275
275
|
readonly bmsz?: string;
|
|
276
276
|
/** Bot Manager major version when known ("3" measured on zozo.jp). */
|
|
277
277
|
readonly version?: string;
|
|
278
|
+
} | {
|
|
279
|
+
readonly kind: "akamai_sbsd";
|
|
280
|
+
readonly pageUrl: string;
|
|
281
|
+
/**
|
|
282
|
+
* Discovered SBSD script URL. `?v=&t=` is a hard challenge and `?v=`
|
|
283
|
+
* is passive or a remembered script. A later challenge token never
|
|
284
|
+
* changes this URL.
|
|
285
|
+
*/
|
|
286
|
+
readonly scriptUrl: string;
|
|
287
|
+
readonly stateCookieName: "sbsd_o" | "bm_so";
|
|
288
|
+
/**
|
|
289
|
+
* Token returned by a later `cpr_chlge` response. When present with a
|
|
290
|
+
* remembered v-only script, it selects index 0 and is appended only to
|
|
291
|
+
* the payload POST URL.
|
|
292
|
+
*/
|
|
293
|
+
readonly challengeToken?: string;
|
|
278
294
|
};
|
|
279
295
|
export type ProviderChallengeKind = ProviderChallenge["kind"];
|
|
280
296
|
/**
|
|
@@ -282,7 +298,9 @@ export type ProviderChallengeKind = ProviderChallenge["kind"];
|
|
|
282
298
|
* per challenge kind: `aws_waf` was measured portable across residential leases
|
|
283
299
|
* on buyee, while `cf_clearance` is unmeasured here and treated as scoped to the
|
|
284
300
|
* identity that produced it. The provider attaches the returned cookies to its
|
|
285
|
-
* own requests.
|
|
301
|
+
* own requests. `cookie_state` solutions carry no values: SBSD state lives only
|
|
302
|
+
* in the engine-owned bound jar, and a 2xx payload POST is not proof of a solve;
|
|
303
|
+
* only the next protected GET can verify it (the safe refetch is Phase 2).
|
|
286
304
|
*/
|
|
287
305
|
export type ChallengeSolution = {
|
|
288
306
|
readonly form: "token";
|
|
@@ -298,8 +316,28 @@ export type ChallengeSolution = {
|
|
|
298
316
|
* the upstream cookie's expiry. `expires` takes precedence when both are present.
|
|
299
317
|
*/
|
|
300
318
|
readonly sdkEstimatedExpires?: number;
|
|
319
|
+
} | {
|
|
320
|
+
readonly form: "cookie_state";
|
|
321
|
+
readonly kind: "akamai_sbsd";
|
|
322
|
+
readonly outcome: "payload_accepted";
|
|
323
|
+
/** Always false until a later protected GET verifies the bound jar (Phase 2). */
|
|
324
|
+
readonly verified: false;
|
|
325
|
+
/** Name only; the identity-bound cookie value remains in the engine-owned jar. */
|
|
326
|
+
readonly stateCookieName: "sbsd_o" | "bm_so";
|
|
327
|
+
/** Epoch seconds observed on the updated state cookie, when supplied upstream. */
|
|
328
|
+
readonly expires?: number;
|
|
301
329
|
};
|
|
302
|
-
|
|
330
|
+
/**
|
|
331
|
+
* `akamai_sbsd` declarations require a transport-owned client profile because the
|
|
332
|
+
* solve runs on the provider's admitted session. Other kinds may omit it.
|
|
333
|
+
*/
|
|
334
|
+
export type ProviderResolverConfig = {
|
|
335
|
+
/** Optional ordered override for the SDK-owned vendor fallback chain. */
|
|
336
|
+
readonly vendors?: readonly ProviderResolverVendor[];
|
|
337
|
+
/** Challenge kinds this provider is permitted to request. */
|
|
338
|
+
readonly kinds: readonly Exclude<ProviderChallengeKind, "akamai_sbsd">[];
|
|
339
|
+
readonly clientProfile?: string;
|
|
340
|
+
} | {
|
|
303
341
|
/** Optional ordered override for the SDK-owned vendor fallback chain. */
|
|
304
342
|
readonly vendors?: readonly ProviderResolverVendor[];
|
|
305
343
|
/** Challenge kinds this provider is permitted to request. */
|
|
@@ -310,8 +348,8 @@ export interface ProviderResolverConfig {
|
|
|
310
348
|
* challenge is served, while a Safari profile is admitted. Provider-declared
|
|
311
349
|
* because only the provider knows its upstream's admission rule.
|
|
312
350
|
*/
|
|
313
|
-
readonly clientProfile
|
|
314
|
-
}
|
|
351
|
+
readonly clientProfile: string;
|
|
352
|
+
};
|
|
315
353
|
export type SttAudioInput = {
|
|
316
354
|
kind: "base64";
|
|
317
355
|
data: string;
|
|
@@ -774,6 +812,13 @@ export type StealthProfileSelection = {
|
|
|
774
812
|
browser: "safari";
|
|
775
813
|
os?: "macos" | "ios";
|
|
776
814
|
};
|
|
815
|
+
/** Provider-owned opt-ins layered onto the transport-owned browser/OS profile. */
|
|
816
|
+
export type ProviderStealthConfig = StealthProfileSelection & {
|
|
817
|
+
readonly challengeDetection?: {
|
|
818
|
+
/** Classify SBSD responses even when no resolver is declared (detect/report mode). */
|
|
819
|
+
readonly akamaiSbsd?: boolean;
|
|
820
|
+
};
|
|
821
|
+
};
|
|
777
822
|
export type BrowserEngine = "playwright-stealth" | "nodriver" | "selenium-uc";
|
|
778
823
|
export interface BrowserOptions {
|
|
779
824
|
headless?: boolean;
|
|
@@ -1027,9 +1072,25 @@ export interface HttpRedirectPolicy {
|
|
|
1027
1072
|
export type HttpRedirectFailureReason = Exclude<RedirectRunReason, "completed">;
|
|
1028
1073
|
export type HttpMethod = "HEAD" | "head" | "GET" | "get" | "POST" | "post" | "PUT" | "put" | "DELETE" | "delete" | "OPTIONS" | "options" | "TRACE" | "trace" | "PATCH" | "patch";
|
|
1029
1074
|
export interface StealthFetchOptions extends Omit<RequestOptions, "redirectPolicy" | "headers"> {
|
|
1075
|
+
/**
|
|
1076
|
+
* Defaults to true. Set to false when callers need to inspect upstream
|
|
1077
|
+
* non-2xx bodies themselves instead of converting them to TransportError.
|
|
1078
|
+
*
|
|
1079
|
+
* Documented exception: a non-2xx response that an opted-in SDK challenge
|
|
1080
|
+
* detector classified (`response.challenge` is set) is returned, not thrown,
|
|
1081
|
+
* whatever this flag says, so the classification stays observable. Callers
|
|
1082
|
+
* that need the failure semantics check `response.challenge` first.
|
|
1083
|
+
*/
|
|
1084
|
+
throwOnHttpError?: boolean;
|
|
1030
1085
|
/**
|
|
1031
1086
|
* Request headers. Array values and case-insensitive duplicate names are
|
|
1032
1087
|
* combined in caller order using `", "`, matching Chrome's Fetch behavior.
|
|
1088
|
+
*
|
|
1089
|
+
* The stealth transport owns `host`, `connection`, `user-agent`, `sec-ch-ua`,
|
|
1090
|
+
* `sec-ch-ua-mobile`, `sec-ch-ua-platform`, `accept-encoding`, and every
|
|
1091
|
+
* `sec-fetch-*` header. Supplying any of them throws
|
|
1092
|
+
* `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` at request time; declare the request
|
|
1093
|
+
* shape with `stealth.requestClass` instead.
|
|
1033
1094
|
*/
|
|
1034
1095
|
headers?: Record<string, string | string[]>;
|
|
1035
1096
|
method?: HttpMethod;
|
|
@@ -1049,13 +1110,26 @@ export interface StealthFetchOptions extends Omit<RequestOptions, "redirectPolic
|
|
|
1049
1110
|
*/
|
|
1050
1111
|
proxyAttemptOffset?: number;
|
|
1051
1112
|
/**
|
|
1052
|
-
* SDK-specific stealth controls
|
|
1053
|
-
* referrer, and content
|
|
1113
|
+
* SDK-specific stealth controls, including a per-request browser and/or OS
|
|
1114
|
+
* override. Standard HTTP metadata such as language, referrer, and content
|
|
1115
|
+
* type belongs in `headers`.
|
|
1054
1116
|
*/
|
|
1055
1117
|
stealth?: StealthProfileSelection & {
|
|
1056
|
-
/**
|
|
1057
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* Declare the Chrome request class when it cannot be inferred from the
|
|
1120
|
+
* method (`POST` infers `post`, everything else `navigation`). This is the
|
|
1121
|
+
* replacement for hand-set `Sec-Fetch-*` headers, which the transport
|
|
1122
|
+
* rejects; it drives `Sec-Fetch-Mode`/`Dest`/`User`, `Accept`, `Priority`,
|
|
1123
|
+
* and `Upgrade-Insecure-Requests`.
|
|
1124
|
+
*/
|
|
1058
1125
|
requestClass?: "navigation" | "xhr" | "post";
|
|
1126
|
+
/**
|
|
1127
|
+
* Whether a `navigation` request carries a user gesture and therefore
|
|
1128
|
+
* `Sec-Fetch-User: ?1` (default `true`). Set `false` for script-driven
|
|
1129
|
+
* navigations such as `location.replace`, which Chrome sends without it.
|
|
1130
|
+
* Ignored for the `xhr` and `post` request classes.
|
|
1131
|
+
*/
|
|
1132
|
+
userActivation?: boolean;
|
|
1059
1133
|
/**
|
|
1060
1134
|
* Use only for proxy products that terminate CONNECT with a private CA
|
|
1061
1135
|
* instead of tunneling the origin certificate chain.
|
|
@@ -1110,6 +1184,8 @@ export interface DeclarativeStealthResponse {
|
|
|
1110
1184
|
headers: Record<string, string>;
|
|
1111
1185
|
rawHeaders: [string, string][];
|
|
1112
1186
|
body: string;
|
|
1187
|
+
/** Present only when an opted-in SDK detector returned an unresolved challenge response. */
|
|
1188
|
+
challenge?: StealthChallengeClassification;
|
|
1113
1189
|
httpVersion?: string;
|
|
1114
1190
|
tlsInfo?: {
|
|
1115
1191
|
protocol?: string;
|
|
@@ -1122,6 +1198,20 @@ export interface DeclarativeStealthResponse {
|
|
|
1122
1198
|
bytes(): Promise<Uint8Array>;
|
|
1123
1199
|
}
|
|
1124
1200
|
export type StealthResponse = DeclarativeStealthResponse;
|
|
1201
|
+
/** Why a detected challenge response was returned to the caller instead of being solved and refetched. */
|
|
1202
|
+
export type StealthChallengeClassification = {
|
|
1203
|
+
readonly challenge: Extract<ProviderChallenge, {
|
|
1204
|
+
readonly kind: "akamai_sbsd";
|
|
1205
|
+
}>;
|
|
1206
|
+
/**
|
|
1207
|
+
* `resolver_unavailable`: detect-only provider or no solver configured.
|
|
1208
|
+
* `replay_required`: the request is not a plain GET, so it is never solved or replayed
|
|
1209
|
+
* automatically. `solve_failed`: a concurrent request on the same session owned the
|
|
1210
|
+
* solve and it rejected (that owner receives the error). `challenge_persisted`: the
|
|
1211
|
+
* single refetch after a solve was challenged again.
|
|
1212
|
+
*/
|
|
1213
|
+
readonly outcome: "resolver_unavailable" | "replay_required" | "solve_failed" | "challenge_persisted";
|
|
1214
|
+
};
|
|
1125
1215
|
export type RequestWithMethodOptions = RequestOptions & {
|
|
1126
1216
|
method?: string;
|
|
1127
1217
|
body?: unknown;
|
|
@@ -1152,6 +1242,12 @@ export interface StealthRedirectRunResult {
|
|
|
1152
1242
|
}
|
|
1153
1243
|
export interface StealthSession {
|
|
1154
1244
|
fetch(url: string, options?: StealthFetchOptions): Promise<StealthResponse>;
|
|
1245
|
+
/**
|
|
1246
|
+
* Explicitly solve and replay one unsafe request previously classified as
|
|
1247
|
+
* `replay_required`. The original method, URL, headers, and body bytes are
|
|
1248
|
+
* retained by this session and may be replayed exactly once.
|
|
1249
|
+
*/
|
|
1250
|
+
replayChallenged(response: StealthResponse): Promise<StealthResponse>;
|
|
1155
1251
|
cookies: StealthSessionCookies;
|
|
1156
1252
|
redirects: {
|
|
1157
1253
|
run(options: StealthRedirectRunOptions): Promise<StealthRedirectRunResult>;
|
|
@@ -2054,7 +2150,7 @@ export interface ProviderDefinition<TContext = ProviderContext> {
|
|
|
2054
2150
|
http?: Record<string, never> | true;
|
|
2055
2151
|
allowedHosts?: string[];
|
|
2056
2152
|
native?: NativeProviderConfig;
|
|
2057
|
-
stealth?:
|
|
2153
|
+
stealth?: ProviderStealthConfig;
|
|
2058
2154
|
proxy?: ProviderProxyConfig;
|
|
2059
2155
|
ocr?: ProviderOcrConfig;
|
|
2060
2156
|
stt?: ProviderSttConfig;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.2.0-beta.
|
|
2
|
+
"version": "2.2.0-beta.60",
|
|
3
3
|
"name": "@apifuse/provider-sdk",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"format": "biome format --write",
|
|
111
111
|
"type-check": "tsc --noEmit",
|
|
112
112
|
"test": "bun test",
|
|
113
|
+
"bench:redaction": "bun scripts/benchmark-request-redaction.ts",
|
|
113
114
|
"check": "bun run lint && bun run type-check && bun run lint:deprecated && bun run lint:test-typesafety && bun run build",
|
|
114
115
|
"api:update": "bun run build && bun scripts/api-reports.ts update",
|
|
115
116
|
"api:check": "bun run build && bun scripts/api-reports.ts check",
|
|
@@ -97,9 +97,21 @@ The `POST /v1/{operation}` body is a request envelope:
|
|
|
97
97
|
- `requestId` is required and can be any unique local debugging string.
|
|
98
98
|
- `input` contains the operation input shape.
|
|
99
99
|
- `headers` is optional.
|
|
100
|
-
- `
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
- `connectionId` is optional connection identity only and does not include
|
|
101
|
+
credentials. The gateway sends it for `optional` connection mode, and only
|
|
102
|
+
when the caller supplied a connection that passed authorization. It is
|
|
103
|
+
exposed as `ctx.request.connectionId`.
|
|
104
|
+
- `connection` is optional credential-bearing connection data. The gateway
|
|
105
|
+
sends it for `required` connection mode; omit it for no-auth/public (`none`
|
|
106
|
+
mode) operations. For local debugging, pass
|
|
107
|
+
`{ "id", "mode", "secrets", "metadata", "externalRef" }` with local-only
|
|
108
|
+
secret values.
|
|
109
|
+
|
|
110
|
+
The gateway sends only `connection` for `required` mode, only `connectionId`
|
|
111
|
+
for `optional` mode (and nothing when the caller passed no connection), and
|
|
112
|
+
neither field for `none` mode. If a malformed or manually constructed envelope
|
|
113
|
+
contains both, a non-empty nested `connection.id` takes precedence over the
|
|
114
|
+
top-level `connectionId`; an empty string is treated as absent.
|
|
103
115
|
|
|
104
116
|
Structured errors return an `error` object with `code`, `message`,
|
|
105
117
|
`requestId`, and optional `details`; validation failures include field paths in
|