fix(gateway): prevent OOM and socket hang up via Claim Check pattern for large attachments [media://inbound/<id> contract]#55513
Conversation
Greptile SummaryThis 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 Key findings:
Confidence Score: 4/5Safe 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.
|
| 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)
-
src/gateway/server-methods/chat.ts, line 364-376 (link)media://URI stored where a filesystem path is expectedSavedMedia.pathis always constructed bybuildSavedMediaResultaspath.join(dir, id)— an absolute filesystem path (e.g.~/.openclaw/media/inbound/photo---<uuid>.png). For offloaded refs,path: ref.mediaRefstores an opaque"media://inbound/<id>"URI instead.resolveChatSendTranscriptMediaFields(line 399) maps these straight intoMediaPath/MediaPathstranscript fields:const mediaPaths = savedImages.map((entry) => entry.path); return { MediaPath: mediaPaths[0], MediaPaths: mediaPaths, ... };
Any downstream consumer that treats
MediaPathas a filesystem path (e.g.rewriteChatSendUserTurnMediaPaths, frontend image viewers, or future transcript readers) will fail silently withENOENTor 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 callingresolveMediaBufferPath(ref.id, "inbound")to obtain it safely — keepingmedia://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
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
|
@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 The cleanest solution is to return an opaque media reference instead of a raw path, for example My suggestion is:
That keeps the memory fix, avoids leaking filesystem layout, and gives us a stable contract for future storage changes. |
|
To use Codex here, create a Codex account and connect to github. |
|
@steipete @vincenttoc Would love maintainer input on the contract direction here. |
There was a problem hiding this comment.
💡 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".
|
All core payload handling, linting, and formatting checks are passing successfully ✅ I noticed a few CI failures in unrelated areas:
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. |
There was a problem hiding this comment.
💡 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".
|
CI failures are pre-existing upstream issues, unrelated to this PR. All 14 failing checks trace back to a TypeScript type error in The only file changed in this PR is src/gateway/chat-attachments.ts. |
|
@steipete @vincenttoc — Would love a quick confirmation on the media://inbound/ contract direction so I can implement the Agent-side resolver. |
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
CI Failure AnalysisAll currently failing checks are unrelated to changes in this PR
The security baseline commit
No changes were made outside of:
|
There was a problem hiding this comment.
💡 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".
Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.
…ed media on size rejection
0cf9641 to
ca2526c
Compare
obviyus
left a comment
There was a problem hiding this comment.
Reviewed latest changes; landing now.
…(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>
…(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>
…(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>
…(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>
…(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>
…(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>
…(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>
✅ Architecture Decision:
media://inbound/<id>Opaque URI — ImplementedAfter reviewing the distributed-deployment constraint and the
workspaceOnlysandbox boundary issue raised in reviews, I have chosen and fully implemented
the following contract:
media://inbound/<id>URI to AgentRationale for
media://inbound/<id>:tools.fs.workspaceOnlysandbox boundaries (media-uri refsare intentionally exempt from workspace checks; they live in the Gateway-managed
media store, not the agent workspace)
resolveMediaBufferPath) to hydrate thereference when needed
the contract
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
imagesarray. This may cause ordering differences vs. the originalattachment 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 uperrors.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 rawimage data. Includes: size-gated offload with MIME whitelist,
assertSavedMediatype guard (replaces fragile
as SavedMediacast), optimizedisValidBase64with 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.,
!messageormessage.length > 20_000) run beforeparseMessageWithAttachmentsis 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.detectImageReferencesnow recognisesmedia://inbound/<id>URIs viaMEDIA_URI_REGEX.loadImageFromRefhandles the new"media-uri"ref type bycalling
resolveMediaBufferPathto obtain the physical path, then loading viathe existing
loadWebMediapipeline. Media-URI refs are intentionally exemptfrom
workspaceOnlypath checks.src/media/store.ts— Read-side counterpart.resolveMediaBufferPathmaps a media ID to its absolute physical path. Appliesits own guards against path traversal (
/,\,..), null-byte injection, andsymlinks 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)
Scope (select all touched areas)
Linked Issue/PR
Root Cause / Regression History
Node.js memory while routing the message through the WebSocket stream.
for media attachments at the Gateway entry point.
Regression Test Plan
- [ ] Unit test
- [ ] Seam / integration test
- [x] End-to-end test
- [ ] Existing coverage already sufficient
disk, return a
media://inbound/<id>reference, and allow the Agent to loadthe image correctly — without the Gateway exceeding a memory threshold.
boundary, WS memory allocation, disk I/O, and the Agent's image resolution
pipeline.
does not yet exist; adding it is a follow-up. Unit tests for
parseMessageWithAttachmentsandresolveMediaBufferPathare a near-termpriority.
Diagram
Security Impact (required)
NoNoNoNoYesRisk + mitigation for data access change:
The Gateway now writes user attachments to
~/.openclaw/media/inbound/on disk.Mitigations applied at multiple layers:
saveMediaBufferenforces size limit, sanitizes filename, writes at0o644with parent dirs at0o700assertSavedMediavalidates ID is non-empty and contains no path separators or null bytes before embedding in the URIMEDIA_URI_REGEXuses an exclusion character class that rejects], whitespace,/,\, and\x00at the parsing layerresolveMediaBufferPathre-checks for path traversal,.., null bytes, and symlinks before returning the physical pathworkspaceOnlyonly because the files are written by the trusted Gateway process, not user-supplied pathscleanOldMediaexpires inbound files after 2 minutes (unchanged)saveMediaBuffer) is strictly bypassed for empty or oversized invalid requests, preventing Disk Churn attacks.Repro + Verification
Environment
openclaw:local(Node 24 bookworm)docker-composesetupSteps
docker-compose up -d.[Gateway] Intercepted large image payload.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
Human Verification (required)
survival confirmed. Agent correctly resolves
media://inbound/<id>and themodel describes image content.
(pass inline unchanged); images above 5 MB (rejected with error); unsupported
MIME types above threshold (rejected with user-friendly error).
separate nodes without shared volumes. The
media://URI contract is designedto be storage-backend-agnostic, but the resolver currently reads from the local
filesystem. A shared storage layer (e.g. S3) would only require changing
resolveMediaBufferPathwithout altering the URI contract.Review Conversations
mainbranch (specifically regarding the newresolveSkillSourceutility and test environment syntax) have been resolved.Compatibility / Migration
Yes— attachments below 2 MB are unchanged. The newmedia://inbound/<id>marker only appears when offloading is triggered.NoNoRisks 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
imagesarray. TheAgent's
detectAndLoadPromptImagesprocessesexistingImagesfirst, thenappends 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
resolveMediaBufferPathresolver reads from the local filesystem. If Gatewayand Agent run on separate nodes without a shared volume, the URI cannot be
resolved.
Mitigation: The
media://contract is intentionally decoupled from the storagebackend. Switching to shared storage (e.g. S3, NFS) only requires updating the
resolver — no changes to the Gateway or the URI format.