Skip to content

fix(security): keep channel-metadata and install-policy truncation surrogate-safe#102266

Merged
steipete merged 2 commits into
openclaw:mainfrom
zw-xysk:fix/security-emoji-safe
Jul 9, 2026
Merged

fix(security): keep channel-metadata and install-policy truncation surrogate-safe#102266
steipete merged 2 commits into
openclaw:mainfrom
zw-xysk:fix/security-emoji-safe

Conversation

@zw-xysk

@zw-xysk zw-xysk commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Security-owned text limits in channel metadata and install-policy results use raw UTF-16 code-unit slices. When a limit stops after the high surrogate of an astral character, OpenClaw introduces malformed text into wrapped prompt metadata, policy block reasons, or normalized findings.

Why This Change Was Made

The two private truncation helpers now use the existing truncateUtf16Safe implementation from @openclaw/normalization-core/utf16-slice while preserving their current limits and suffix behavior.

  • Channel metadata keeps the 400-unit per-entry and configurable 800-unit combined limits.
  • Install policy keeps the 1,000-unit reason and 500-unit finding limits.

No config, schema, protocol, dependency, or security decision changes.

User Impact

Long untrusted channel descriptions and long install-policy output no longer end in a surrogate half introduced by OpenClaw. Short text and text not crossing an astral-character boundary remain unchanged.

Evidence

  • Added exact owner-local assertions for both channel-metadata limits, including the complete randomized external-content wrapper after marker-ID normalization.
  • Added an exact production runInstallPolicy assertion for a block reason whose emoji crosses the 1,000-unit boundary.
  • Blacksmith Testbox run 28995972864: 2 files, 27/27 tests passed.
  • git diff --check: passed.
  • Fresh structured autoreview: clean, no accepted/actionable findings; confidence 0.98.

The submitted generic lone-surrogate test was moved from test/security/ to the owning src/security/ shard and replaced with exact behavioral expectations. The previously untested install-policy change now has production-path coverage.

@zw-xysk
zw-xysk requested a review from a team as a code owner July 8, 2026 17:34
@clawsweeper

clawsweeper Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 1:28 AM ET / 05:28 UTC.

Summary
The PR changes the channel-metadata and install-policy private truncation helpers to use truncateUtf16Safe, with focused tests for surrogate-boundary cuts.

PR surface: Source +2, Tests +65. Total +67 across 4 files.

Reproducibility: yes. Source inspection shows current main can split a surrogate pair at the channel metadata 397-code-unit cut and the install-policy 1000-code-unit cut.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: serialized state: src/security/install-policy.test.ts, vector/embedding metadata: src/security/channel-metadata.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Next step before merge

  • No ClawSweeper repair is needed because the latest diff already addresses the mapped truncation paths and has no concrete review findings.

Security
Cleared: The diff uses an existing internal helper and focused tests without adding dependencies, permissions, package-resolution changes, secret handling, or new execution paths.

Review details

Best possible solution:

Land the helper swap with the exact boundary tests after normal maintainer review and current-head merge/check gating.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main can split a surrogate pair at the channel metadata 397-code-unit cut and the install-policy 1000-code-unit cut.

Is this the best way to solve the issue?

Yes. Reusing the existing truncateUtf16Safe helper at the two caller-owned truncation points preserves limits and suffix behavior while removing malformed surrogate output.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against acac359de64e.

Label changes

Label justifications:

  • P2: This is a focused security text-correctness bug fix with limited blast radius and no config, schema, dependency, or default behavior change.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR carries proof: sufficient; the durable ClawSweeper review records copied live-output proof, and the latest head adds committed production-path boundary assertions.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR carries proof: sufficient; the durable ClawSweeper review records copied live-output proof, and the latest head adds committed production-path boundary assertions.
Evidence reviewed

PR surface:

Source +2, Tests +65. Total +67 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 4 2 +2
Tests 2 65 0 +65
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 69 2 +67

What I checked:

  • Repository policy read: The root review policy was read fully; no scoped AGENTS.md owns src/security, and no matching maintainer notes were present. (AGENTS.md:1, acac359de64e)
  • Current main channel-metadata behavior: buildUntrustedChannelMetadata still bounds entries with raw value.slice(0, maxChars - 3), so a cut after the high surrogate of an astral character can return malformed text. (src/security/channel-metadata.ts:19, acac359de64e)
  • Current main install-policy behavior: Install-policy block reasons and findings still use raw value.slice(0, maxChars) before appending an ellipsis, so the 1000-code-unit reason/finding limit can split a surrogate pair. (src/security/install-policy.ts:381, acac359de64e)
  • Shared helper contract: truncateUtf16Safe delegates to sliceUtf16Safe, whose end-boundary check backs off when a slice would leave a high surrogate dangling. (packages/normalization-core/src/utf16-slice.ts:33, acac359de64e)
  • PR fix and tests: The latest PR diff imports truncateUtf16Safe in both touched helpers and adds exact boundary tests for the 397-code-unit channel metadata cut and the 1000-code-unit install-policy reason cut. (src/security/install-policy.test.ts:358, 6cf25a8c6751)
  • Prior review continuity: The earlier fixture issue is addressed on the latest head: channel metadata now uses a 396-code-unit prefix plus emoji to force the truncation boundary, and install-policy now has production-path coverage. (src/security/channel-metadata.test.ts:20, 6cf25a8c6751)

