Skip to content

security: Bound terminal HTTP and RPC request resources #271

Closed

Description

@adrienlacombe

Summary

Terminal HTTP and RPC clients do not consistently enforce internal deadlines or response-size limits. The shared request cache also removes the caller signal before starting its deduplicated fetch, so a request that never settles can remain in the global in-flight map for the lifetime of the page even after every caller detaches.

Affected paths

  • apps/terminal/src/data/core/client.ts
  • apps/terminal/src/data/core/cache.ts
  • apps/terminal/src/protocols/support/provider.ts
  • Direct AVNU and Vesu fetch boundaries
  • Terminal privacy and NFT RPC providers

Audit baseline: v0.9.2 / e317af75434f065458dbcd677f0b1a499704fafd.

Impact

A stalled or oversized response from a third-party data service or RPC endpoint can indefinitely wedge a cached terminal request or consume excessive tab memory. This affects terminal availability; it does not directly expose wallet secrets or bypass approval.

Required remediation

Introduce one reusable bounded terminal network boundary that:

  • composes the caller signal with an internal deadline;
  • enforces a response-byte cap before JSON parsing, including a Content-Length precheck where available;
  • stores only a bounded error-body prefix;
  • ensures shared in-flight requests have their own lifecycle even when all callers detach;
  • bounds retry and backoff duration;
  • supplies a bounded baseFetch to Starknet RPC providers;
  • is used by every AVNU, Vesu, privacy, NFT, and market-data path.

Preserve the current rule that semantic RPC failures do not trigger endpoint failover.

Retest criteria

  • A request that never produces headers is aborted internally.
  • Oversized success and error bodies are rejected before unbounded parsing/storage.
  • Caller cancellation does not cancel work still needed by another caller.
  • A timed-out shared request is removed from inFlight and can be retried.
  • Retry/backoff cannot outlive the total request budget.
  • Every production terminal fetch and RpcProvider is covered by the bounded boundary.
  • bun run check, bun run check:coverage, and terminal build pass.

Severity: Low · Area: terminal availability and resource exhaustion · Status: Open

Activity

  1. added
    terminalStandalone trade terminal work
    securitySecurity-sensitive changes (keys, permissions, crypto)
    track:terminalOut-of-GTM terminal app track (apps/terminal)
    on Aug 25, 2026
  2. mossbot55 commented on Oct 2, 2026

    @mossbot55
    Contributor

    I will check this against the current terminal HTTP/RPC layer (data/core/client.ts, data/core/cache.ts, protocols/support/provider.ts, and the direct AVNU, Vesu, privacy and NFT fetch/RPC paths) and try to reproduce the resource-bounding gaps locally, then report what I find.

  3. mossbot55 commented on Oct 2, 2026

    @mossbot55
    Contributor

    Reproduced locally against the current terminal network layer (the release/v0.10.5 tree plus unrelated local work, not committed).

    Observed behaviour:

    1. No internal deadline. A GET issued through data/core/cache.ts whose upstream never produces headers stays pending indefinitely. The caller's signal is removed before the shared fetch, so once every caller detaches the request keeps its slot in the shared in-flight map; a later request for the same URL is de-duplicated onto that stuck promise and also never settles. fetchJson in data/core/client.ts passes no composed signal.
    2. No response-byte cap. fetchJson reads and parses an arbitrarily large success body (a 6 MB JSON body parsed normally), and HttpError keeps the full error body (a 6 MB error body was stored verbatim instead of a bounded prefix).
    3. No retry/backoff budget for the shared request.

    Evidence: apps/terminal/tests/network-boundary.test.ts, run with the terminal's vitest config. Three checks fail on the unchanged tree (missing deadline, oversized success body accepted, unbounded stored error body); the existing check that one caller's abort does not cancel a request another caller still needs passes. I reproduced this at the unit level only; no live upstream was contacted.

    Limitations: this is a local reproduction of the resource-bounding gaps, not a test against a production endpoint.

  4. derek-flossman commented on Oct 13, 2026

    @derek-flossman
    Collaborator

    Fixed in #510, merged into release/v0.10.6 through batch #642 (fecb613).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:lowLow prioritysecuritySecurity-sensitive changes (keys, permissions, crypto)severity:lowstatus:in-progressWork in progressterminalStandalone trade terminal worktrack:terminalOut-of-GTM terminal app track (apps/terminal)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions