


























@@ -3,7 +3,10 @@ import { createHash } from "node:crypto";
33import fs from "node:fs/promises";
44import os from "node:os";
55import path from "node:path";
6-import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit";
6+import {
7+readResponseTextSnippet,
8+readResponseWithLimit,
9+} from "@openclaw/media-core/read-response-with-limit";
710import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion";
811import {
912normalizeLowercaseStringOrEmpty,
@@ -20,6 +23,12 @@ const DEFAULT_CLAWHUB_URL = "https://clawhub.ai";
2023const DEFAULT_GITHUB_CODELOAD_URL = "https://codeload.github.com";
2124const DEFAULT_FETCH_TIMEOUT_MS = 30_000;
2225const SKILL_CARD_MAX_BYTES = 256 * 1024;
26+// ClawHub is an external marketplace (untrusted source): bound JSON and error
27+// bodies so a hostile or malfunctioning host cannot exhaust memory by streaming
28+// an unbounded response. Mirrors the error-stream hardening landed in #95108.
29+const CLAWHUB_JSON_MAX_BYTES = 16 * 1024 * 1024;
30+const CLAWHUB_ERROR_BODY_MAX_BYTES = 8 * 1024;
31+const CLAWHUB_ERROR_BODY_MAX_CHARS = 400;
23322433export type ClawHubPackageFamily = "skill" | "code-plugin" | "bundle-plugin";
2534export type ClawHubPackageChannel = "official" | "community" | "private";
@@ -675,8 +684,12 @@ async function clawhubRequest(
675684676685async function readErrorBody(response: Response): Promise<string> {
677686try {
678-const text = (await response.text()).trim();
679-return text || response.statusText || `HTTP ${response.status}`;
687+const snippet = await readResponseTextSnippet(response, {
688+maxBytes: CLAWHUB_ERROR_BODY_MAX_BYTES,
689+maxChars: CLAWHUB_ERROR_BODY_MAX_CHARS,
690+chunkTimeoutMs: DEFAULT_FETCH_TIMEOUT_MS,
691+});
692+return snippet || response.statusText || `HTTP ${response.status}`;
680693} catch {
681694return response.statusText || `HTTP ${response.status}`;
682695}
@@ -720,8 +733,21 @@ async function fetchJson<T>(params: ClawHubRequestParams): Promise<T> {
720733if (!response.ok) {
721734throw await buildClawHubError(response, url, hasToken);
722735}
736+return parseClawHubJsonBody<T>(response, url);
737+}
738+739+async function parseClawHubJsonBody<T>(response: Response, url: URL): Promise<T> {
740+const buffer = await readResponseWithLimit(response, CLAWHUB_JSON_MAX_BYTES, {
741+chunkTimeoutMs: DEFAULT_FETCH_TIMEOUT_MS,
742+onOverflow: ({ size, maxBytes }) =>
743+new Error(
744+`ClawHub ${url.pathname} response exceeded ${maxBytes} bytes (${size} bytes received)`,
745+),
746+onIdleTimeout: ({ chunkTimeoutMs }) =>
747+new Error(`ClawHub ${url.pathname} response stalled after ${chunkTimeoutMs}ms`),
748+});
723749try {
724-return (await response.json()) as T;
750+return JSON.parse(new TextDecoder().decode(buffer)) as T;
725751} catch (cause) {
726752throw new Error(`ClawHub ${url.pathname} returned malformed JSON`, { cause });
727753}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。