Skip to content

fix(agents): bound model catalog JSON response reads for bedrock-mantle and huggingface#96480

Closed
hugenshen wants to merge 1 commit into
openclaw:mainfrom
hugenshen:fix/bound-model-catalog-json-responses
Closed

fix(agents): bound model catalog JSON response reads for bedrock-mantle and huggingface#96480
hugenshen wants to merge 1 commit into
openclaw:mainfrom
hugenshen:fix/bound-model-catalog-json-responses

Conversation

@hugenshen

@hugenshen hugenshen commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Amazon Bedrock Mantle model discovery (/v1/models) and HuggingFace model discovery (/models) both parse successful HTTP responses with an unbounded await response.json(). On Node, response.json() buffers the entire body before parsing, so a provider-controlled endpoint can stream an arbitrarily large or never-ending JSON payload and push the discovery path into memory pressure, hangs, or OOM.

That matters here because both inputs are untrusted:

  • Mantle discovery reads from an operator-selected AWS endpoint.
  • HuggingFace discovery reads from an external router endpoint.

This PR closes the same response-body gap already fixed for sibling catalog surfaces in #95418, #95420, and #96027, but for the two remaining bundled provider discovery reads still using unbounded success-path JSON parsing on current main.

Changes

  • Replace the Mantle success-path read in discoverMantleModels with readProviderJsonResponse(response, "bedrock-mantle.model-discovery").
  • Replace the HuggingFace success-path read in discoverHuggingfaceModels with readProviderJsonResponse(response, "huggingface.model-discovery").
  • Reuse the existing shared provider JSON reader instead of introducing a new provider-local helper. That helper already enforces the standard 16 MiB JSON byte cap and wraps malformed JSON with a caller label.
  • Preserve the existing fail-soft behavior around the changed call sites: Mantle still falls back to cached models or [] on discovery failure, and HuggingFace still falls back to the static bundled catalog on discovery failure.
  • Update Mantle discovery tests to return real Response objects instead of plain { json() {} } mocks so the changed path uses the same body/stream API shape as production.
  • Add scripts/proof-mantle-catalog-bound.mjs, an affected-path proof harness that drives the real discoverMantleModels entry point against a local streaming server via injected fetchFn.

Real behavior proof

  • Behavior addressed: a successful catalog response with no Content-Length must not be buffered whole if it exceeds the JSON safety cap; the read must stop near the cap, cancel the stream, and fail soft through the existing discovery fallback paths instead of draining the full body.
  • Affected entry point: discoverMantleModels in extensions/amazon-bedrock-mantle/discovery.ts — the exact success-path function changed by this PR.
  • Real environment tested: Node v22, local node:http server on 127.0.0.1, streaming ~24 MiB catalog JSON in 64 KiB chunks with no Content-Length, injected via the production fetchFn parameter.
  • Exact proof shape:
    1. Call discoverMantleModels({ fetchFn }) against /huge → fail-soft fallback ([] when cache cold) plus early stream stop near the 16 MiB cap.
    2. Call against /small → normal catalog parses with model id intact.
    3. Warm cache from /small, expire refresh window, hit /huge → bounded overflow falls back to cached models.
  • Evidence after fix:
    • Oversized stream: discoverMantleModels returned [] when cache was cold.
    • Early cancel: server sent 17,104,912 bytes, near the 16 MiB cap rather than the full ~24 MiB body.
    • Happy path: small catalog parsed into exactly one model with id anthropic.claude-sonnet-4-6.
    • Cached fallback: after warm cache + stale refresh, oversized stream returned the cached model instead of hanging or OOM.
  • Suite coverage: pnpm test extensions/amazon-bedrock-mantle/discovery.test.ts passed 29/29 tests, including updated Mantle discovery tests that now use real Response objects on the success path.
  • HuggingFace note: discoverHuggingfaceModels has no injectable fetch seam, but uses the same readProviderJsonResponse call-site pattern; Mantle affected-path proof exercises the bounded read + fail-soft catch path through the real changed discovery function.
  • What was not tested: live Mantle or HuggingFace network endpoints; direct discoverHuggingfaceModels harness against a local streaming server.

Evidence

$ node --import tsx scripts/proof-mantle-catalog-bound.mjs

[case 1] discoverMantleModels + oversized /v1/models stream, no Content-Length
  ok: fail-soft fallback returns empty catalog when body exceeds cap — true
  ok: server stopped near 16 MiB cap, not full ~24 MiB (sent 17104912) — true
  ok: server did not stream the entire hostile body (sent 17104912 < 22649242) — true

[case 2] discoverMantleModels + normal small catalog still parses
  ok: small catalog returns one model — true
  ok: model id intact (anthropic.claude-sonnet-4-6) — true

[case 3] discoverMantleModels uses cached models after oversized stream when cache warm
  ok: warm cache populated from small catalog — true
  ok: oversized stream falls back to cached models — true
  ok: cached model id preserved (anthropic.claude-sonnet-4-6) — true
  ok: stale refresh re-fetched before bounded overflow fallback — true

ALL PROOF ASSERTIONS PASSED
$ pnpm test extensions/amazon-bedrock-mantle/discovery.test.ts -- --reporter=verbose

Test Files  1 passed (1)
     Tests  29 passed (29)

Label: security

AI-assisted.

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Keep open: current main still has the HuggingFace unbounded success-body read, and this branch is a narrow, plausible fix with no blocking code finding. The remaining blocker is merge readiness: the current PR body proof is stale to the dropped Mantle half and does not directly demonstrate the current HuggingFace-only change.

Canonical path: Close this PR as superseded by #99961.

So I’m closing this here and keeping the remaining discussion on #99961.

Review details

Best possible solution:

Close this PR as superseded by #99961.

Do we have a high-confidence way to reproduce the issue?

Yes by source inspection: current main sends successful HuggingFace discovery responses to native response.json() with no byte cap. I did not run an OOM repro, but the affected path and bounded-reader replacement are clear.

Is this the best way to solve the issue?

Yes for the code shape: using the existing plugin-sdk provider JSON reader is the narrow owner-boundary fix and preserves the existing static-catalog fallback. Merge readiness is not complete until the current HuggingFace-only head has direct real behavior proof or a maintainer proof override.

Security review:

Security review cleared: The diff narrows an untrusted provider-response DoS boundary and does not add dependencies, workflows, permissions, package-resolution changes, or secret handling.

AGENTS.md: found and applied where relevant.

What I checked:

  • linked superseding PR: fix(bedrock): bound Mantle model discovery fetches #99961 (fix(bedrock): bound Mantle model discovery fetches) is merged at 2026-07-06T01:21:01Z.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • steipete: Peter Steinberger authored the provider model-helper move that introduced extensions/huggingface/models.ts and later normalized HuggingFace discovery timeout/ref behavior. (role: feature-history owner; confidence: high; commits: c28e76c490a3, 5ac07b8ef086, 6c1433a3c0ec; files: extensions/huggingface/models.ts, extensions/huggingface/provider-catalog.ts, extensions/huggingface/onboard.ts)
  • Alix-007: The PR body explicitly follows recently merged bounded-response hardening work by Alix-007 across sibling provider catalog surfaces. (role: adjacent hardening contributor; confidence: medium; commits: 06ca1235efb7, 3da4280caf4c, d1c2934d0d17; files: src/agents/model-scan.ts, src/agents/embedded-agent-runner/openrouter-model-capabilities.ts, extensions/ollama/src/provider-models.ts)
  • zhangguiping-xydt: They authored the merged Mantle bounded-discovery PR that superseded the dropped half of this branch and used the same response-boundary pattern. (role: recent adjacent contributor; confidence: medium; commits: a77ea59ce73c; files: extensions/amazon-bedrock-mantle/discovery.ts, extensions/amazon-bedrock-mantle/discovery.test.ts)

Codex review notes: model internal, reasoning high; reviewed against 92cfceac0924.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 24, 2026
@hugenshen

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 24, 2026
@nocodet666

Copy link
Copy Markdown

@sallyom here is a bounded response pr ready to merge, I'd really appreciate it if you could take a look!

@hugenshen

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@openclaw-barnacle openclaw-barnacle Bot added scripts Repository scripts size: M and removed size: S labels Jun 28, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 28, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

The Amazon Bedrock Mantle half of this PR is now superseded by #99961, landed as a77ea59ce73cca21b3897bbf313abee2df50dd23 with a 4 MiB provider-catalog cap, 30-second abort budget, response-body release, and normal/oversized/stalled proof.

Please drop the Mantle production/test changes when refreshing this branch. The Hugging Face model-catalog bound remains distinct and should stay independently reviewable. Thanks for the broader response-boundary work.

…ider reader

Drop the Mantle half of the original combined change; that surface is
already covered by openclaw#99961 on main. Keep only the HuggingFace catalog
success-path bound read using readProviderJsonResponse.

Co-authored-by: Cursor <[email protected]>
@hugenshen
hugenshen force-pushed the fix/bound-model-catalog-json-responses branch from 04c9d94 to cbd201f Compare July 6, 2026 17:42
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 6, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Superseded by #101079, now landed on main as a464620141f6.

The landed fix consumes the Hugging Face model-discovery JSON through the shared 16 MiB bounded reader, cancels overflow, preserves the static-catalog fallback, and includes overflow-cleanup plus valid-response coverage. Exact-SHA release gate 28826762727 passed.

Thank you @hugenshen for working on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: huggingface merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants