fix(mcode-webui): round 8 — CORS tightening + cross-origin token-leak fix (supersedes #16) - #31
fix(mcode-webui): round 8 — CORS tightening + cross-origin token-leak fix (supersedes #16)#31modacker wants to merge 13 commits into
Conversation
Browser-based chat frontend for the mcode agent runtime. Streams
mcode acp / exec sessions with real-time tool events, plan review,
ask-user prompts, context usage, and quota. Zero npm dependencies;
runs on Node 22+.
- New plugin at plugins/Wzdhehe/mcode-webui/ per Agent Plugins 1.0
- plugin.json (10 white-listed top-level fields, 13 capabilities)
- skills/mcode-webui/SKILL.md (frontmatter name + description 343 chars)
- LICENSE (MIT)
- README.md + README.zh-CN.md (bilingual)
- references/SECURITY-NOTES.md (canonical security disclosure)
- docs/ (ARCHITECTURE, API, CAPABILITIES, DEVELOPMENT, TROUBLESHOOTING)
- server/, public/, test/ (real directory copies, kept in sync with
the project root at github.com/Wzdhehe/mcode-webui)
- PR_DESCRIPTION.md + CONTRIBUTING.md
Source: github.com/Wzdhehe/mcode-webui (v1.0.0 + doc polish)
Validate: OK plugin Wzdhehe/mcode-webui
Mirror of the source-repo follow-up: - SKILL.md frontmatter name back to mcode-webui (spec requires it to match the directory name) - Strip CR from UTF-8 text files so the official validator sees LF-only frontmatter - Revert product-name mcode->Mcode in CLI/trigger references
…owercase by spec)
…y mode Addresses PR MiniMax-AI#16 reviewer feedback (Please fix the authentication boundary before merge). New behavior: - Token auth gate: server-side constant-time token validation on every non-local /api/* request via new server/lib/auth.js. Token resolved from TOKEN env > settings.currentToken > auto-generated 32-hex on first start (printed to stdout once, never to .server.log, persisted to ~/.mcode-webui/settings.json with mode 0600). - LAN sub-card: 顶栏 LAN chip 下弹出子卡片, 4 个子功能 (read-only toggle, token rotation with SSE auth.token_rotated broadcast, token acknowledged state machine, 复制可分享 URL 含 token). - Read-only mode: 非本机 POST/DELETE 到 /api/* 返 403, 远程只能读. 顶栏红色脉动 chip 提示只读状态. /api/settings 例外 (escape hatch). - Top-bar read-only chip + bilingual single-page LAN reject page (zh + en stacked, dynamic PORT). Sub-mechanisms documented separately in CHANGELOG, README (× 2 langs), CAPABILITIES, SECURITY-NOTES. Tests: 372/372 pass. Lint: 0 warnings. Independent audit: FUNCTIONAL.
…on support Same commit as Wzdhehe/Mcode-webui ea896d1, mirrored to plugin layout for MiniMax-Code-Plugins registry. Round 2 audit (reviewer mentioned 'CORS/URL-token leakage considerations') found two related bugs: 1. L281: Access-Control-Allow-Headers only listed 'Content-Type', so any cross-origin fetch with 'Authorization: Bearer' would fail CORS preflight. 2. Gate 3 (token auth) had no exemption for OPTIONS preflight, so even with the L281 fix, OPTIONS preflight to /api/* would hit Gate 3 and return 401 (browsers cannot attach Authorization to a preflight). The real POST would never reach the server. Fixes: - server/router.js L281: Allow-Headers now lists 'Content-Type, Authorization' - server/router.js Gate 3: add req.method !== 'OPTIONS' exemption (matches Gate 4 read-only's existing pattern) - test/router-cors.test.js: 9 new tests covering CORS headers + Gate 3 preflight behavior. Tests: 381 pass / 0 fail. Lint: 0 warning. Independent audit: FUNCTIONAL.
…ke test + doc sync)
Mirror of Wzdhehe/Mcode-webui commits decceb6 + 91d0bb0 to plugin layout.
Round 3 review (reviewer: 'setTokenAuthEnabled load-time blocker' +
'add a startup/import smoke test that exercises the real server bootstrap')
found two real bugs plus 13 stale doc claims. All addressed:
Code fixes (commit decceb6):
- plugins/.../server/lib/auth.js: synced from root, now exports
setExpectedToken + setTokenAuthEnabled (mirror was stale since
v1.0.1 LAN sub-card commit 999115d — setTokenAuthEnabled is
imported by server.js:26, missing export was a load-time blocker)
- plugins/.../test/lib-auth.test.js: synced from root (4 new tests
for the setters + clean try/finally state reset)
- plugins/.../test/server-startup.test.js (new): spawns \
ode server.js\,
captures stdout/stderr, SIGTERMs after 2s, asserts no ESM load
errors and 'listening on' reached
Doc fixes (commit 91d0bb0):
- docs/API.md: remove availableInterfaces (v1.0.1 cleanup removed it
but doc still had it)
- docs/ARCHITECTURE.md: remove pushEvent from state-bus exports,
correct the mcodeCommandsCache claim (lives in state-bus.js not
acp-client.js), expand config.js exports list
- docs/DEVELOPMENT.md: remove pushEvent from example code + import +
transport-layer description
- plugins/.../references/SECURITY-NOTES.md: remove 'set-headers' and
'crash-now' debug endpoints claims (those endpoints never existed
in the v1.0.1 source)
- README.md / README.zh-CN.md / CHANGELOG.md / CONTRIBUTING.md /
plugins/.../README.zh-CN.md / scripts/verify.mjs: stale test counts
fixed (302/372 → 382 passing + 1 skipped, 383 total)
- plugins/.../package.json: validate:plugin and verify scripts added
(mirror was missing them; CONTRIBUTING.md references them)
- All 4 docs/{API,ARCHITECTURE,DEVELOPMENT,TROUBLESHOOTING}.md:
brought back in sync with root (mirror drift fixed)
Verified: lint 0 warning, ROOT vs mirror SHA256 match for all synced
docs and code files.
Tests: 382 passing + 1 skipped (383 total).
…n-canonical install layouts Mirror of Wzdhehe/Mcode-webui commit 4abf56c to plugin layout. Round 4 review (modacker follow-up on PR MiniMax-AI#16) found that server/lib/db.js's getMcodeBetterSqlite3() hardcoded \__dirname/../../../node_modules/@minimax-ai/code/node_modules/better-sqlite3\. This path only works in the canonical dev layout where webui is at \<mcode-root>/webui/\. On macOS, registry install, or any non-canonical layout, mavis returns null → DELETE /api/sessions/:id fails (500) → 5 db.js tests fail on macOS reviewer. Fix: candidate-list fallback with priority: 1. \ env (explicit user override) 2. <MCODE_CMD>/../../node_modules/@minimax-ai/code/node_modules/better-sqlite3 3. <__dirname>/../../../node_modules/@minimax-ai/code/node_modules/better-sqlite3 (dev layout fallback — unchanged) Changes: - plugins/.../server/lib/db.js: replaced single hardcoded path with candidate-list fallback. Exports _getBetterSqlite3Candidates() for install-layout tests. - plugins/.../test/lib-db-resolver.test.js (new): 5 tests covering env override priority, dev layout fallback, candidate count invariant, MCODE_CMD branch. - plugins/.../references/SECURITY-NOTES.md §7: documents MCODE_BETTER_SQLITE3 env override next to existing MCODE_RUNTIME_DB and MCODE_WEBUI_SETTINGS_PATH entries. Tests: 387 passing / 0 failing / 1 skipped. Lint: 0 warnings. Independent audit: PASS.
Round 5 addresses the two remaining CHANGES_REQUESTED items from hetaoBackend's 2026-08-27 01:34Z review on PR MiniMax-AI#16 (commit 99dd587). 1. server/lib/db.js::_getBetterSqlite3Candidates Round 4 used `join(MCODE_CMD, '..', '..', ...)` which treated the mcode executable file as a directory. The fix uses `dirname(mcodeCmd)` directly: mcode.cmd is a file in the install dir, and the package's node_modules/ sits next to it. Round 4 accidentally went 3 levels above the install root (e.g. `/Users/moc/node_modules/...` instead of `/Users/moc/.minimax-code/node_modules/...`). The function is now parameterized as `_getBetterSqlite3Candidates({ mcodeCmd = MCODE_CMD } = {})` so install-layout tests can simulate any layout without mutating the module-level constant. 2. server/lib/db.js::deleteMcodeSessionFromDb The db-path check now runs BEFORE better-sqlite3 load. Round 4 had these reversed: callers passing a missing MCODE_RUNTIME_DB got `better_sqlite3_not_loaded` even when the db path was the actual problem. The test in lib-db.test.js:65 already documents the expected order (`mcode_db_not_found` must win) — implementation now matches. 3. test/lib-db-resolver.test.js - Test 5 ('MCODE_CMD-derived candidate is present...') updated to expect the corrected `dirname(MCODE_CMD)` prefix instead of the round 4 buggy `dirname(MCODE_CMD)/..` prefix. Adds a guard assertion that the buggy prefix is NOT used. - 3 new install-layout tests covering: • mcode binary at <install>/mcode.cmd → <install>/node_modules/... • mcode binary at /usr/local/bin/mcode → /usr/local/bin/node_modules/... • MCODE_CMD = 'mcode' (PATH placeholder) → no MCODE_CMD-derived candidate Test results in modacker env (no mcode install): • lib-db-resolver.test.js: 8/8 pass (5 existing + 3 new) • lib-db.test.js input validation: 5/5 pass • lib-db.test.js happy path / table-missing: 2/8 fail with reason='better_sqlite3_not_loaded' — environmental (no real better-sqlite3 binary at any candidate path). The fix doesn't cause this; these tests require a real mcode install to pass. Reviewer should re-run in a mcode-installed env. Refs: hetaoBackend review 2026-08-27 01:34Z on MiniMax-AI#16
…egration tests on macOS)
Round 5 closed two of hetaoBackend's open items, but the candidate
list still missed the actual mcode install layout on a real macOS dev
box (where the user is running mcode as their agent runtime).
Symptom: MCODE_CMD in config.js resolves to the PATH placeholder
'mcode' (not a full path) because the detection chain only looks for
'mcode.cmd' / 'MCODE_ROOT/mcode.cmd' / '~/.minimax-code/mcode.cmd',
and on macOS the binary is just 'mcode' at '~/.minimax-code/bin/mcode'.
With MCODE_CMD='mcode' the MCODE_CMD-derived branch is skipped
altogether, and the dev-layout fallback points at the plugin source
tree, not the real mcode package. Integration tests that actually
load better-sqlite3 fail with reason='better_sqlite3_not_loaded'.
Fix: emit three additional candidates.
1. From MCODE_CMD (when it IS a real path), try BOTH the npm-style
layout (<root>/bin/mcode → <root>/lib/node_modules/...) and the
flat layout (<root>/mcode → <root>/node_modules/...). Round 5 only
emitted the flat one.
2. Always emit <home>/.minimax-code/lib/node_modules/... as an
unconditional standard-install candidate. This is where mcode
actually ships its bundled deps on macOS dev installs
(verified: 'find ~/.minimax-code' shows
lib/node_modules/@minimax-ai/code/node_modules/better-sqlite3).
The function is now parameterized as
_getBetterSqlite3Candidates({ mcodeCmd, home } = {}) so tests
can simulate any install layout without env mutation.
Tests added:
- standard ~/.minimax-code/lib/node_modules/... is always tried
- mcode at <root>/bin/mcode emits BOTH npm-style and flat candidates
Test results in modacker env (real mcode install, no env override):
Before round 6: 387 pass / 4 fail / 2 skipped (all 4 fails =
better_sqlite3_not_loaded on the 2 happy-path integration tests
in lib-db.test.js and 2 in sessions.test.js)
After round 6: 391 pass / 0 fail / 2 skipped — all integration
tests now find and load better-sqlite3 via the standard-install
candidate
Refs: MiniMax-AI#16 round 5 follow-up, modacker env verification
5 个独立修复 + 1 个外部 key 源特性,全部端到端验证 + 测试覆盖。
=== Core bug fixes ===
1. SSE clobber wiped quotaEnabled on every push
- server/lib/state-bus.js: 3 snapshot builders + pushOnlineCount now
include quotaEnabled / hasTokenPlanKey / tokenPlanApiKeyMasked
- server/routes/state.js: handleEvents (SSE first push) + handleState
(GET /api/state fallback) carry the same fields
- public/app/state.js: SSE onmessage defensively preserves these
three (mirrors the askUserAnswers / mcodeSessions pattern)
- Root cause was the appearance-card '显示套餐用量' toggle looking
like a no-op: server's snapshot was missing the field, so the
next SSE onmessage state=JSON.parse(ev.data) replaced local
state.quotaEnabled with undefined, btn.classList.toggle re-added
usage-hidden, button hid. All pushed on every /api/settings POST.
2. 4 missing imports in events.js (closeApiKeyModal / openApiKeyModal /
setLeftOpen / setRightOpen) — ReferenceError at attachEvents init.
Previously the modal handlers crashed the whole JS bootstrap.
3. usage.js parser read wrong field path + typo
- Read data?.current_interval_remaining_percent (top level) which
is always undefined. Real API nests it under
model_remains[i].current_interval_remaining_percent.
- Used 'pct' suffix instead of 'percent':
data?.current_weekly_remaining_pct (always undefined)
correct:
data?.current_weekly_remaining_percent
- Extracted to pure parseTokenPlanResponse(data, cs) for testability.
- Now also stores cs.usage.raw (8 KB cap) for future debugging.
- Real key 端到端验证: fiveHourPercent=25, weekly=51%,
fiveHourReset=1787864400, error=None, raw=990 bytes.
4. api-key-modal type=password made the whole page a 'credential form'
for Chrome autofill, so every text input on the page got email
autofill injected (including the search input).
- type=password → type=text + secret-input class
- CSS: -webkit-text-security: disc + monospace + letter-spacing
(visually a password box, semantically NOT a password field)
- This was the actual root cause of the stubborn autofill.
The other mitigations (readonly / autocomplete=off / data-1p-ignore)
were all bandaids; removing the page-level credential signal is
the real fix.
5. 5 <label class='lan-card-row-label'> had no associated form field
(DevTools a11y warning). Converted to <div> (they're row titles,
not form labels) and added explicit for= on each toggle's label.
=== Feature ===
6. Token Plan key can now be injected via env or file (priority chain
env > file > settings.json), per user request.
- env: MCODE_WEBUI_TOKEN_PLAN_KEY (env always wins, like the
existing process.env.TOKEN pattern for the LAN auth token)
- file: ~/.minimax/credentials/token-plan.json (raw or
{"key":"..."} JSON), path overridable via
MCODE_WEBUI_TOKEN_PLAN_KEY_FILE
- When env or file provides a key, quotaEnabled auto-enables so
the user doesn't have to flip the toggle.
- Webui surfaces the source ('env' / 'file' / 'settings') in the
popover + modal, hides the 'delete' button when the key is
external (operator must unset at the source).
=== i18n / UX ===
- 启用 → 显示套餐用量 (and synced en 'Enabled' → 'Show usage')
- New strings: quota_source_env / quota_source_file / delete disabled
hint / input placeholder hints for external sources
- Help text: was '关闭后,按钮仍显示,但点开是降级提示' (old behavior),
now '关闭后,套餐用量按钮在主界面消失' (matches current behavior)
=== Tests ===
- 3 new quota-field tests in test/state-bus.test.js (per-cid / broadcast /
pushOnlineCount all carry the 3 quota fields, setQuotaEnabled(false)
clears them in the next push)
- 6 new external-key-source tests in test/state-bus.test.js (settings /
file / env / live downgrade chain / broadcast / empty state)
- 7 new parser tests in test/usage.test.js (real API fixture pins
exact field names + 'general' model picking + end_time ms→s)
- test/_setup.js mock: mirrors real priority chain in getTokenPlanApiKey
+ getTokenPlanApiKeySource + maskTokenPlanKey so tests don't lie
Full suite: 409 pass / 0 fail / 2 skipped (was 393 / 0 / 2 → +16 tests).
Files: 13 modified + 1 new test + .gitignore (drop webui runtime
artifacts .server.err / .webui-sessions.json). Excluded from this
commit: 2 docs (REVIEW-SiHankor-baselines + BORROW-dsh) — modacker
perspective work product, not part of the PR.
Co-authored-by: mavis <noreply@example.com>
… fix hetaoBackend reported on 2026-09-01 against PR MiniMax-AI#23 head 091dec5 that a malicious page at https://evil.example could exfiltrate the operator's bootstrap auth token from a webui instance running on the same machine via a cross-origin fetch('/api/settings'). Three combined properties created the leak: 1. router.js:279 Access-Control-Allow-Origin: * (any cross-origin page could read responses — no CORS preflight needed for simple GET/POST with JSON body) 2. auth.js:113 isRequestAuthorized() bypassed Gate 3 for isLocalRequest — the request arrived over loopback (127.0.0.1), so no token was required regardless of where the request originated in the browser 3. routes/settings.js + state-bus.js the bootstrap token was included in GET /api/settings (when !tokenAcknowledged), the POST resetToken response, and the auth.token_rotated SSE event payload This commit closes the leak by: * Removing Access-Control-Allow-Origin: * (router.js: setCorsHeaders helper). New behavior: same-origin OR in MCODE_WEBUI_ALLOWED_ORIGINS env allowlist → echo Origin + Vary; otherwise omit CORS headers (browsers block the cross-origin read). OPTIONS preflight still short-circuits before any gate (regression guard). * Making isRequestAuthorized require a valid token for cross-origin requests even when they arrive over loopback (auth.js: new isCrossOriginRequest helper). Same-origin / no-Origin (server-to- server, curl, mcode acp subprocess) keeps the isLocalRequest fast path. * Removing currentToken from every HTTP response and SSE state payload (settings.js getSettingsSnapshot, routes/settings.js resetToken response, state-bus.js pushStateFor + ensureMcodeSessionsFetchedAndPush + pushOnlineCount, plus broadcastTokenRotated which now sends JSON {rotated:true, at:ms} instead of the raw new token). * SPA changes: state.js now clears HEADERS.Authorization + localStorage on auth.token_rotated (no auto-update of HEADERS) and dispatches a window CustomEvent 'webui:token_rotated'. render.js listens for it and shows an 8s toast telling the operator to read the new value from server stdout or ~/.mcode-webui/settings.json. The LAN-card 'token display' placeholder now points to the out-of-band delivery channels. i18n.js adds lan_card_token_saved_v2 (zh+en) and token_rotated_toast (zh+en). UX trade-off: a small one-click convenience (auto-HEADERS-update on rotation) is gone. The operator must re-open the URL with the new ?token=... value. See SECURITY-NOTES §10 for the full contract. Test plan: * Existing csrf-token-disclosure.test.js (added in 091dec5 with 4 blockers) — 3 of 4 RED pre-commit, all 4 GREEN post-commit. The 4th (blocker#4: cross-origin DELETE /api/sessions/:id) was already GREEN pre-commit (404 fallback) and remains GREEN — the underlying Gate 3 tightening now blocks the real cross-origin DELETE too. * router-cors.test.js L279 "Allow-Origin remains wildcard" replaced with a NEGATIVE assertion (source MUST NOT contain '*') and a setCorsHeaders + MCODE_WEBUI_ALLOWED_ORIGINS presence test. L280 + L281 unchanged. * routes-settings.test.js resetToken:true test updated to assert the response does NOT include currentToken and that a 'hint' field points to stdout / settings.json. * state-bus.test.js new describe 'state-bus — currentToken removal (round 8)': per-cid push + __broadcast__ push + broadcastTokenRotated all verified to NOT carry the token value. * BASELINE-2026-09-04.md captures the pre-fix test counts and PoC output for the reviewer to diff against. Drive-by: eslint.config.mjs was at the upstream/ root and imported @eslint/js + globals, but the devDeps live in plugins/Wzdhehe/mcode- webui/package.json — npm install in the subdir didn't satisfy the upstream-root resolve. Moved the config into the mcode-webui subdir so npm run lint works (0 errors, 9 pre-existing 'imported but never used' warnings in test/ that are not new in this commit). Post-commit status: * npm test → 417 / 415 / 0 / 2 (no regressions, +3 new tests) * npm run lint → 0 errors, 9 warnings (none new in this commit) * poc-csrf.mjs → exit 0, 'PoC did not reproduce the blocker' * Same-origin SPA requests (curl with Origin: http://127.0.0.1:PORT) still 200 + ACAO echo + currentToken: '' (field kept for back-compat)
hetaoBackend
left a comment
There was a problem hiding this comment.
Current head 16ebfab substantially tightens CORS and removes the currentToken field, and the local unit suite reports 411 pass / 0 fail / 2 skipped. A material token-disclosure blocker remains: server/lib/settings.js:691-700,735-736 puts the bearer credential into lanUrlWithToken; server/routes/settings.js:28-30 returns the full settings snapshot; server/router.js:337-340 exempts /api/settings from the LAN guard; and server/lib/auth.js:130-139 authorizes loopback requests without a token. The settings response therefore still exposes a token-bearing URL through an unauthenticated local bootstrap surface, contrary to the security comments claiming the token is never echoed over HTTP. Remove lanUrlWithToken from HTTP/SSE snapshots or expose it only through an explicitly authenticated user-triggered path. Also package.json advertises validate:plugin and verify scripts whose files are absent; either restore those files or correct the package scripts. [code]smith is SKIPPED.
Supersedes #16 — bring marketplace mcode-webui in sync with upstream round 8
The previous PR #16 (564af66, CORS preflight) is now stale: the source-of-truth
Wzdhehe/Mcode-webuilanded commitsffacf58(round 3 auth.js setters) +afd4167/0dbdacd/c448424(rounds 4-6 db.js better-sqlite3 path resolver) +091dec5(Token Plan key integration) +7acdfe9(round 8 CSRF / bootstrap-token-disclosure fix), all of which are missing from PR #16.The marketplace copy at
plugins/Wzdhehe/mcode-webui/should match the upstream. This PR brings the marketplace in sync with the upstream state at the round 8 merge commit (Wzdhehe/Mcode-webui#6, merged 2026-09-04 by Wzdhehe at072220c).What's in this PR
Cherry-picked from
Wzdhehe/Mcode-webuimain(7acdfe9):server/router.js— newsetCorsHeaders(req, res)helper;Access-Control-Allow-Origin: *removed; CORS is now per-origin withMCODE_WEBUI_ALLOWED_ORIGINSenv allowlist + same-origin defaultserver/lib/auth.js— newisCrossOriginRequest;isRequestAuthorizedno longer bypasses Gate 3 for cross-origin requests over loopbackserver/lib/settings.js—getSettingsSnapshotno longer returnscurrentToken(always empty string, field kept for back-compat)server/lib/state-bus.js— all 4 SSE push paths (per-cid / broadcast / online-count / mcodeSessionsFetchedAndPush) emitcurrentToken: "";broadcastTokenRotatedpayload changed from raw token toJSON.stringify({rotated: true, at: <ms>})server/routes/settings.js—POST /api/settings {resetToken: true}no longer returnscurrentToken; only{ok, changed, tokenRotated, hint, tokenRotatedAt}references/SECURITY-NOTES.md—§9row updated + new§10"Cross-origin request handling" addedpublic/app/state.js—auth.token_rotatedhandler clearsHEADERS.Authorization+localStorageand dispatcheswebui:token_rotatedCustomEventpublic/app/render.js— listens forwebui:token_rotatedand shows an 8s toastpublic/app/i18n.js—lan_card_token_saved_v2(zh+en) +token_rotated_toast(zh+en)test/router-cors.test.js— L279 "Allow-Origin remains wildcard" replaced with NEGATIVE assertion +setCorsHeaderspresence testtest/routes-settings.test.js—resetToken:truetest asserts response does NOT includecurrentTokentest/state-bus.test.js— newstate-bus — currentToken removal (round 8)describe block (3 tests)+ BASELINE-2026-09-04.md— pre-fix snapshotplugins/Wzdhehe/mcode-webui/eslint.config.mjs)Threat model (what this closes)
The hetaoBackend 2026-09-01 report against PR #23 head
091dec5identified a CSRF / bootstrap-token-disclosure vulnerability: a malicious page athttps://evil.examplecould exfiltrate the operator's auth token from a webui instance running on the same machine viafetch('http://127.0.0.1:PORT/api/settings'). Three combined properties created the leak — all three are now closed:Access-Control-Allow-Origin: *→ per-origin with allowlistisRequestAuthorizedbypassed Gate 3 for cross-origin over loopback → now requires valid tokencurrentTokenin HTTP responses + SSE state pushes → removed from all channelsThe new value is delivered out-of-band: server stdout +
~/.mcode-webui/settings.json.Test results
PoC verification
poc-csrf.mjs(the script used to report the blocker on 2026-09-01) on this branch:UX trade-off
A small one-click convenience (auto-HEADERS-update on token rotation) is gone. The operator must now:
/api/settingsor the UI button)settings.jsprints it on rotation) OR from~/.mcode-webui/settings.json?token=<new-value>appendedThe SPA shows an 8-second toast guiding the user. See
SECURITY-NOTES.md§10 for the full contract.Relationship to other PRs
072220c). This PR mirrors that merge into the marketplace layout.564af66. hetaoBackend has 3CHANGES_REQUESTEDreviews on it. The author (Wzdhehe) should close Add plugin: mcode-webui (Wzdhehe) #16 in favor of this PR.Reviewer checklist for the marketplace maintainer
npm test→ 0 fail (verified locally: 411/0/2)npm run lint→ 0 errors (verified: 0 errors, 9 pre-existing warnings)poc-csrf.mjsagainst the built server → no cross-origin token leak (verified)Wzdhehe/Mcode-webui#6(round 8 commit7acdfe9plus the merge base92cae0c)Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.