@@ -23,9 +23,8 @@ const DEFAULT_CLAWHUB_URL = "https://clawhub.ai";
2323const DEFAULT_GITHUB_CODELOAD_URL = "https://codeload.github.com" ;
2424const DEFAULT_FETCH_TIMEOUT_MS = 30_000 ;
2525const 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.
26+ // ClawHub is an external marketplace: bound untrusted JSON and error bodies so
27+ // a hostile or malfunctioning host cannot exhaust memory with an endless stream.
2928const CLAWHUB_JSON_MAX_BYTES = 16 * 1024 * 1024 ;
3029const CLAWHUB_ERROR_BODY_MAX_BYTES = 8 * 1024 ;
3130const CLAWHUB_ERROR_BODY_MAX_CHARS = 400 ;
@@ -654,10 +653,7 @@ async function clawhubRequest(
654653 const timeoutMs = resolveClawHubRequestTimeoutMs ( params . timeoutMs ) ;
655654 const controller = new AbortController ( ) ;
656655 const timeout = setTimeout (
657- ( ) =>
658- controller . abort (
659- new Error ( `ClawHub request timed out after ${ timeoutMs } ms` ) ,
660- ) ,
656+ ( ) => controller . abort ( new Error ( `ClawHub request timed out after ${ timeoutMs } ms` ) ) ,
661657 timeoutMs ,
662658 ) ;
663659 try {
@@ -682,12 +678,12 @@ async function clawhubRequest(
682678 }
683679}
684680
685- async function readErrorBody ( response : Response ) : Promise < string > {
681+ async function readErrorBody ( response : Response , timeoutMs ?: number ) : Promise < string > {
686682 try {
687683 const snippet = await readResponseTextSnippet ( response , {
688684 maxBytes : CLAWHUB_ERROR_BODY_MAX_BYTES ,
689685 maxChars : CLAWHUB_ERROR_BODY_MAX_CHARS ,
690- chunkTimeoutMs : DEFAULT_FETCH_TIMEOUT_MS ,
686+ chunkTimeoutMs : resolveClawHubRequestTimeoutMs ( timeoutMs ) ,
691687 } ) ;
692688 return snippet || response . statusText || `HTTP ${ response . status } ` ;
693689 } catch {
@@ -699,8 +695,9 @@ async function buildClawHubError(
699695 response : Response ,
700696 url : URL ,
701697 hasToken : boolean ,
698+ timeoutMs ?: number ,
702699) : Promise < ClawHubRequestError > {
703- let body = await readErrorBody ( response ) ;
700+ let body = await readErrorBody ( response , timeoutMs ) ;
704701 if ( response . status === 429 ) {
705702 const suffix = formatRateLimitSuffix ( response . headers , hasToken ) ;
706703 if ( suffix ) {
@@ -731,14 +728,18 @@ function formatRateLimitSuffix(headers: Headers, hasToken: boolean): string {
731728async function fetchJson < T > ( params : ClawHubRequestParams ) : Promise < T > {
732729 const { response, url, hasToken } = await clawhubRequest ( params ) ;
733730 if ( ! response . ok ) {
734- throw await buildClawHubError ( response , url , hasToken ) ;
731+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
735732 }
736- return parseClawHubJsonBody < T > ( response , url ) ;
733+ return parseClawHubJsonBody < T > ( response , url , params . timeoutMs ) ;
737734}
738735
739- async function parseClawHubJsonBody < T > ( response : Response , url : URL ) : Promise < T > {
736+ async function parseClawHubJsonBody < T > (
737+ response : Response ,
738+ url : URL ,
739+ timeoutMs ?: number ,
740+ ) : Promise < T > {
740741 const buffer = await readResponseWithLimit ( response , CLAWHUB_JSON_MAX_BYTES , {
741- chunkTimeoutMs : DEFAULT_FETCH_TIMEOUT_MS ,
742+ chunkTimeoutMs : resolveClawHubRequestTimeoutMs ( timeoutMs ) ,
742743 onOverflow : ( { size, maxBytes } ) =>
743744 new Error (
744745 `ClawHub ${ url . pathname } response exceeded ${ maxBytes } bytes (${ size } bytes received)` ,
@@ -1016,9 +1017,13 @@ export async function fetchClawHubSkillInstallResolution(params: {
10161017 } ) ;
10171018 const isStructuredBlock = [ 403 , 409 , 410 , 423 ] . includes ( response . status ) ;
10181019 if ( ! response . ok && ! isStructuredBlock ) {
1019- throw await buildClawHubError ( response , url , hasToken ) ;
1020+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
10201021 }
1021- return parseClawHubJsonBody < ClawHubSkillInstallResolutionResponse > ( response , url ) ;
1022+ return parseClawHubJsonBody < ClawHubSkillInstallResolutionResponse > (
1023+ response ,
1024+ url ,
1025+ params . timeoutMs ,
1026+ ) ;
10221027}
10231028
10241029export async function fetchClawHubSkillVerification ( params : {
@@ -1094,7 +1099,7 @@ export async function fetchClawHubSkillCard(params: {
10941099 skipAuth,
10951100 } ) ;
10961101 if ( ! response . ok ) {
1097- throw await buildClawHubError ( response , url , hasToken ) ;
1102+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
10981103 }
10991104 const bytes = await readClawHubResponseBytes ( {
11001105 response,
@@ -1129,7 +1134,7 @@ export async function downloadClawHubPackageArchive(params: {
11291134 fetchImpl : params . fetchImpl ,
11301135 } ) ;
11311136 if ( ! response . ok ) {
1132- throw await buildClawHubError ( response , url , hasToken ) ;
1137+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
11331138 }
11341139 const bytes = await readClawHubResponseBytes ( {
11351140 response,
@@ -1208,7 +1213,7 @@ export async function downloadClawHubPackageArchive(params: {
12081213 fetchImpl : params . fetchImpl ,
12091214 } ) ;
12101215 if ( ! response . ok ) {
1211- throw await buildClawHubError ( response , url , hasToken ) ;
1216+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
12121217 }
12131218 const bytes = await readClawHubResponseBytes ( {
12141219 response,
@@ -1255,7 +1260,7 @@ export async function downloadClawHubSkillArchive(params: {
12551260 } ,
12561261 } ) ;
12571262 if ( ! response . ok ) {
1258- throw await buildClawHubError ( response , url , hasToken ) ;
1263+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
12591264 }
12601265 const bytes = await readClawHubResponseBytes ( {
12611266 response,
@@ -1298,7 +1303,7 @@ export async function downloadClawHubSkillArchiveUrl(params: {
12981303 skipAuth,
12991304 } ) ;
13001305 if ( ! response . ok ) {
1301- throw await buildClawHubError ( response , url , hasToken ) ;
1306+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
13021307 }
13031308 const bytes = await readClawHubResponseBytes ( {
13041309 response,
@@ -1335,7 +1340,7 @@ export async function downloadClawHubGitHubSkillArchive(params: {
13351340 fetchImpl : params . fetchImpl ,
13361341 } ) ;
13371342 if ( ! response . ok ) {
1338- throw await buildClawHubError ( response , url , hasToken ) ;
1343+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
13391344 }
13401345 const bytes = await readClawHubResponseBytes ( {
13411346 response,
@@ -1395,7 +1400,7 @@ export async function reportClawHubSkillInstallTelemetry(params: {
13951400 } ,
13961401 } ) ;
13971402 if ( ! response . ok ) {
1398- throw await buildClawHubError ( response , url , hasToken ) ;
1403+ throw await buildClawHubError ( response , url , hasToken , params . timeoutMs ) ;
13991404 }
14001405}
14011406
0 commit comments