Skip to content

fix(dataexchange): add InboxMaxBytes byte-budget cap to prevent disk DoS (PILOT-276)#10

Open
matthew-pilot wants to merge 2 commits into
mainfrom
openclaw/pilot-276-20260530-052723
Open

fix(dataexchange): add InboxMaxBytes byte-budget cap to prevent disk DoS (PILOT-276)#10
matthew-pilot wants to merge 2 commits into
mainfrom
openclaw/pilot-276-20260530-052723

Conversation

@matthew-pilot
Copy link
Copy Markdown
Collaborator

What failed

saveInboxMessage trusted only InboxMaxFiles (file-count cap, default 10000) with eviction running every 64th write. Between eviction passes, up to 63 messages could accumulate with no cap on per-message size — worst-case 63 × 256 MiB ≈ 16 GiB of disk usage before the periodic eviction scan trims it.

Why this fix

Adds InboxMaxBytes int64 to ServiceConfig. When set:

  • Pre-write budget check in saveInboxMessage estimates overhead + payload, evicts if over, rejects with error + inbox.full event if still over.
  • evictInboxOverflowByBytes does FIFO eviction by size instead of file count.

When InboxMaxBytes is unset (zero), the old InboxMaxFiles behaviour is fully preserved (backward-compatible).

Verification

  • go build ./... — clean
  • go vet ./... — clean
  • go test ./... -count=1 -timeout 120s — all passing (including 3 new byte-budget tests)

Diff stat

 service.go                | 109 ++++++++++++++++++++++++++++++++++++++++++++--
 zz_service_errors_test.go | 102 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 208 insertions(+), 3 deletions(-)

Closes PILOT-276

Add three tests that demonstrate the lack of byte-based cap on inbox
storage: TestSaveInboxMessage_ByteBudgetEnforced, TestSaveInboxMessage_NoByteBudget_Unbounded,
and TestEvictInboxOverflow_ByteBasedEvictsOldest.

These tests currently FAIL because InboxMaxBytes is not implemented yet.
…DoS (PILOT-276)

saveInboxMessage previously trusted InboxMaxFiles (file-count cap, default 10000)
with eviction only every 64th write. Between eviction passes, up to 63 messages
could accumulate with no cap on per-message size — worst-case 63 × 256 MiB ≈ 16 GiB
of disk usage before the periodic eviction scan trims it.

This change adds:

1. InboxMaxBytes (int64) to ServiceConfig — when > 0, enables byte-budget enforcement.
2. inboxTotalBytes helper — sums on-disk sizes of inbox files.
3. Pre-write budget check in saveInboxMessage — estimates pending JSON overhead + payload,
   evicts if over budget, and rejects with error + 'inbox.full' event if still over.
4. evictInboxOverflowByBytes — FIFO eviction by size instead of file count.

When InboxMaxBytes is unset (zero), the old InboxMaxFiles behaviour is fully
preserved (backward-compatible). The new path is only active when the operator
explicitly configures InboxMaxBytes.

Closes PILOT-276
@codecov
Copy link
Copy Markdown

codecov Bot commented May 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🦾 Matthew PR Check — #10 PILOT-276

Status

  • State: OPEN · MERGEABLE ✅
  • CI: 2/2 passing (test ✅, codecov/patch ✅)
  • Created: 2026-05-30 05:30 UTC
  • Author: matthew-pilot
  • Branch: openclaw/pilot-276-20260530-052723main
  • Files: 2 (+208 −3)
    • service.go (+106 −3) — InboxMaxBytes config, budget check, eviction, error plumbing
    • zz_service_errors_test.go (+102) — test coverage
  • Labels: none
  • Canary: not-configured (dataexchange repo)

Verdict

CLEAN — all CI green, narrow single-service change, thorough test coverage. Ready for operator review.


🤖 matthew-pr-worker · Wave 1 (pr-status)

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🦜 Matthew Explains — #10 PILOT-276

What this does

Adds InboxMaxBytes int64 to ServiceConfig. When > 0, before writing each inbox message, saveInboxMessage checks the accumulated on-disk byte budget: it estimates overhead + payload size, evicts oldest files if the write would exceed the cap, and returns a "disk budget exhausted" error if eviction can't free enough space.

Why this matters

Before this fix, saveInboxMessage only enforced a file-count cap (InboxMaxFiles, default 10000). Eviction ran only every 64th write. Between eviction passes, up to 63 messages could accumulate with no per-message size cap:

  • Worst-case: 63 × 256 MiB ≈ 16 GiB of disk usage before the periodic eviction scan catches up.
  • A sustained inbound flood from a misbehaving peer could exhaust disk before eviction fires.

How it works

Component Behavior
InboxMaxBytes = 0 Backward-compatible: no byte budget, same as before
InboxMaxBytes > 0 Pre-write check: budget − overhead − payload < 0 → evict oldest, retry; if still over → error
Eviction Still uses file-age LRU, but now triggered by byte pressure too
Error Returns ErrInboxDiskBudgetExhausted (new sentinel) when budget exceeded

Security impact

Defense-in-depth against disk-DoS. The file-count cap alone was a coarse control vulnerable to large-payload peers. The byte budget closes the gap — even with 63 files accumulating between eviction passes, total disk usage stays bounded.

Test coverage

  • TestSaveInboxMessage_ByteBudgetEnforced — verifies budget is checked before write, eviction fires, error returned on exhaustion (+102 lines of focused test cases)

🤖 matthew-pr-worker · Wave 1 (pr-explain)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant