Skip to content

Add page-select-translate (select-to-translate popover, npm: magpie-select-translate) - #1029

Open
cty1735 wants to merge 1 commit into
magpie-community:mainfrom
cty1735:add-select-translate
Open

cty1735 wants to merge 1 commit into
magpie-community:mainfrom
cty1735:add-select-translate

Conversation

@cty1735

@cty1735 cty1735 commented Nov 12, 2026

Copy link
Copy Markdown

Adds Select Translate to the market: a page plugin plus a local helper.

  • What it does: select text in any Windows app, press Alt+D, and a popover next to the cursor streams the translation from the user's own magpie gateway.
  • Package: magpie-select-translate (npm), version 0.1.1. Not a @magpie-community/* package; maintained at https://github.com/cty1735/magpie-select-translate.
  • Host part: index.mjs exports {id, server}; server() pings 127.0.0.1:3477/api/ping and starts the bundled helper if it isn't running. No imports, no build step.
  • Bundled binary: bin/helper.exe (Go, windows/amd64, ~6.3 MB, CGO off, no runtime needed). It only listens on 127.0.0.1, validates Host, never logs the source text, and restores the clipboard after reading the selection. Source: the repository above.
  • Page: sandboxed as usual (connect-src 'none'); reads nothing from magpie, only magpie.options.get/set. No permissions declared on purpose.
  • Platform: Windows x64 only for now (the helper is a Win32 program); it says so in the summary.

Tested on Windows 11: magpie plugin add npm:magpie-select-translate installs, the page shows, and the helper registers the hotkey within a second.

@cty1735

cty1735 commented Nov 12, 2026

Copy link
Copy Markdown
Author

Friendly ping for review — the package is already live, so this PR is the last step:

$ magpie plugin add magpie-select-translate
added magpie-select-translate@latest        # npm 0.1.1, page shows as "Translate"

If you'd rather host it in this repo (name @magpie-community/page-select-translate, published by CI), say the word and I'll move it in and redo this PR. I kept it in my own repo only because it ships a native binary next to the page.

@yetone yetone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this, @cty1735. I read every file of magpie-select-translate@0.1.1 from npm and the helper's Go source, rebuilt helper.exe from it (it matches apart from the build id and vcs stamps), and ran the helper on a Windows box. I can't merge it yet. The first item is a security problem.

Must fix

  1. Any web page can change the helper's settings. The helper's local API (internal/api/server.go, guardHost) checks only that Host is a loopback name. It doesn't check Origin, and it accepts text/plain POSTs, which a browser sends cross-site without a preflight. So a page open in any browser can rewrite /api/settings. In a test on Windows that let a page:

    • point gateway at its own server. The next Alt+D then sent the selected text, with the magpie gateway key as the Bearer token, to that server.
    • point glossaryFile at any local file. Its contents then went out in the prompt.

    /api/clipboard is open the same way. Please refuse every request that carries a browser Origin (or require a per-install token that only the page gets), require Content-Type: application/json on writes, and limit glossaryFile to a file the user picked in the page.

  2. The package doesn't load. The published package.json starts with a UTF-8 BOM. Bun's JSON.parse and magpie's internal/plugin/page.go (json.Unmarshal) both reject it, so the page never opens.

  3. "community": true in registry.json. That flag shows the "magpie community" shield, which means the package lives in this repo. A third-party package leaves it out, as the other 11 do.

  4. The summary says it "reads nothing". The helper reads magpie's caller-keys.json for the gateway key. Please say so.

Checked and fine

  • Port 3477 is used by nothing in magpie.
  • The only outbound calls go to the configured gateway.
  • The key is kept in memory only, and logs record timings and character counts, never text.
  • The clipboard is saved and put back, and terminals, Explorer, Remote Desktop and password managers are skipped. One note: formats other than text are lost when the clipboard is put back.
  • bun scripts/check.mjs at ffdd22f passes.

Since 0.1.1 is already on npm, please fix item 1 in a new version soon, even before this is listed. Once it's out, I'll run the same checks again.

@cty1735

cty1735 commented Nov 13, 2026

Copy link
Copy Markdown
Author

All four fixed in 0.2.1 (repo commit 3dab5bb; registry.json change is on this branch). The tarball is built (magpie-select-translate-0.2.1.tgz) and is going up to npm now; I'll comment again the moment magpie-select-translate@0.2.1 is live.

1. Cross-site writes (the security one) — internal/api/server.go, guardHost now does three things:

  • refuses every request that carries an Origin header (403 cross-origin request), not just loopback-host mismatches. Nothing legitimate calls this API from a browser page: the page plugin only touches magpie.options, and the helper calls itself in-process.
  • requires Content-Type: application/json on every non-GET (415 otherwise), so the text/plain no-preflight trick is gone.
  • glossaryFile is gone entirely. Settings now carry glossary — the term text itself — and the helper never opens a file on behalf of a request. --glossary-file still exists as a startup-only flag for people who want to load a file from their own shell, but the HTTP API can only send text. So the "point it at C:\Windows\win.ini and it lands in the prompt" path no longer exists.

Tests added: TestGuardRejectsBrowserOrigin (Origin → 403), TestWritesRequireJSON (text/plain → 415), TestSettingsIgnoreGlossaryPath ({"glossaryFile":"C:\\Windows\\win.ini"} → glossary stays empty). Verified against the rebuilt exe too: curl -H "Origin: https://evil.example" /api/ping → 403, text/plain PUT → 415, {"glossaryFile":...} → ignored.

I chose "refuse all Origins" over a per-install token because the helper is a standalone exe that also has to work with the page absent; a token would need a handshake the sandboxed page can't do (connect-src 'none').

2. BOM — plugin/package.json was written by PowerShell 5.1's Out-File and carried a UTF-8 BOM. Rewritten as BOM-less UTF-8, and scripts/build-plugin.ps1 now strips it before npm pack, so it can't come back. Checked the packed tarball: JSON.parse and json.Unmarshal both accept it now.

3. community: true — removed from the registry.json entry in this PR (branch is now at 8745c51; the diff is still the one entry).

4. Summary wording — no longer says "reads nothing". It now says the helper reads caller-keys.json for the gateway key, in memory only, and listens on 127.0.0.1 only. Same correction in README.md and plugin/README.md.

On your note about non-text clipboard formats: correct, and it's in the README's known trade-offs (剪贴板只还原 HGLOBAL 型格式;复制图片后划词会丢图). I'm leaving it as is for now — preserving every format means OleFlushClipboard/IDataObject round-tripping, which is a bigger change than this fix should carry.

go test ./... passes on 3dab5bb, and scripts/build-plugin.ps1 produces a tarball whose package.json parses cleanly with JSON.parse. I'll comment again as soon as 0.2.1 is on npm so you can re-run your checks.

@cty1735
cty1735 force-pushed the add-select-translate branch 2 times, most recently from b0f279d to f24cf17 Compare November 13, 2026 06:17
@cty1735

cty1735 commented Nov 13, 2026

Copy link
Copy Markdown
Author

0.2.1 is on npm now (magpie-select-translate@0.2.1, shasum a1115a64a1dfa0c48e2495bc1b02db065b90d6b3), so the cross-site fix you asked for is out ahead of this listing.

Checked against the published tarball (fetched back from the registry, not the local build):

  • package.json has no BOM, JSON.parse and Go's json.Unmarshal both take it.
  • magpie.options is {"port","hotkey","model","target","glossary"}; glossaryFile no longer exists anywhere.
  • internal/api/server.go: guardHost rejects any request with an Origin header (403), requires Content-Type: application/json on every non-GET/HEAD (415), and still limits Host to loopback names. Rather than a per-install token: the helper is a separate exe and the page runs under connect-src 'none', so the page cannot take part in a token handshake — refusing Origin outright is the whole fix, and /api/clipboard is covered by the same guard.
  • The glossary is now text in the settings (Settings.GlossaryText, JSON glossary, helper flag --glossary-text). --glossary-file remains only as a startup convenience for the local user; the HTTP API cannot name a file at all.

Local run of the built helper on 3499: Origin: https://evil.example → 403; text/plain PUT → 415; {"target":"en","glossaryFile":"C:\\Windows\\win.ini"} → 200 with an empty glossary; {"glossary":"magpie=喜鹊"} stored. go test ./... passes, including new tests for the origin rejection, the content-type requirement and the ignored glossaryFile key.

Also fixed while here: an upgraded plugin reused whatever helper was already installed, so users who upgraded kept running the old exe — the bundled one now wins.

The registry entry on this branch has community removed and the summary now says the helper reads caller-keys.json for the gateway key, in memory only. Branch is rebased on current main (f24cf17), diff is the one entry.

On the clipboard note: yes, only text is put back; other formats are dropped. I'd rather fix that properly than paper over it, so it stays as is for now.

@cty1735
cty1735 force-pushed the add-select-translate branch from f24cf17 to b82e7b1 Compare November 13, 2026 06:53
@cty1735

cty1735 commented Nov 13, 2026

Copy link
Copy Markdown
Author

0.2.2 is on npm (dee524ef201bda0d8906a9719c51a7d96a095c99): the only change is the display name. magpie.name was "Select Translate", which is what the Discover card showed; it is now the package name magpie-select-translate, and the registry entry's name is updated to match (PR head b82e7b1, still +3 −1). No code, helper or settings change; the four review items from 0.2.1 stand as replied above.

@yetone

yetone commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Re-reviewed at head b82e7b1 against the npm 0.2.2 tarball (shasum dee524ef… matches yours) and the source repo at c891227.

The 11-12 points

  • Cross-site writes: fixed. guardHost refuses a non-loopback Host, refuses any request carrying Origin (403), and a non-GET/HEAD needs Content-Type: application/json (415). A form or text/plain POST from a web page can no longer reach /api/settings, /api/clipboard or /api/translate.
  • package.json BOM: gone.
  • Registry entry: no community flag any more, and the summary now says the helper reads caller-keys.json. Good.
  • Display name matches the package now.

One left, smaller
GET /api/selection and /api/hide still act on a plain GET. A web page can fire a GET at 127.0.0.1:3477 (an <img> or a no-cors fetch), with no Origin header and a loopback Host, so it passes the guard. It can't read the reply, but /api/selection then clears the clipboard and sends Ctrl+C (and Esc on a retry) into whatever window is in front. Nothing leaks, but a page shouldn't be able to press keys for the user. Making both POST-only puts them behind the JSON check you already have. Neither index.mjs nor the page calls them, so it should be a small change on the helper side.

Merge status
The package ships a prebuilt helper.exe, so whatever the review says, it waits for the maintainer to look at it before merge. That's the repo's rule for binaries, not a judgement on this one. I haven't rebuilt helper.exe from source to compare, or run it on Windows.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants