Commit graph

3 commits

Author SHA1 Message Date
Mike Conley
c4849b6651
improve: OSC 8 hyperlink handling, and a setting to turn it off (#9731)
#### Brief overview of PR changes/additions
- Hardens how OSC 8 link payloads and link text are handled before they
are run or displayed; link commands are no longer built by
string-formatting remote text into Lua source.
- Adds a per-profile setting (General → Game protocols) to turn OSC 8
hyperlinks off, which also reports `0` for every `OSC_HYPERLINKS*`
NEW-ENVIRON variable and sends an INFO update if toggled mid-session.
- Fixes `selected=` callbacks on `send:` links, which never fired, and
keeps emoji and Persian/Arabic/Indic text intact in tooltips and menu
labels.

#### Motivation for adding to Mudlet

Inspired by
[conversation](https://discord.com/channels/279748146316312576/1416447642472284261/1535109010066251890)
on the MUD Discord and updates to terminal emulators.

OSC 8 sequences arrive from the game server — and often from another
player whose say/tell text the server relays — so they have to be
treated as untrusted input rather than as content the user chose to
load.

#### Other info (issues closed, discussion etc)
New unit tests: `LuaLiteralTest` (28 cases, including an exhaustive
sweep over the bracket alphabet, each evaluated in a real Lua 5.1 state)
and `UntrustedTextTest` (26 cases covering emoji sequences, non-Latin
shaping and the two sanitization policies). There is no automated
NEW-ENVIRON coverage anywhere in the repo, so that path was verified
manually against a live server instead.

**Test case:**
1. `say !osc8-docs` — every documented feature still works.
2. Send a link whose command ends in `]`, e.g. `send:say [OOC]` —
clicking sends the literal text (previously the click silently did
nothing).
3. Settings → General → Game protocols → uncheck "Enable OSC 8
hyperlinks from the server" — links stop rendering and the server is
told without a reconnect; re-check and they return.
4. Send a tooltip or menu label containing a multi-part emoji such as
👨‍🍳 — it renders normally, not as its component parts.

---------

Signed-off-by: Michael Conley <sousesider@gmail.com>
2026-08-08 10:09:32 +02:00
Vadim Peretokin
963b035ab4
fix: four security holes in the new browser sign-in (#9713)
#### Brief overview of PR changes/additions

Four security fixes to the GMCP `Char.Login` v2 browser sign-in added in
#9378 *Add: sign in to supported games using your browser (e.g. Google,
Discord, or the game's own account)*, found by the 5.0 QA sweep and
reproduced on the wire. The game server is untrusted throughout: Mudlet
connects to arbitrary user-specified MUDs.

- **A saved sign-in no longer goes out in the clear.**
`Char.Login.Reconnect` carries a bearer token that signs into the
player's account without their password, and Mudlet replayed it on
whatever transport was live at the time - so a sign-in earned over TLS
went out over plain telnet on the next connect. It is now refused on a
cleartext transport, mirroring the `Char.Login.AuthCode` guard already
in the same file; the player is told why and the sign-in falls back to
the provider resume or the game's own sign-in screen.
- **A server can no longer open browser tabs at will.** The
client-driven OAuth path reached `QDesktopServices::openUrl()` with a
server-chosen address and no guard at all, once per frame the server
sent (5 frames measured, 5 tabs). Both flows now go through one decision
point with a budget of one automatic hand-off per connection, refilled
whenever the player sends something to the game. The client-driven flow
still opens the browser on connect - that is the sign-in the player came
for - but a burst of frames buys one tab, not a tab each, and
`Char.Login.URL`, which a server may push at any moment, still needs
actual input first.
- **The OIDC nonce reaches the game, and a `Char.Login.Default` flood no
longer buys credential-store churn.** With `"nonce": true` Mudlet
generated a nonce and put it in the authorization URL but never sent it
on, so the party that actually validates the ID token could not check
its `nonce` claim; it now rides in `Char.Login.AuthCode`. Separately
each `Char.Login.Default` frame started a fresh credential-store read (a
114 KB flood measured 4002 reads); sign-in attempts are now throttled to
one per second, and dropped outright if the connection that scheduled
them has gone.

#### Motivation for adding to Mudlet

All four are new in 5.0 and none was covered by a test, which is why
they shipped. The token replay is the serious one: it hands a
password-equivalent account credential to anyone on the path.

Two decisions worth a second opinion:

- The Char.Login 2 draft on Area 51 defines no `nonce` field in
`Char.Login.AuthCode`, so this adds one. Without it `"nonce": true`
cannot mean anything - nobody is in a position to verify the value.
**The spec needs the field added to match.**
- The same draft explicitly permits sending the reconnect token over
plain telnet ("a server may choose to issue and accept tokens only over
`telnets://`"). It does not require a client to, so refusing is
conformant, but it is deliberately stricter than the spec.

#### Other info (issues closed, discussion etc)



Test case: connect a profile with a saved sign-in to a game offering
`Char.Login 2` over plain telnet - Mudlet says the connection is not
encrypted and hands off to the game's own sign-in screen instead of
replaying the token.

`GMCPCharLoginTest` gains a TLS-capable stub (embedded self-signed
loopback certificate) and a loopback OpenID discovery stub, so the
encrypted-transport and client-driven paths are exercised for real
rather than assumed. The seven existing token tests move onto it, which
is itself the proof that they were previously passing over cleartext.
80/80 `ctest` green.

Assisted-by: Claude:claude-opus-5
2026-08-07 06:23:06 +02:00
Mike Conley
a97186ad5f
Add: sign in to supported games using your browser (e.g. Google, Discord, or the game's own account) (#9378)
#### Brief overview of PR changes/additions

Adds client-side support for the GMCP `Char.Login` v2 sign-in flow. When
a game offers it, Mudlet automates sign-in *around the game's own login
screen* — it renders no sign-in UI of its own:

- Hands off to the game's interactive screen with an empty
`Char.Login.Credentials {}` when nothing is stored, so the player picks
a provider (Google, Discord, GitHub, the game's own account, …) as text
on the game's own screen.
- Opens the sign-in URL the server pushes (`Char.Login.URL`) in the
system browser — but only after the player has acted on this connection,
never unprompted.
- Autofills a stored character name + password when the profile has
them.
- Persists and replays the server's reconnect token (`Char.Login.Token`
/ `Char.Login.Reconnect`) for instant, password-less reconnects, with a
"forget saved sign-in" control in Preferences → Connection.
- Resumes the *remembered* provider's browser sign-in without a menu
when a saved token has expired or been revoked (`Char.Login.Credentials
{account, provider}`), falling back to the interactive hand-off only
when no provider is remembered.
- Handles token rotation and multiple devices safely: overwrites the
saved token when the server rotates it, and if another running Mudlet
instance sharing the profile's keychain rotates the token mid-flight,
replays the fresh token instead of discarding it.
- For a game that is its own OpenID Provider over TLS, optionally runs
the client-driven PKCE flow end to end (`Char.Login.AuthCode`).

#### Motivation for adding to Mudlet

Modern games are moving to browser-based single sign-on; this lets
Mudlet players use those accounts directly, and reconnect without
re-entering anything — while classic character-name/password logins keep
working unchanged.

#### Other info (issues closed, discussion etc)

- Reworks the approach to resolve @vadi2's UX feedback
(https://github.com/Mudlet/Mudlet/pull/9373#issuecomment-4865669499):
there is **no client pop-up and no in-client chooser**. The game owns
the sign-in screen and offers the choice as text; Mudlet only automates
the mechanical hooks around it (autofill, open URL, save token, replay
token).
- Supersedes and closes #9373.
- Implements the revised v2 draft spec, inspired by #9354.
- Covered by new tests: `OAuthClientFlowTest` (OIDC discovery, PKCE,
loopback capture) and the `GMCPCharLoginTest` functional suite driving a
GMCP server stub through the full client flow.
- Try it out on StickMUD.



https://github.com/user-attachments/assets/566947f6-4f43-4bff-b98c-9328b7a40a2d

---------

Signed-off-by: Michael Conley <sousesider@gmail.com>
2026-07-14 12:20:02 +00:00