[morethanwords/tweb/master](https://github.com/morethanwords/tweb/tree/master) • [b59a023](https://github.com/morethanwords/tweb/commit/b59a02302fb385faaa9bd8997a36c14310e9c370) • _3 files, +146/-13_
Bind the Mini App bridge to the origin it was opened at

The inbound listener authorised a bridge message by the frame's WindowProxy
alone. That identity survives a cross-origin navigation, so once a Mini App
frame ended up on another origin — an open redirect in the app, a script it
embeds, a callback on a partner domain — the document that replaced it kept
the bridge, and with it the privileges of the bot the frame was opened for:
that bot's device storage, invokeWebViewCustomMethod, and the location the
user had already granted, handed over with no further prompt.

Replies leaked the same way without anyone sending anything: they went out
with target origin '*', so an answer to a request the Mini App itself made —
its location, the clipboard, a custom method result — reached whatever
document occupied the frame by the time it arrived.

The server has been setting webViewResultUrl.same_origin by default since
July 25; Desktop, iOS and Web A all enforce it, and here it was parsed and
never read. Bind on that flag alone, so apps the server does not flag behave
exactly as before, and let postMessage do the outbound half: an event
addressed to the origin is dropped by the browser once the frame has left it.

The check is per message, not a kill switch. An app that walks through a
payment or OAuth origin loses only what it sends from there and works again
on return, which is how the other clients already behave. loadExternal
recomputes the binding because joinChatBotResultWebView replaces the URL.
Games, Instant View embeds and the 3DS verification frame pass no origin and
are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

[morethanwords/tweb/master](https://github.com/morethanwords/tweb/tree/master) • [b0c2052](https://github.com/morethanwords/tweb/commit/b0c20529a79d4c9c751c8960aa86dffd395d30f0) • _2 files, +5/-5_
Stop a group title from running as script in the search chips

The suggestion chips the left search renders under the input take their label
from `community?.title` — the raw `Chat.title` string — and renderEntity assigned
a string label with `innerHTML`. A group named `<img src=x onerror=...>` therefore
executed on [web.telegram.org](http://web.telegram.org/) as soon as anyone holding that chat typed enough of
its name for the chip to appear. No click, no opening the chat, no interaction
past the search box.

Only groups and channels reached it: `getCommunityByPeerId` is gated on
`isAnyChat()`, so private chats fell through to `PeerTitle` and built text nodes
like the rest of the app. The string branch went live with "Implement Telegram
Communities" (2d2f188e1); before that the only strings arriving were the
generated date tips, and the sink sat unreachable.

Wrap the string through `wrapEmojiText` + `setInnerHTML` — the path every other
title in the app already takes — rather than escaping at the call site, so the
next caller to pass a string cannot reopen it. Emoji in a chip label now render
as emoji, which the raw assignment never did.

`wrapMessageForReply` carried the identical `innerHTML` line. Nothing hostile
reaches it today, because every rich part is built as a node and only a literal
space ever takes the string branch, so it appends now too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

[#webk](?q=%23webk)