Skip to content

fix(stage-ui): sanitize context snapshots before clone#1928

Open
Gujiassh wants to merge 9 commits into
moeru-ai:mainfrom
Gujiassh:fix/context-clone-sanitize-1909
Open

fix(stage-ui): sanitize context snapshots before clone#1928
Gujiassh wants to merge 9 commits into
moeru-ai:mainfrom
Gujiassh:fix/context-clone-sanitize-1909

Conversation

@Gujiassh

@Gujiassh Gujiassh commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • sanitize nested non-cloneable values before the context bridge calls structuredClone(...)
  • reuse the sanitize helper from the context bridge broadcast path
  • add focused regression coverage for the sanitize helper and a contract test for broadcasted stream events

Testing

  • pnpm exec vitest run --config vitest.sanitize.config.ts
  • git diff --check

Closes #1909

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

⏳ Approval required for deploying to Cloudflare Workers (Preview) for stage-web.

Name Link
🔭 Waiting for approval For maintainers, approve here

Hey, maintainers, kindly take some time to review and approve this deployment when you are available. Thank you! 🙏

@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: af7a9609f4

ℹ️ 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 on lines +6 to +7
if (typeof value === 'bigint' || typeof value === 'symbol' || typeof value === 'function')
return undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve cloneable BigInt context values

When a mod or hook includes a bigint in a context message's generic metadata or content, the value is already structured-cloneable, so the previous structuredClone(...) broadcast path could carry it. This sanitizer now returns undefined for bigint, causing object properties to be dropped and array items to be removed before the stream event is broadcast, which silently corrupts otherwise valid context snapshots.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot added bug Something isn't working scope/avatar Scope related how we work with the avatar to be rendered, controlled, or interacted scope/ui Scope related to UI/UX, or interface improve, perf, and bugs labels Jun 2, 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: 16003f121a

ℹ️ 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 on lines +699 to +701
expect(messages.at(-1)).toMatchObject({
type: 'context:update',
data: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fix the broadcast snapshot test shape

When the stage-ui browser Vitest project runs this new case, messages.at(-1) is the raw ContextMessage posted by broadcastContext(toRaw(contextMessage)), not a { type, data } server event, and the handler overwrites event.data.metadata with event.metadata. As written this assertion can never match the broadcasted value, so the browser suite fails instead of guarding the BigInt behavior.

Useful? React with 👍 / 👎.

Comment on lines +31 to +32
if (proto !== Object.prototype && proto !== null)
return undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve structured-cloneable built-ins

When a local hook or context provider puts a structured-cloneable non-plain object such as Map, Set, Blob, or ArrayBuffer into ContextMessage.content, the registry and previous stream broadcast path could carry it via structuredClone. This branch now returns undefined solely because the prototype is not Object.prototype, so those fields or array entries are silently removed from stream events even though they were valid cloneable context data.

Useful? React with 👍 / 👎.

@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: 319f87ce4b

ℹ️ 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 on lines +26 to +27
if (seen.has(rawValue))
return undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve repeated cloneable objects

When a single context message reuses the same object in two places, such as metadata: { a: shared, b: shared } or a Map whose key and value are the same object, structuredClone would broadcast both references successfully. This WeakSet treats the second visit as a cycle and returns undefined, so the sanitizer drops the later field or entry and corrupts otherwise cloneable context snapshots; it should distinguish true recursion from shared references, or cache cloned values.

Useful? React with 👍 / 👎.

Comment on lines +17 to +21
if (rawValue instanceof Date)
return rawValue.toISOString()

if (rawValue instanceof RegExp)
return rawValue.toString()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve cloneable Date and RegExp values

Fresh evidence in this revision is that Date and RegExp are still explicitly converted here even after other cloneable built-ins were preserved. When a hook stores one of these values in ContextMessage.content or metadata, the previous structuredClone(...) path would deliver the same built-in type, but this sanitizer changes it to a string before broadcasting, breaking consumers that expect date or regexp behavior.

Useful? React with 👍 / 👎.

@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: d679631c61

ℹ️ 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".

if (Array.isArray(rawValue)) {
return rawValue
.map(item => sanitizeCloneable(item, seen))
.filter(item => item !== undefined)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve cloneable undefined values

When context metadata or content intentionally contains undefined (for example as an array placeholder or Map value), it is already structured-cloneable, but using undefined as the removal sentinel makes this filter and the analogous Map/object filters drop it. For positional arrays this also shifts later entries, so broadcast stream snapshots can no longer match the sender's context even though the original value was clone-safe.

Useful? React with 👍 / 👎.

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

Labels

bug Something isn't working scope/avatar Scope related how we work with the avatar to be rendered, controlled, or interacted scope/ui Scope related to UI/UX, or interface improve, perf, and bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vision image input crashes with structuredClone error on Window object

1 participant