Skip to content

fix(mcode-webui): round 8 — CORS tightening + cross-origin token-leak fix (supersedes #16) - #31

Open
modacker wants to merge 13 commits into
MiniMax-AI:mainfrom
modacker:fix/round8-mp-csrf-from-upstream
Open

fix(mcode-webui): round 8 — CORS tightening + cross-origin token-leak fix (supersedes #16)#31
modacker wants to merge 13 commits into
MiniMax-AI:mainfrom
modacker:fix/round8-mp-csrf-from-upstream

Conversation

@modacker

@modacker modacker commented Sep 4, 2026

Copy link
Copy Markdown

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-webui landed commits ffacf58 (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 at 072220c).

What's in this PR

Cherry-picked from Wzdhehe/Mcode-webui main (7acdfe9):

  • server/router.js — new setCorsHeaders(req, res) helper; Access-Control-Allow-Origin: * removed; CORS is now per-origin with MCODE_WEBUI_ALLOWED_ORIGINS env allowlist + same-origin default
  • server/lib/auth.js — new isCrossOriginRequest; isRequestAuthorized no longer bypasses Gate 3 for cross-origin requests over loopback
  • server/lib/settings.jsgetSettingsSnapshot no longer returns currentToken (always empty string, field kept for back-compat)
  • server/lib/state-bus.js — all 4 SSE push paths (per-cid / broadcast / online-count / mcodeSessionsFetchedAndPush) emit currentToken: ""; broadcastTokenRotated payload changed from raw token to JSON.stringify({rotated: true, at: <ms>})
  • server/routes/settings.jsPOST /api/settings {resetToken: true} no longer returns currentToken; only {ok, changed, tokenRotated, hint, tokenRotatedAt}
  • references/SECURITY-NOTES.md§9 row updated + new §10 "Cross-origin request handling" added
  • public/app/state.jsauth.token_rotated handler clears HEADERS.Authorization + localStorage and dispatches webui:token_rotated CustomEvent
  • public/app/render.js — listens for webui:token_rotated and shows an 8s toast
  • public/app/i18n.jslan_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 + setCorsHeaders presence test
  • test/routes-settings.test.jsresetToken:true test asserts response does NOT include currentToken
  • test/state-bus.test.js — new state-bus — currentToken removal (round 8) describe block (3 tests)
  • + BASELINE-2026-09-04.md — pre-fix snapshot
  • eslint config path fix (moved from repo root to plugins/Wzdhehe/mcode-webui/eslint.config.mjs)

Threat model (what this closes)

The hetaoBackend 2026-09-01 report against PR #23 head 091dec5 identified a CSRF / bootstrap-token-disclosure vulnerability: a malicious page at https://evil.example could exfiltrate the operator's auth token from a webui instance running on the same machine via fetch('http://127.0.0.1:PORT/api/settings'). Three combined properties created the leak — all three are now closed:

  1. Access-Control-Allow-Origin: * → per-origin with allowlist
  2. isRequestAuthorized bypassed Gate 3 for cross-origin over loopback → now requires valid token
  3. currentToken in HTTP responses + SSE state pushes → removed from all channels

The new value is delivered out-of-band: server stdout + ~/.mcode-webui/settings.json.

Test results

$ npm test
ℹ tests 413
ℹ pass 411
ℹ fail 0
ℹ skipped 2
ℹ duration_ms ~2400

$ npm run lint
✖ 9 problems (0 errors, 9 warnings — all pre-existing)

PoC verification

poc-csrf.mjs (the script used to report the blocker on 2026-09-01) on this branch:

=== SUMMARY ===
  CORS allows cross-origin read:  NO (safe)
  Local bypass on cross-origin:   NO (401)
  Bootstrap token leak count:     0/2
>>> PoC did not reproduce the blocker
exit 0

UX trade-off

A small one-click convenience (auto-HEADERS-update on token rotation) is gone. The operator must now:

  1. Trigger the rotation (POST /api/settings or the UI button)
  2. Read the new value from server stdout (where settings.js prints it on rotation) OR from ~/.mcode-webui/settings.json
  3. Re-open the webui URL with ?token=<new-value> appended

The SPA shows an 8-second toast guiding the user. See SECURITY-NOTES.md §10 for the full contract.

Relationship to other PRs

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.mjs against the built server → no cross-origin token leak (verified)
  • Diff matches Wzdhehe/Mcode-webui#6 (round 8 commit 7acdfe9 plus the merge base 92cae0c)
  • Close Add plugin: mcode-webui (Wzdhehe) #16 in favor of this PR

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Wzdhehe and others added 13 commits August 27, 2026 18:05
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
…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)
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