fix: strip metadata blocks and prevent reply tag streaming leaks#21643
fix: strip metadata blocks and prevent reply tag streaming leaks#21643akramcodez wants to merge 50 commits intoopenclaw:mainfrom
Conversation
* vincentkoc-code/security-fix-hono-2026.2.20: Security: bump hono for timing-safe auth hardening
|
@greptile-apps please re-review it |
|
Hi @gumadeiras @thewilloftheshadow @cpojer, Would appreciate a review when you get a chance. This fixes two TUI-specific issues by stripping gateway reconnect metadata blocks from chat history and preventing partial [[reply_to_current]] tags from leaking during streaming. The changes are scoped to TUI sanitization/streaming only; non-TUI channels and existing message handling remain unaffected. Thanks! |
|
Status: post-merge follow-up from PR #22142. Keeping this open as a monitored surface for potential metadata / reply-tag streaming gap until validated. |
|
Update: I closed #20807, #21548, and #22147 as redundant/covered by merged #22142. I am leaving #21643 open for now because it appears to be a distinct TUI streaming-edge fix (chat-sanitize.ts + trailing |
* fix(docker): pin base images to SHA256 digests for supply chain security Pin all 9 Dockerfiles to immutable SHA256 digests to prevent supply chain attacks where a compromised upstream image could be silently pulled into production builds. Also add Docker ecosystem to Dependabot configuration for automated digest updates. Images pinned: - node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935 - node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45 - debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe - ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b Fixes openclaw#7731 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(docker): add digest pinning regression coverage --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…nclaw#21086) * fix: treat HTTP 503 as failover-eligible for LLM provider errors When LLM SDKs wrap 503 responses, the leading "503" prefix is lost (e.g. Google Gemini returns "high demand" / "UNAVAILABLE" without a numeric prefix). The existing isTransientHttpError only matches messages starting with "503 ...", so these wrapped errors silently skip failover — no profile rotation, no model fallback. This patch closes that gap: - resolveFailoverReasonFromError: map HTTP status 503 → rate_limit (covers structured error objects with a status field) - ERROR_PATTERNS.overloaded: add /\b503\b/, "service unavailable", "high demand" (covers message-only classification when the leading status prefix is absent) Existing isTransientHttpError behavior is unchanged; these additions are complementary and only fire for errors that previously fell through unclassified. * fix: address review feedback — drop /\b503\b/ pattern, add test coverage - Remove `/\b503\b/` from ERROR_PATTERNS.overloaded to resolve the semantic inconsistency noted by reviewers: `isTransientHttpError` already handles messages prefixed with "503" (→ "timeout"), so a redundant overloaded pattern would classify the same class of errors differently depending on message formatting. - Keep "service unavailable" and "high demand" patterns — these are the real gap-fillers for SDK-rewritten messages that lack a numeric prefix. - Add test case for JSON-wrapped 503 error body containing "overloaded" to strengthen coverage. * fix: unify 503 classification — status 503 → timeout (consistent with isTransientHttpError) resolveFailoverReasonFromError previously mapped status 503 → "rate_limit", while the string-based isTransientHttpError mapped "503 ..." → "timeout". Align both paths: structured {status: 503} now also returns "timeout", matching the existing transient-error convention. Both reasons are failover-eligible, so runtime behavior is unchanged. --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…verbose logs, and WebUI (openclaw#20704)
…0988) * fix(slack): pass recipient_team_id and recipient_user_id to streaming API calls The Slack Agents & AI Apps streaming API (chat.startStream / chat.stopStream) requires recipient_team_id and recipient_user_id parameters. Without them, stopStream fails with 'missing_recipient_team_id' (all contexts) or 'missing_recipient_user_id' (DM contexts), causing streamed messages to disappear after generation completes. This passes: - team_id (from auth.test at provider startup, stored in monitor context) - user_id (from the incoming message sender, for DM recipient identification) through to the ChatStreamer via recipient_team_id and recipient_user_id options. Fixes openclaw#19839, openclaw#20847, openclaw#20299, openclaw#19791, openclaw#20337 AI-assisted: Written with Claude (Opus 4.6) via OpenClaw. Lightly tested (unit tests pass, live workspace verification in progress). * fix(slack): disable block streaming when native streaming is active When Slack native streaming (`chat.startStream`/`stopStream`) is enabled, `disableBlockStreaming` was set to `false`, which activated the app-level block streaming pipeline. This pipeline intercepted agent output, sent it via block replies, then dropped the final payloads that would have flowed through `deliverWithStreaming` to the Slack streaming API — resulting in zero replies delivered. Set `disableBlockStreaming: true` when native streaming is active so the final reply flows through the Slack streaming API path as intended. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…#11046) - Changed "cask" to "formula" in SKILL.md for consistency. - Enhanced formula parsing in frontmatter.ts to trim whitespace and fallback to cask if formula is not provided.
…penclaw#21447) Fixes the pairing required regression from openclaw#21236 for legacy paired devices created without roles/scopes metadata. Detects legacy paired metadata shape and skips upgrade enforcement while backfilling metadata in place on reconnect. Co-authored-by: Josh Avant <830519+joshavant@users.noreply.github.com> Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
26 similar comments
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch. |
|
Superseded by clean reroute: #22346. |
Summary
[[reply_to_current]]tags leaked during streamingchat-sanitize.ts; prevent streaming lone[before[[completes in streaming handlerChange Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
[[reply_to_current]]directives no longer flash/leak during text streamingSecurity Impact (required)
Repro + Verification
Environment
Steps
openclaw-tuiopenclaw gateway restart)Expected
[characters during streamingActual
Evidence
chat-sanitize.test.tspi-embedded-subscribe.handlers.messages.test.tsHuman Verification (required)
[logic doesn't break closed directives[[...]]tags (not stripped)Compatibility / Migration
Greptile Summary
This PR fixes two TUI-specific issues: metadata block leakage and streaming directive tag leaks. The implementation correctly strips "Conversation info (untrusted metadata)" blocks via regex and prevents lone
[characters from appearing before[[reply_to_current]]directives complete during streaming.Key changes:
stripInboundMeta()function with regex/Conversation info \(untrusted metadata\):\n```json[\s\S]*?```\n\n/gthat matches the actual format generated bybuildInboundUserContextPrefix()contentandtextfields)[when no[[is found, preventing partial directive leaks during assistant message streamingThe changes are well-scoped to TUI reconnect scenarios and streaming contexts, with fast-path optimization for messages without metadata.
Confidence Score: 5/5
[stripping logic is sound since it only applies to assistant messages where[should never appear outside of complete[[...]]directives. Changes are scoped to sanitization and streaming contexts with no impact on core message handling.Last reviewed commit: 68940d8