Likely related people:

  • kevinslin: The current main raw-slice helpers in both central files trace to the merged release-preflight PR authored and merged by this GitHub user. (role: introduced current behavior; confidence: high; commits: 9eeebf7cb13a; files: src/security/channel-metadata.ts, src/security/install-policy.ts, src/security/install-policy.test.ts)
  • steipete: Authored the latest test-strengthening commit on this PR branch and recently merged an adjacent UTF-16-safe truncation fix on Matrix text surfaces. (role: recent reviewer and adjacent owner; confidence: medium; commits: 6cf25a8c6751, acac359de64e; files: src/security/channel-metadata.test.ts, src/security/install-policy.test.ts, extensions/matrix/src/matrix/sdk/event-helpers.ts)
  • vincentkoc: Authored and merged the recent bounded-output UTF-16-safe truncation PR that established the same helper-based pattern across multiple text surfaces. (role: adjacent owner; confidence: medium; commits: cfef7ebc1baf; files: packages/speech-core/src/tts.ts, src/agents/tools/web-fetch-utils.ts, src/talk/consult-question.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (3 earlier review cycles)
  • reviewed 2026-07-08T17:40:42.448Z sha be886b2 :: needs changes before merge. :: [P2] Make the surrogate fixtures hit the truncation cuts
  • reviewed 2026-07-08T17:48:31.122Z sha be886b2 :: needs changes before merge. :: [P2] Make the surrogate fixtures hit the truncation cuts
  • reviewed 2026-07-08T18:21:17.562Z sha 6a94d23 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. labels Jul 8, 2026
…rrogate-safe

channel-metadata.ts and install-policy.ts both had private truncateText
functions using String.prototype.slice(0, N) on user-visible text.
channel-metadata is explicitly user-controlled untrusted content that gets
injected into LLM prompt context -- a dangling surrogate from unsafe
truncation would corrupt the prompt with invalid Unicode, affecting token
counting and potentially LLM response quality.

Replace .slice(0, ...) with truncateUtf16Safe(...) in both files so the
truncation point always falls on a complete code-point boundary.

Add test coverage for buildUntrustedChannelMetadata with emoji at both
the entry-level (400-char) and outer (800-char) truncation boundaries.
@zw-xysk
zw-xysk force-pushed the fix/security-emoji-safe branch from be886b2 to 6a94d23 Compare July 8, 2026 18:06
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 8, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Land-ready proof for exact head 6cf25a8c6751885461e8621eafbfd666d752bd4d:

  • Moved the generic channel-metadata test into the owning src/security shard and replaced lone-surrogate scanning with exact wrapped-output assertions for both the 400-unit entry limit and configurable outer limit.
  • Added production runInstallPolicy coverage for a block reason whose astral character crosses the existing 1,000-unit boundary.
  • Blacksmith Testbox run 28995972864: 2 files, 27/27 tests passed.
  • Exact-head CI run 28996074469: passed, including security-specific gates.
  • git diff --check: passed.
  • Fresh structured autoreview: clean, no accepted/actionable findings, confidence 0.98.

The reviewed implementation is the best bounded fix: it retains the existing limits, external-content wrapping, policy decisions, and fail-closed behavior while replacing only the unsafe prefix cuts.

@steipete
steipete merged commit cc96100 into openclaw:main Jul 9, 2026
105 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 9, 2026
…rrogate-safe (openclaw#102266)

* fix(security): keep channel-metadata and install-policy truncation surrogate-safe

channel-metadata.ts and install-policy.ts both had private truncateText
functions using String.prototype.slice(0, N) on user-visible text.
channel-metadata is explicitly user-controlled untrusted content that gets
injected into LLM prompt context -- a dangling surrogate from unsafe
truncation would corrupt the prompt with invalid Unicode, affecting token
counting and potentially LLM response quality.

Replace .slice(0, ...) with truncateUtf16Safe(...) in both files so the
truncation point always falls on a complete code-point boundary.

Add test coverage for buildUntrustedChannelMetadata with emoji at both
the entry-level (400-char) and outer (800-char) truncation boundaries.

* test(security): prove UTF-16 truncation boundaries

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants