Repository navigation
Description
Problem
pkgs/mcpx/internal/wasmtools (the #1970 WASM tool-source spike, merged today in #2002) caps guest output at 1 MiB (maxMsg = 1 << 20, wasmtools.go:58, enforced in readOutput, wasmtools.go:126-129) but places no bound at all on the granted log capability:
- The host
loghandler appends every call tos.logsunconditionally — no per-line length limit, no line-count limit, no total-bytes limit (wasmtools.go:105-112). Logs()returns a copy of the full history and never drains it (wasmtools.go:121-123). There is no other accessor, so retention grows monotonically for the whole lifetime of aSource.- A guest can invoke the
logimport any number of times inside a singleListTools/CallToolinvocation, and each line's length is bounded only by the guest's own memory. The guest is exactly the untrusted party this package's capability model exists to contain (deny-by-default imports, no WASI — see the package doc,wasmtools.go:1-13).
So the one channel the host forgot to cap is the one a hostile or buggy guest can use to grow host memory without limit, while the result channel right next to it is carefully capped.
Evidence (verified this run, main 26221fcc)
Probe on the laptop (~/git/fleet/probe-2026-10-28-laneB-wasmlogs/, verbatim copy of pkgs/mcpx/internal/wasmtools + internal/mcpclient from main 26221fcc, Go test using the shipped testdata/echo.wasm — the well-behaved guest, which logs exactly one line per call):
=== RUN TestProbeLogsUnbounded
probe_wasmlogs_test.go:29: after 20000 CallTool calls: len(Logs())=20000
probe_wasmlogs_test.go:32: second Logs() call: len=%d (drain? false) // len=20000
--- PASS: TestProbeLogsUnbounded (1.09s)
20,000 ordinary calls → 20,000 retained lines, and a second Logs() call proves nothing is ever drained or trimmed. A looping guest reaches arbitrary retention within one call; the probe only needed the polite guest to prove no bound exists.
Why it matters
The spike's stated purpose is hosting untrusted tool sources behind a capability boundary. An unbounded host-side accumulator reachable through a granted capability is a memory-DoS hole in that boundary, and it will be inherited by whatever production registration path (#1885) adopts this package if it is not fixed while the API is still cheap to change. It is also asymmetric with the package's own maxMsg discipline, so the fix is in keeping with the author's intent.
Acceptance criteria
- Log retention is bounded: a documented cap on line count and/or total bytes (and a sane per-line truncation or rejection), in the same spirit as
maxMsgfor output. - The behavior when the cap is hit is explicit and observable (e.g. further lines dropped with a marker/counter, or an error) — not silent truncation that loses the fact logging overflowed.
- A test with a guest that logs excessively (looping guest or repeated calls past the cap) asserts retention stays at/below the cap; existing tests (
TestListAndCallWithGrantedLog, grant-denial tests) still pass. - The package doc /
Logs()doc states the retention contract (bounded, and whetherLogs()drains or is a snapshot).
Found by new-issue-creator-code-b (Lane B) this run; not self-solved — the cap value and drop-vs-error contract are triage's pin.
Activity
developing-today commentedon Oct 29, 2026
CODE-990
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 29, 2026
Intake review
Related: parent spike #1970 / PR #2002 (merged today) introduced the wasmtools package; the 1 MiB output cap (
maxMsg) is the in-package precedent. No open PR touches wasmtools (checked this run); no duplicate issue found.Notes: the bug is real and self-contained — the
loghost handler appends unboundedly andLogs()never drains, so retention grows for the lifetime of aSource. The untrusted-guest framing in the body is the right severity lens.Risks: the cap value and drop-vs-error contract are open design pins (the filer deliberately left them to triage); an overly small cap could break legitimate verbose guests.
Suggested approach: add line-count + total-bytes caps beside
maxMsg, drop-with-counter past the cap, document the retention contract onLogs(), and add the looping-guest regression test the AC names.— Fleet monitor intake pass (protocol §5): issue verified still live this run (filed within the last hour; no merged PR or duplicate resolves it), labels normalized, meta/enriched applied.
37 remaining items
dezren39 commentedon Nov 1, 2026
agent-note:v1 — queue task 5078 (lane A) — parked again: PR #3366 checks still queued in the CI stall
Fresh check 2026-11-01T07:31Z (plus a ~5-minute poll): PR #3366 is OPEN at head 8c3f4ff. Codex review on that head is complete with no findings (codex-review-state: has_any_findings=false, has_unresolved_findings=false). The mcpx-docs build passed;
go test ./internal/wasmtoolspassed locally per the PR evidence. Still pending (queued, 0s elapsed — runner stall, same as the id workflow): docs-history check, lint, plugin tests, PR changed tests.Resume condition: when those four checks report, merge PR #3366 if green (Codex already dispositioned: clean), close out #2073, and complete task 5078 as fixed_merged. If a check fails on the change itself, fix on branch queue/5078-2073-bound-wasm-logs.
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done