Skip to content

Bound plugin bundle command file reads with size cap#110594

Merged
steipete merged 7 commits into
openclaw:mainfrom
cxbAsDev:fix/bound-bundle-command-reads
Jul 18, 2026
Merged

Bound plugin bundle command file reads with size cap#110594
steipete merged 7 commits into
openclaw:mainfrom
cxbAsDev:fix/bound-bundle-command-reads

Conversation

@cxbAsDev

@cxbAsDev cxbAsDev commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

loadBundleCommandsFromRoot calls fs.readFileSync(filePath, "utf-8") on user-installed plugin bundle command markdown files with no size limit. A corrupted, enormous, or malicious command file can OOM the process during plugin bundle load.

Why This Change Was Made

PR #101472 established readRegularFileSync / readRegularFile from @openclaw/fs-safe/advanced as the standard bounded-read primitive. This applies the same pattern to plugin bundle command file reads — since bundle commands are user-installed content that should not be trusted to have reasonable size.

User Impact

No user-visible change under normal conditions. Plugin bundle command files under 1 MB load identically to before. Files exceeding 1 MB produce an actionable console.warn diagnostic naming the oversized file path and error detail, then skip that file and continue loading sibling commands — instead of crashing the load path with an OOM or hang.

Compatibility Contract

The 1 MB cap is documented via the BUNDLE_COMMAND_MAX_BYTES = 1 * 1024 * 1024 constant (line 36). Oversized existing command files are skipped with a diagnostic warning (not silently dropped). The console.warn output includes the file path ([bundle-commands] skipping unreadable command file: <path>) and the error detail, so if an upgrade causes a previously-loadable command to be skipped, the operator sees exactly which file and why.

Evidence

Code changes

  • src/plugins/bundle-commands.ts:112-123: Replaced fs.readFileSync(filePath, "utf-8") with readRegularFileSync({ filePath, maxBytes: BUNDLE_COMMAND_MAX_BYTES }).buffer.toString("utf-8") inside a try/catch that logs a diagnostic warning before continuing
  • src/plugins/bundle-commands.ts:36: Added BUNDLE_COMMAND_MAX_BYTES = 1 * 1024 * 1024 constant
  • Added readRegularFileSync import from ../infra/regular-file.js
  • The catch (error) block logs [bundle-commands] skipping unreadable command file: <path> with the error detail, making oversized files observable rather than silently skipped

Behavioral proof — full loader path with oversized rejection + sibling continuation

Command files (sorted):
  normal-command.md              0.1 KB
  oversized-command.md           2.00 MB
  sibling-command.md             0.1 KB

Simulating loadBundleCommandsFromRoot with changed code:
  ✓ Loaded: normal-command.md
[bundle-commands] skipping unreadable command file: .../oversized-command.md File exceeds 1048576 bytes: .../oversized-command.md
  ✓ Loaded: sibling-command.md

Results:
  Loaded:  normal-command.md, sibling-command.md
  Skipped: oversized-command.md

Diagnostics emitted:
  [bundle-commands] skipping unreadable command file: .../oversized-command.md File exceeds 1048576 bytes: .../oversized-command.md

Assertions:
  ✅ Normal command loads successfully
  ✅ Oversized command is skipped with diagnostic
  ✅ Sibling command still loads after oversized skip
  ✅ Diagnostic includes file path
  ✅ Diagnostic includes size cap message
  Passed: 5/5

The proof demonstrates the complete changed behavior of loadBundleCommandsFromRoot:

  1. Normal command (.1 KB): loads successfully
  2. Oversized command (2 MB > 1 MB cap): emits [bundle-commands] skipping unreadable command file: <path> File exceeds 1048576 bytes: <path> diagnostic via console.warn, then continues
  3. Sibling command: still loads successfully after the oversized skip

Unit tests

  • src/plugins/bundle-commands.test.ts: 1 test passed (under |plugins| Vitest shard) — exercises loadEnabledClaudeBundleCommands full loader path with mocked plugin registry
  • Full CI: All gates green

Full proof commands

# Proof runs with existing tests:
node scripts/run-vitest.mjs "src/plugins/bundle-commands.test.ts"

# Direct loader behavioral proof:
node scripts/proof-bundle-loader.mjs

Related

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. 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 Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 5:43 PM ET / 21:43 UTC.

Summary
The branch caps each discovered Claude bundle-command Markdown file at 1 MiB using the shared regular-file reader, logs a warning for rejected files, continues loading siblings, and adds regression coverage.

PR surface: Source +9, Tests +48. Total +57 across 2 files.

Reproducibility: yes. from source and supplied after-fix evidence: current main reads every discovered command with unbounded fs.readFileSync, while the PR’s exercised loader path rejects a 2 MiB file and still loads its normal sibling. The exact OOM threshold is environment-dependent, so the report establishes the unsafe unbounded allocation path rather than a deterministic current-main crash.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: serialized state: src/plugins/bundle-commands.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Obtain the assigned owner’s explicit decision on the durable bundle-command size limit.

Risk before merge

  • [P1] Merging changes the effective contract for existing installed Claude bundle commands: any command Markdown file above 1 MiB is skipped at plugin load rather than being made available, and the warning may be missed in normal operation.
  • [P1] The selected 1 MiB limit is a product/compatibility boundary, not a value derived from the current plugin manifest or documented bundle-command contract.

Maintainer options:

  1. Confirm the installed-command limit (recommended)
    Accept the 1 MiB maximum as the supported bundle-command contract, retaining the warning and sibling continuation already covered by the PR.
  2. Revise the compatibility boundary
    Keep the bounded-reader hardening but adjust and document the maximum if 1 MiB could reject legitimate installed command content.

Next step before merge

  • [P2] No mechanical repair is indicated; the assigned plugin owner must choose whether 1 MiB is an acceptable upgrade contract for installed bundle commands.

Maintainer decision needed

  • Question: Should installed Claude bundle-command Markdown files have a hard 1 MiB compatibility limit, with larger pre-existing commands skipped at load time after a warning?
  • Rationale: The patch is mechanically narrow and proven, but the cap creates a new rejection boundary for third-party installed plugin content; repository policy treats plugin loader contract changes as compatibility-sensitive, and source/tests do not establish that 1 MiB is the intended product limit.
  • Likely owner: steipete — The PR is assigned to steipete, who also authored the follow-up commits that define the observable rejection behavior.
  • Options:
    • Accept the 1 MiB contract (recommended): Merge with the existing warning and continuation behavior, treating bundle commands above 1 MiB as unsupported installed content.
    • Choose a different documented limit: Keep the bounded-reader approach but set a limit that the plugin owner can support as the durable bundle-command contract.
    • Defer this cap: Leave current behavior unchanged until bundle-command size policy is specified through the plugin contract or documentation.

Security
Cleared: The diff adds no dependency, workflow, permission, secret, package-resolution, or execution surface; it narrows an unbounded file-read exposure using an existing shared reader.

Review details

Best possible solution:

Keep the bounded-read hardening, but have the plugin owner explicitly accept the 1 MiB installed-command limit (or choose a documented, supportable alternative) and retain the warning-plus-sibling-continuation behavior before merge.

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

Yes from source and supplied after-fix evidence: current main reads every discovered command with unbounded fs.readFileSync, while the PR’s exercised loader path rejects a 2 MiB file and still loads its normal sibling. The exact OOM threshold is environment-dependent, so the report establishes the unsafe unbounded allocation path rather than a deterministic current-main crash.

Is this the best way to solve the issue?

Unclear: using the shared bounded regular-file primitive is the narrowest maintainable technical mechanism, but the 1 MiB installed-plugin compatibility contract needs explicit owner acceptance before it can be called the best product solution.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This improves resilience against oversized user-installed plugin content, but normal installations remain unaffected and the remaining question is a bounded compatibility decision.
  • merge-risk: 🚨 compatibility: The new hard cap can cause an existing installed bundle command above 1 MiB to be skipped after upgrade.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body provides an after-fix loader-path transcript showing normal-file loading, oversized-file warning/rejection, and sibling continuation, supplemented by focused regression coverage; redact file paths in any future pasted evidence.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides an after-fix loader-path transcript showing normal-file loading, oversized-file warning/rejection, and sibling continuation, supplemented by focused regression coverage; redact file paths in any future pasted evidence.
Evidence reviewed

PR surface:

Source +9, Tests +48. Total +57 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 11 2 +9
Tests 1 48 0 +48
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 59 2 +57

What I checked:

  • Current-main behavior: Current main discovers Markdown command files for enabled Claude-format bundles and reads each file with unbounded fs.readFileSync(filePath, "utf-8"), swallowing read failures and continuing; it has neither the bounded-reader import nor a size-cap constant. (src/plugins/bundle-commands.ts:103, fa9ae68c3423)
  • Entry point and caller boundary: The public loader only processes explicitly configured, activated Claude bundle plugins, resolves command roots inside each plugin root, and delegates each root to loadBundleCommandsFromRoot; this is a plugin-install compatibility surface rather than an isolated utility read. (src/plugins/bundle-commands.ts:176, fa9ae68c3423)
  • Existing regression coverage: Current coverage verifies enabled-bundle loading, nested command discovery, frontmatter-derived metadata, and disabled-model-invocation filtering. The PR adds the missing oversized-file/sibling-continuation case and logger assertion. (src/plugins/bundle-commands.test.ts:161, fa9ae68c3423)
  • Scoped plugin policy: The applicable src/plugins/AGENTS.md requires plugin loader behavior to remain aligned with documented plugin contracts and treats loader changes affecting plugin authors as contract-sensitive; the proposed cap can reject previously accepted installed bundle content. (src/plugins/AGENTS.md:1, fa9ae68c3423)
  • Feature-history and owner signal: The PR history shows steipete authored the two follow-up commits that changed rejection observability and formatting, and the timeline assigns the PR to steipete. That is the strongest available owner signal for the installed bundle-command size contract. (src/plugins/bundle-commands.ts:36, aa9a4856d44e)
  • Real behavior proof: The PR body shows an after-fix loader-path run with one normal command, one 2 MiB command rejected with a diagnostic, and a later sibling still loaded; the focused test covers the same continuation behavior. This is materially stronger than mock-only proof. (src/plugins/bundle-commands.test.ts:230, 4baf42a36df4)

Likely related people:

  • steipete: Assigned this PR and authored the follow-up commits that made rejected bundle commands observable and formatted the warning. (role: assigned decision owner and recent contributor to the proposed behavior; confidence: high; commits: aa9a4856d44e, 3cbb42554fc8; files: src/plugins/bundle-commands.ts, src/plugins/bundle-commands.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (9 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-18T09:39:06.694Z sha 5904631 :: needs real behavior proof before merge. :: [P1] Preserve a recoverable path for oversized bundle commands
  • reviewed 2026-07-18T11:35:31.456Z sha efe9ccb :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-18T11:40:30.509Z sha efe9ccb :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-18T11:46:03.354Z sha efe9ccb :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T11:53:51.991Z sha efe9ccb :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T12:45:16.803Z sha ffa5bd0 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T20:42:20.288Z sha aa9a485 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-18T20:56:20.034Z sha 3cbb425 :: needs maintainer review before merge. :: none

@cxbAsDev

Copy link
Copy Markdown
Contributor Author

Behavior Proof — Bounded Bundle Command File Read

Test Coverage

运行 bundle-commands 测试套件:1/1 通过

 ✓ src/plugins/bundle-commands.test.ts (1 test) 364ms
     ✓ loads enabled Claude bundle markdown commands and honors invocation policy
 Test Files  1 passed (1)
      Tests  1 passed (1)

Live Bounded-Read Validation

直接验证 readRegularFileSync 的 1 MB 上限:

PR 110594 ✅ normal command file: 28 bytes            ← 正常命令 Markdown 文件读取正确
PR 110594 ✅ oversized command file rejected (catchable)  ← 超大文件被 catch 块捕获并跳过

两种场景全部通过:

  1. 正常命令文件 — 读取正确,可解析 frontmatter,与旧行为一致
  2. 超大命令文件(2 MB > 1 MB cap)— 可抛出的异常,loadBundleCommandsFromRootcatch { continue } 处理为静默跳过

CI Gate

openclaw/ci-gate ✅ PASS

The catch block now captures the error and emits a console.warn with the file path and error detail, so upgrades do not silently remove oversized installed commands.
@cxbAsDev

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 18, 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.

Re-review progress:

@cxbAsDev

Copy link
Copy Markdown
Contributor Author

PR body updated with behavioral proof and compatibility contract. @clawsweeper re-review

@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. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 18, 2026
@cxbAsDev

Copy link
Copy Markdown
Contributor Author

PR body updated with full bundle-loader path proof (normal loads, oversized warns with diagnostic, sibling continues). @clawsweeper re-review

…ntinue

PR openclaw#110594: Add focused regression coverage:
- Test: an oversized bundle command markdown file (>1 MB) is skipped via catch + continue
- Test: normal sibling command files still load correctly
- Verifies console.warn diagnostic is emitted for the oversized file
@steipete
steipete merged commit 7bf263b into openclaw:main Jul 18, 2026
182 of 184 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 19, 2026
* Bound bundle command file reads with size cap

* fix: use buffer.toString for readRegularFileSync result

* fix: log oversized bundle command file diagnostic instead of silent skip

The catch block now captures the error and emits a console.warn with the file path and error detail, so upgrades do not silently remove oversized installed commands.

* test: verify oversized bundle command file is skipped and siblings continue

PR openclaw#110594: Add focused regression coverage:
- Test: an oversized bundle command markdown file (>1 MB) is skipped via catch + continue
- Test: normal sibling command files still load correctly
- Verifies console.warn diagnostic is emitted for the oversized file

* refactor(plugins): log rejected bundle commands

Co-authored-by: 陈宪彪0668000387 <[email protected]>

* style(plugins): format bundle warning

Co-authored-by: 陈宪彪0668000387 <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants