

























@@ -17,6 +17,10 @@ const DEFAULT_ENDPOINT_PREFIX = QA_CREDENTIALS_DEFAULT_ENDPOINT_PREFIX;
1717const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
1818const DEFAULT_HTTP_TIMEOUT_MS = 15_000;
1919const DEFAULT_LEASE_TTL_MS = 20 * 60 * 1_000;
20+const DEFAULT_CHUNKED_PAYLOAD_MAX_BYTES = 64 * 1024 * 1024;
21+const DEFAULT_CHUNKED_PAYLOAD_MAX_CHUNKS = 4096;
22+const CHUNKED_PAYLOAD_MAX_BYTES_ENV = "OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_BYTES";
23+const CHUNKED_PAYLOAD_MAX_CHUNKS_ENV = "OPENCLAW_QA_CREDENTIAL_PAYLOAD_MAX_CHUNKS";
2024const RETRY_BACKOFF_MS = [500, 1_000, 2_000, 4_000, 5_000] as const;
2125const RETRYABLE_ACQUIRE_CODES = new Set(["POOL_EXHAUSTED", "NO_CREDENTIAL_AVAILABLE"]);
2226const CHUNKED_PAYLOAD_MARKER = "__openclawQaCredentialPayloadChunksV1";
@@ -55,6 +59,8 @@ type ConvexCredentialBrokerConfig = {
5559httpTimeoutMs: number;
5660leaseTtlMs: number;
5761ownerId: string;
62+payloadMaxBytes: number;
63+payloadMaxChunks: number;
5864payloadChunkUrl: string;
5965releaseUrl: string;
6066role: QaCredentialRole;
@@ -203,14 +209,27 @@ function resolveConvexCredentialBrokerConfig(params: {
203209"OPENCLAW_QA_CREDENTIAL_HTTP_TIMEOUT_MS",
204210DEFAULT_HTTP_TIMEOUT_MS,
205211),
212+payloadMaxBytes: parsePositiveIntegerEnv(
213+params.env,
214+CHUNKED_PAYLOAD_MAX_BYTES_ENV,
215+DEFAULT_CHUNKED_PAYLOAD_MAX_BYTES,
216+),
217+payloadMaxChunks: parsePositiveIntegerEnv(
218+params.env,
219+CHUNKED_PAYLOAD_MAX_CHUNKS_ENV,
220+DEFAULT_CHUNKED_PAYLOAD_MAX_CHUNKS,
221+),
206222acquireUrl: joinQaCredentialEndpoint(baseUrl, endpointPrefix, "acquire"),
207223heartbeatUrl: joinQaCredentialEndpoint(baseUrl, endpointPrefix, "heartbeat"),
208224payloadChunkUrl: joinQaCredentialEndpoint(baseUrl, endpointPrefix, "payload-chunk"),
209225releaseUrl: joinQaCredentialEndpoint(baseUrl, endpointPrefix, "release"),
210226};
211227}
212228213-function parseChunkedPayloadMarker(payload: unknown) {
229+function parseChunkedPayloadMarker(
230+payload: unknown,
231+limits: { maxBytes: number; maxChunks: number },
232+) {
214233if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
215234return null;
216235}
@@ -225,13 +244,19 @@ function parseChunkedPayloadMarker(payload: unknown) {
225244) {
226245throw new Error("Chunked credential payload marker has an invalid chunkCount.");
227246}
247+if (record.chunkCount > limits.maxChunks) {
248+throw new Error(`Chunked credential payload marker exceeds ${limits.maxChunks} chunks.`);
249+}
228250if (
229251typeof record.byteLength !== "number" ||
230252!Number.isInteger(record.byteLength) ||
231253record.byteLength < 0
232254) {
233255throw new Error("Chunked credential payload marker has an invalid byteLength.");
234256}
257+if (record.byteLength > limits.maxBytes) {
258+throw new Error(`Chunked credential payload marker exceeds ${limits.maxBytes} bytes.`);
259+}
235260return {
236261chunkCount: record.chunkCount,
237262byteLength: record.byteLength,
@@ -304,11 +329,15 @@ async function resolveConvexCredentialPayload(params: {
304329fetchImpl: typeof fetch;
305330kind: string;
306331}) {
307-const marker = parseChunkedPayloadMarker(params.acquired.payload);
332+const marker = parseChunkedPayloadMarker(params.acquired.payload, {
333+maxBytes: params.config.payloadMaxBytes,
334+maxChunks: params.config.payloadMaxChunks,
335+});
308336if (!marker) {
309337return params.acquired.payload;
310338}
311339const chunks: string[] = [];
340+let serializedBytes = 0;
312341for (let index = 0; index < marker.chunkCount; index += 1) {
313342const payload = await postConvexBroker({
314343fetchImpl: params.fetchImpl,
@@ -325,10 +354,14 @@ async function resolveConvexCredentialPayload(params: {
325354},
326355});
327356const parsed = convexPayloadChunkSuccessSchema.parse(payload);
357+serializedBytes += Buffer.byteLength(parsed.data, "utf8");
358+if (serializedBytes > marker.byteLength) {
359+throw new Error("Chunked credential payload exceeded declared byteLength.");
360+}
328361chunks.push(parsed.data);
329362}
330363const serialized = chunks.join("");
331-if (Buffer.byteLength(serialized, "utf8") !== marker.byteLength) {
364+if (serializedBytes !== marker.byteLength) {
332365throw new Error("Chunked credential payload length mismatch.");
333366}
334367return JSON.parse(serialized) as unknown;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。