Skip to content

fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments [media://inbound/<id> contract]#55513

Merged
obviyus merged 30 commits into
openclaw:mainfrom
Syysean:fix-webui-base64-payload
Mar 30, 2026
Merged

fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments [media://inbound/<id> contract]#55513
obviyus merged 30 commits into
openclaw:mainfrom
Syysean:fix-webui-base64-payload

Conversation

@Syysean

@Syysean Syysean commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

✅ Architecture Decision: media://inbound/<id> Opaque URI — Implemented

After reviewing the distributed-deployment constraint and the workspaceOnly
sandbox boundary issue raised in reviews, I have chosen and fully implemented
the following contract:

Attachment Size Handling
< 2 MB Pass inline as base64 (unchanged)
2–5 MB Offload to disk → pass media://inbound/<id> URI to Agent
> 5 MB Reject with controlled error (unchanged)
2–5 MB, unsupported MIME Reject with user-friendly error (convert to JPEG/PNG/WEBP/GIF)

Rationale for media://inbound/<id>:

  • Decouples storage layout from the Agent contract — no filesystem path leaks
  • Compatible with tools.fs.workspaceOnly sandbox boundaries (media-uri refs
      are intentionally exempt from workspace checks; they live in the Gateway-managed
      media store, not the agent workspace)
  • Single resolver on the Agent side (resolveMediaBufferPath) to hydrate the
      reference when needed
  • Future-proof: switching to S3 or DB only requires changing the resolver, not
      the contract
  • HTTP URL was rejected: requires new Gateway routing + auth surface, breaks in
      isolated Docker deployments where the Agent cannot reach the Gateway's HTTP port

Known limitation (documented in code):
When a mix of large (offloaded) and small (inline) images is present, offloaded
images appear as text markers appended to the message while inline images are in
the images array. This may cause ordering differences vs. the original
attachment list when downstream image detection re-assembles them. A future
refactor should unify all references into a single ordered list.


Summary

Problem: Sending large, high-resolution images via the WebUI passes massive
base64 payloads through the WebSocket connection, causing severe memory spikes on
the Gateway, leading to OOM crashes and socket hang up errors.

Why it matters: It completely breaks the connection for the user and crashes
the core orchestration layer when handling standard modern media files.

What changed: Key files touched:

  • src/gateway/chat-attachments.ts — Claim Check pattern implementation.
      The Gateway intercepts large base64 payloads, persists them via saveMediaBuffer,
      and emits an opaque media://inbound/<id> URI into the message instead of raw
      image data. Includes: size-gated offload with MIME whitelist, assertSavedMedia
      type guard (replaces fragile as SavedMedia cast), optimized isValidBase64
      with sampled spot-check for large payloads, and a hard re-throw on offload
      failure (no silent fallback to inline base64).

  • src/gateway/server-node-events.ts & agent.ts — Request validation and IO protection.
      Re-ordered the validation gates to ensure lightweight checks (e.g., !message or message.length > 20_000) run before parseMessageWithAttachments is called. This prevents malicious or oversized requests from causing avoidable disk I/O churn. Added a secondary length guard after parsing to ensure that the injected [media attached: ...] markers do not push the final prompt over the 20,000-character limit.

  • src/agents/pi-embedded-runner/run/images.ts — Agent-side resolver.
      detectImageReferences now recognises media://inbound/<id> URIs via
      MEDIA_URI_REGEX. loadImageFromRef handles the new "media-uri" ref type by
      calling resolveMediaBufferPath to obtain the physical path, then loading via
      the existing loadWebMedia pipeline. Media-URI refs are intentionally exempt
      from workspaceOnly path checks.

  • src/media/store.ts — Read-side counterpart.
      resolveMediaBufferPath maps a media ID to its absolute physical path. Applies
      its own guards against path traversal (/, \, ..), null-byte injection, and
      symlinks before returning the path.

What did NOT change (scope boundary):
WebUI frontend logic and the downstream Agent's core execution loop.


Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause / Regression History

  • Root cause: The Gateway holds the entire uncompressed base64 string in
      Node.js memory while routing the message through the WebSocket stream.
  • Missing guardrail: No payload size limits or streaming/chunking mechanisms
      for media attachments at the Gateway entry point.
  • Prior context: N/A
  • Why this regressed now: N/A

Regression Test Plan

  • Coverage level that should have caught this:
      - [ ] Unit test
      - [ ] Seam / integration test
      - [x] End-to-end test
      - [ ] Existing coverage already sufficient
  • Target test: Gateway message routing test.
  • Scenario to lock in: Sending a >2 MB image via WebSocket should save to
      disk, return a media://inbound/<id> reference, and allow the Agent to load
      the image correctly — without the Gateway exceeding a memory threshold.
  • Why E2E is the smallest reliable guardrail: The issue spans the network
      boundary, WS memory allocation, disk I/O, and the Agent's image resolution
      pipeline.
  • Existing test coverage: None.
  • Why no new test in this PR: The E2E harness for Gateway ↔ Agent media flow
      does not yet exist; adding it is a follow-up. Unit tests for
      parseMessageWithAttachments and resolveMediaBufferPath are a near-term
      priority.

Diagram

Before:
[User sends 5 MB image]
  → [Gateway holds 7 MB Base64 in RAM]
  → [Gateway OOM / Socket Hang Up]

After:
[User sends 5 MB image]
  → [Gateway: sizeBytes > OFFLOAD_THRESHOLD (2 MB)?]
      YES → [saveMediaBuffer → disk: ~/.openclaw/media/inbound/<id>.png]
          → [message += "\n[media attached: media://inbound/<id>]"]
          → [Agent receives message + images=[]]
          → [detectImageReferences finds media://inbound/<id>]
          → [resolveMediaBufferPath(<id>) → physical path]
          → [loadWebMedia(physicalPath) → ImageContent]
          → [Model receives image inline]
      NO  → [images.push(b64 inline)] → [unchanged path]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? Yes

Risk + mitigation for data access change:

The Gateway now writes user attachments to ~/.openclaw/media/inbound/ on disk.
Mitigations applied at multiple layers:

Layer Control
Write path saveMediaBuffer enforces size limit, sanitizes filename, writes at 0o644 with parent dirs at 0o700
Media ID assertSavedMedia validates ID is non-empty and contains no path separators or null bytes before embedding in the URI
URI parsing MEDIA_URI_REGEX uses an exclusion character class that rejects ], whitespace, /, \, and \x00 at the parsing layer
Read path resolveMediaBufferPath re-checks for path traversal, .., null bytes, and symlinks before returning the physical path
Sandbox Media-URI refs bypass workspaceOnly only because the files are written by the trusted Gateway process, not user-supplied paths
TTL cleanOldMedia expires inbound files after 2 minutes (unchanged)
Resource Exhaustion Pre-validation length guards ensure disk I/O (saveMediaBuffer) is strictly bypassed for empty or oversized invalid requests, preventing Disk Churn attacks.

Repro + Verification

Environment

  • OS: Windows (WSL2) / Docker Desktop
  • Runtime/container: Docker openclaw:local (Node 24 bookworm)
  • Model/provider: SiliconFlow / router
  • Integration/channel: WebUI
  • Relevant config (redacted): Standard local docker-compose setup

Steps

  1. Start the OpenClaw stack via docker-compose up -d.
  2. Open the WebUI and drag-and-drop a large image (> 2 MB, < 5 MB).
  3. Monitor Gateway logs for [Gateway] Intercepted large image payload.
  4. Confirm the Agent successfully loads and describes the image.

Expected

Gateway intercepts the payload, saves it to disk, passes media://inbound/<id>
to the Agent. Agent resolves the URI, loads the image, and the model receives it
correctly.

Actual (with this PR)

File is saved, memory remains stable, Agent resolves the URI via
resolveMediaBufferPath, and the model receives the image.

Evidence

[gateway] Intercepted large image payload. Saved: media://inbound/photo---1c77ce17-20b9-4546-be64-6e36a9adcb2c.png

Human Verification (required)

  • Verified: Local Docker deployment sending large images (2–5 MB). Gateway
    survival confirmed. Agent correctly resolves media://inbound/<id> and the
    model describes image content.
  • Edge cases checked: Empty image arrays; images below the 2 MB threshold
    (pass inline unchanged); images above 5 MB (rejected with error); unsupported
    MIME types above threshold (rejected with user-friendly error).
  • Not verified: Distributed deployments where Gateway and Agent run on
    separate nodes without shared volumes. The media:// URI contract is designed
    to be storage-backend-agnostic, but the resolver currently reads from the local
    filesystem. A shared storage layer (e.g. S3) would only require changing
    resolveMediaBufferPath without altering the URI contract.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • All Codex bot suggestions have been fully implemented, including moving disk offload logic behind authentication/validation gates and adding secondary length guards for injected media markers.
  • Merge conflicts with the latest main branch (specifically regarding the new resolveSkillSource utility and test environment syntax) have been resolved.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — attachments below 2 MB are unchanged. The new
    media://inbound/<id> marker only appears when offloading is triggered.
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

Risk 1 — Mixed-batch attachment ordering

When large and small images are sent together, large images become text markers
appended to the message while small images remain in the images array. The
Agent's detectAndLoadPromptImages processes existingImages first, then
appends URI-detected refs, which may differ from the original attachment order.
This is documented in a JSDoc comment on parseMessageWithAttachments.

Mitigation: Prompts that rely on attachment order (e.g. "compare first and second
image") should be retested. A future refactor should unify all references into a
single ordered list before forwarding to the model.

Risk 2 — Distributed deployments

The resolveMediaBufferPath resolver reads from the local filesystem. If Gateway
and Agent run on separate nodes without a shared volume, the URI cannot be
resolved.

Mitigation: The media:// contract is intentionally decoupled from the storage
backend. Switching to shared storage (e.g. S3, NFS) only requires updating the
resolver — no changes to the Gateway or the URI format.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: XS labels Mar 27, 2026
@Syysean Syysean marked this pull request as ready for review March 27, 2026 03:04
@greptile-apps

greptile-apps Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements a Claim Check pattern to prevent OOM crashes and WebSocket disconnects caused by large base64 image payloads being held in Gateway memory. Attachments above 2 MB are offloaded to disk as media://inbound/<id> opaque URIs; the agent runner resolves them via resolveMediaBufferPath before passing them to the model. The architecture is sound and the security layering (path-traversal guards, symlink rejection, assertSavedMedia type guard, MIME whitelist, best-effort cleanup on partial failure) is thorough.

Key findings:

  • MediaPath/MediaPaths in transcripts will contain an unresolvable media:// URI for large imagespersistChatSendImages synthesises SavedMedia with path: ref.mediaRef (\"media://inbound/<id>\") rather than the real filesystem path. SavedMedia.path is always expected to be a filesystem path (path.join(dir, id) from buildSavedMediaResult), and resolveChatSendTranscriptMediaFields maps it directly into the transcript without any URI-awareness. Any consumer that reads MediaPath as a file will receive an ENOENT or a nonsensical URI scheme. The actual file already exists on disk at path.join(getMediaDir(), \"inbound\", ref.id).

  • offloadedRefs are not captured in server-node-events.ts — the iOS share / deep-link path drops parsed.offloadedRefs, so large-attachment transcript metadata is incomplete on that code path (agent resolution still works because the URI is embedded in the message text).

  • The supportsImages catalog-probe logic is copy-pasted identically across agent.ts, chat.ts, and server-node-events.ts. Extracting it into a shared utility would reduce future maintenance burden.

  • (entry.skill as any) in config.ts uses a looser cast than the typed structural assertions used everywhere else in this PR for the same property."

Confidence Score: 4/5

Safe to merge for the OOM fix itself; the MediaPath URI mismatch is a real transcript-integrity issue that should be addressed before this goes to production.

The core Claim Check implementation is correct and well-guarded. One P1 issue remains: persistChatSendImages stores a media:// URI in SavedMedia.path instead of a filesystem path, which will silently corrupt MediaPath/MediaPaths transcript fields for large images. All other findings are P2 style/quality suggestions.

src/gateway/server-methods/chat.ts — persistChatSendImages stores an opaque URI in SavedMedia.path instead of the real filesystem path, breaking transcript media metadata for offloaded images.

Important Files Changed

Filename Overview
src/gateway/chat-attachments.ts Core Claim Check implementation: size-gated offload, assertSavedMedia type guard, verifyDecodedSize, cleanup on partial failure. Logic is sound; prior review issues (trim, log level) are addressed.
src/media/store.ts Adds resolveMediaBufferPath and deleteMediaBuffer with thorough path-traversal guards (separator, .., null-byte, symlink via lstat).
src/agents/pi-embedded-runner/run/images.ts Adds MEDIA_URI_REGEX and media-uri branch in loadImageFromRef. workspaceOnly bypass is well-justified and documented.
src/gateway/server-methods/chat.ts Wires supportsImages and offloadedRefs through the chat send path, but persistChatSendImages synthesises SavedMedia with path set to an opaque media:// URI instead of the real filesystem path, corrupting transcript MediaPath fields.
src/gateway/server-methods/agent.ts Adds supportsImages probe and MediaOffloadError → UNAVAILABLE mapping. Catalog probe logic is duplicated from chat.ts but not a functional bug.
src/gateway/server-node-events.ts Session/config loading moved earlier to gate supportsImages. offloadedRefs are not captured after parsing, leaving node-event transcript media metadata incomplete for large images.

Comments Outside Diff (1)

  1. src/gateway/server-methods/chat.ts, line 364-376 (link)

    P1 media:// URI stored where a filesystem path is expected

    SavedMedia.path is always constructed by buildSavedMediaResult as path.join(dir, id) — an absolute filesystem path (e.g. ~/.openclaw/media/inbound/photo---<uuid>.png). For offloaded refs, path: ref.mediaRef stores an opaque "media://inbound/<id>" URI instead.

    resolveChatSendTranscriptMediaFields (line 399) maps these straight into MediaPath / MediaPaths transcript fields:

    const mediaPaths = savedImages.map((entry) => entry.path);
    return { MediaPath: mediaPaths[0], MediaPaths: mediaPaths, ... };

    Any downstream consumer that treats MediaPath as a filesystem path (e.g. rewriteChatSendUserTurnMediaPaths, frontend image viewers, or future transcript readers) will fail silently with ENOENT or simply receive an unresolvable URI scheme.

    The offloaded file already exists on disk at path.join(resolveMediaDir(), "inbound", ref.id). Consider storing the real path there, or calling resolveMediaBufferPath(ref.id, "inbound") to obtain it safely — keeping media:// solely as the agent-facing wire contract:

    for (const ref of params.offloadedRefs) {
      saved.push({
        id: ref.id,
        path: path.join(getMediaDir(), "inbound", ref.id), // real filesystem path for transcript
        size: 0,
        contentType: ref.mimeType,
      });
    }
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/gateway/server-methods/chat.ts
    Line: 364-376
    
    Comment:
    **`media://` URI stored where a filesystem path is expected**
    
    `SavedMedia.path` is always constructed by `buildSavedMediaResult` as `path.join(dir, id)` — an absolute filesystem path (e.g. `~/.openclaw/media/inbound/photo---<uuid>.png`). For offloaded refs, `path: ref.mediaRef` stores an opaque `"media://inbound/<id>"` URI instead.
    
    `resolveChatSendTranscriptMediaFields` (line 399) maps these straight into `MediaPath` / `MediaPaths` transcript fields:
    
    ```typescript
    const mediaPaths = savedImages.map((entry) => entry.path);
    return { MediaPath: mediaPaths[0], MediaPaths: mediaPaths, ... };
    ```
    
    Any downstream consumer that treats `MediaPath` as a filesystem path (e.g. `rewriteChatSendUserTurnMediaPaths`, frontend image viewers, or future transcript readers) will fail silently with `ENOENT` or simply receive an unresolvable URI scheme.
    
    The offloaded file already exists on disk at `path.join(resolveMediaDir(), "inbound", ref.id)`. Consider storing the real path there, or calling `resolveMediaBufferPath(ref.id, "inbound")` to obtain it safely — keeping `media://` solely as the agent-facing wire contract:
    
    ```typescript
    for (const ref of params.offloadedRefs) {
      saved.push({
        id: ref.id,
        path: path.join(getMediaDir(), "inbound", ref.id), // real filesystem path for transcript
        size: 0,
        contentType: ref.mimeType,
      });
    }
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/server-methods/chat.ts
Line: 364-376

Comment:
**`media://` URI stored where a filesystem path is expected**

`SavedMedia.path` is always constructed by `buildSavedMediaResult` as `path.join(dir, id)` — an absolute filesystem path (e.g. `~/.openclaw/media/inbound/photo---<uuid>.png`). For offloaded refs, `path: ref.mediaRef` stores an opaque `"media://inbound/<id>"` URI instead.

`resolveChatSendTranscriptMediaFields` (line 399) maps these straight into `MediaPath` / `MediaPaths` transcript fields:

```typescript
const mediaPaths = savedImages.map((entry) => entry.path);
return { MediaPath: mediaPaths[0], MediaPaths: mediaPaths, ... };
```

Any downstream consumer that treats `MediaPath` as a filesystem path (e.g. `rewriteChatSendUserTurnMediaPaths`, frontend image viewers, or future transcript readers) will fail silently with `ENOENT` or simply receive an unresolvable URI scheme.

The offloaded file already exists on disk at `path.join(resolveMediaDir(), "inbound", ref.id)`. Consider storing the real path there, or calling `resolveMediaBufferPath(ref.id, "inbound")` to obtain it safely — keeping `media://` solely as the agent-facing wire contract:

```typescript
for (const ref of params.offloadedRefs) {
  saved.push({
    id: ref.id,
    path: path.join(getMediaDir(), "inbound", ref.id), // real filesystem path for transcript
    size: 0,
    contentType: ref.mimeType,
  });
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/gateway/server-node-events.ts
Line: 1044-1055

Comment:
**Offloaded refs silently dropped — transcript incomplete**

`parsed.offloadedRefs` is never captured here (unlike in `chat.ts` which stores `parsedOffloadedRefs`). Large images sent via iOS share/deep-link will have their `media://inbound/<id>` marker correctly embedded in `message`, so the agent resolves and loads them, but the transcript `MediaPath`/`MediaPaths` fields will be missing those entries.

This is an acceptable short-term gap if node-events transcripts don't record media metadata, but the limitation should be documented. Consider adding a `// TODO: node-event transcripts do not persist offloadedRefs yet` comment or, if a `persistChatSendImages` equivalent is called on this path, wiring `offloadedRefs` through.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/skills/config.ts
Line: 226

Comment:
**`as any` inconsistent with rest of PR**

Every other site that accesses `sourceInfo` uses a typed structural cast (e.g. `(entry.skill as { sourceInfo?: { source?: string } })`). Using `as any` here loses type-checking on subsequent property accesses.

```suggestion
  return BUNDLED_SOURCES.has((entry.skill as { sourceInfo?: { source?: string } }).sourceInfo?.source ?? "");
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix-webui-base6..." | Re-trigger Greptile

Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76ef46faa6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 134753242e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf5efea0b6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad06ccb69d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe43752d47

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
@Syysean

Syysean commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

@chatgpt-codex-connector @greptile-apps

This PR exposes a contract gap between the Gateway and sandboxed agents.

Offloading large media to disk is the right direction, but returning an absolute filesystem path is not compatible with tools.fs.workspaceOnly. Falling back to inline base64 also defeats the purpose of offloading.

The cleanest solution is to return an opaque media reference instead of a raw path, for example media://inbound/<id> or a Gateway-served URL that downstream agents can resolve safely.

My suggestion is:

  • keep inline base64 for small attachments,
  • offload large attachments,
  • return a sandbox-safe media reference,
  • add a resolver on the agent side to hydrate that reference when needed.

That keeps the memory fix, avoids leaking filesystem layout, and gives us a stable contract for future storage changes.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@Syysean

Syysean commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

@steipete @vincenttoc

Would love maintainer input on the contract direction here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b70cd11d5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
@Syysean

Syysean commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

All core payload handling, linting, and formatting checks are passing successfully ✅

I noticed a few CI failures in unrelated areas:

  • voice-call.plugin.test.ts (log assertion not triggered)
  • test-planner.test.ts (Windows path separator mismatch)

Based on the scope of this PR (chat attachment handling only), these appear to be unrelated upstream issues rather than caused by this change.

The attachment offloading logic is fully implemented, isolated, and ready for review.

Happy to rebase or adjust once CI stabilizes 👍

No changes were made outside src/gateway/chat-attachments.ts.

@Syysean Syysean changed the title [WIP] fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments Mar 27, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06738df0ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
@Syysean Syysean changed the title fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments [media://inbound/<id> contract] Mar 28, 2026
@Syysean

Syysean commented Mar 28, 2026

Copy link
Copy Markdown
Contributor Author

CI failures are pre-existing upstream issues, unrelated to this PR.

All 14 failing checks trace back to a TypeScript type error in
src/agents/skills-*.test.ts — 'source' does not exist in type 'Skill' (TS2353).
This is a type definition change on main that broke existing tests across the repo.

The only file changed in this PR is src/gateway/chat-attachments.ts.

@Syysean

Syysean commented Mar 28, 2026

Copy link
Copy Markdown
Contributor Author

@steipete @vincenttoc — Would love a quick confirmation on the media://inbound/ contract direction so I can implement the Agent-side resolver.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M and removed size: S labels Mar 28, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f485a7c818

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/pi-embedded-runner/run/images.ts Outdated
Comment thread src/media/store.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f448f6c6a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/agents/pi-embedded-runner/run/images.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e8483b4e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts
Comment thread src/gateway/chat-attachments.ts Outdated
@Syysean

Syysean commented Mar 28, 2026

Copy link
Copy Markdown
Contributor Author

CI Failure Analysis

All currently failing checks are unrelated to changes in this PR
(src/gateway/chat-attachments.ts, src/agents/pi-embedded-runner/run/images.ts,
src/media/store.ts).

security-fast + cascading pre-commit checks — Fails in 15s at
"Prepare trusted pre-commit config":

fatal: path '.pre-commit-config.yaml' exists on disk, but not in 'f6de4cd766c087be4ecad62fd7cf8c0d3cfb0e4a'

The security baseline commit f6de4cd7 predates the addition of
.pre-commit-config.yaml to the repository. This is a CI infrastructure
issue affecting all PRs against this base, not caused by this change.

checks-node-test-* / checks-windows-* / macos-* — Two tests failing
across all shards:

Test file Failure Relation to this PR
src/media-understanding/runtime.test.ts:15 Timed out after 120 000 ms None — no changes to media-understanding/
src/plugins/cli.browser-plugin.integration.test.ts:44 expected [] to include 'browser' None — no changes to plugins/

checks-fast-extensions / checks-fast-contracts-protocol — Both are
downstream of the same shard infrastructure failures above.

No changes were made outside of:

  • src/gateway/chat-attachments.ts
  • src/agents/pi-embedded-runner/run/images.ts
  • src/media/store.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2f1b7ef72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated
Comment thread src/gateway/chat-attachments.ts Outdated
@obviyus obviyus force-pushed the fix-webui-base64-payload branch from 0cf9641 to ca2526c Compare March 30, 2026 15:17

@obviyus obviyus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed latest changes; landing now.

@obviyus obviyus merged commit c6f2db1 into openclaw:main Mar 30, 2026
8 checks passed
@obviyus

obviyus commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Landed on main.

Thanks @Syysean.

pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Tardisyuan pushed a commit to Tardisyuan/openclaw that referenced this pull request Apr 30, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…(thanks @Syysean)

* feat(gateway): implement claim check pattern to prevent OOM on large attachments

* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path

* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer

* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions

* fix: align saveMediaBuffer arguments and satisfy oxfmt linter

* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)

* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF

* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug

* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT

* feat: implement agent-side resolver for opaque media URIs and finalize contract

* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review

* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass

* refactor(gateway): harden media offload with performance and security optimizations

This update refines the Claim Check pattern with industrial-grade guards:

- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.

* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer

* fix(gateway): clean up offloaded media files on attachment parse failure

Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.

* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass

Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.

* fix(gateway): preserve offloaded media metadata and fix validation error mapping

Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \�erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.

* fix(agents): explicitly allow media store dir when loading offloaded images

Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.

* fix(gateway): resolve attachment offload regressions and error mapping

Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \�gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.

* fix(gateway): dynamically compute supportsImages for overrides and node events

Address follow-up Codex review feedback:

- Use effective model (including overrides) to compute \supportsImages\ in \�gent.ts\.

- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.

* fix(gateway): resolve capability edge cases reported by codex

Address final Codex edge cases:
- Refactor \�gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \�gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.

* fix(agents): restore before_install hook for skill installs

Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.

* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks

* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events

* fix(gateway): re-enforce message length limit after attachment parsing

Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.

* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection

* fix(gateway): share attachment image capability checks

* fix(gateway): preserve mixed attachment order

* fix: fail closed on unknown image capability (openclaw#55513) (thanks @Syysean)

* fix: classify offloaded attachment refs explicitly (openclaw#55513) (thanks @Syysean)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui gateway Gateway runtime size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants