Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions plugins/Wzdhehe/mcode-webui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

# v2026-08-28 modacker: webui runtime artifacts (server.err + sessions json)
.server.err
.webui-sessions.json
126 changes: 126 additions & 0 deletions plugins/Wzdhehe/mcode-webui/BASELINE-2026-09-04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# BASELINE — mcode-webui pre-fix snapshot

> 采集日期:2026-09-04 20:55 CST
> 工作目录:`/Users/moc/workspaces/Mcode-webui-sync/upstream` 分支 `fix/cve-csrf-token-leak-2026-09`
> 起点 commit:`92cae0c` (== `Wzdhehe/Mcode-webui` PR #23 head `091dec5` "Token Plan key integration end-to-end")
> 环境:macOS Darwin, Node v25.9.0, sqlite3 系统默认, mcode 0.2.4, mavis 0.1.0+
> 工具:npm 11.x, `node --experimental-test-module-mocks --test test/*.test.js`

---

## 1. npm test

```
ℹ tests 413
ℹ suites 105
ℹ pass 408
ℹ fail 3
ℹ cancelled 0
ℹ skipped 2
ℹ todo 0
ℹ duration_ms 2313.78
```

### 3 个 fail 全部来自 `test/csrf-token-disclosure.test.js`

| # | 测试 | 行 | 状态 | 现象 |
|---|---|---|---|---|
| blocker#1 | `GET /api/settings` 跨源不得返回 200 带 token | L160 | ❌ FAIL | status=200, 响应体含 `currentToken: 8a520aae769fbf4ea6eac2227fa59ba6` |
| blocker#2 | `POST /api/settings` 跨源不得返回 200 带 token | L172 | ❌ FAIL | status=200, 响应体含 `currentToken` |
| blocker#3 | 跨源响应 `Access-Control-Allow-Origin` 不得为 `*` | L182 | ❌ FAIL | header = `*`(裸星号) |
| blocker#4 | `DELETE /api/sessions/:id` 跨源须 401/403/404 | L193 | ✅ PASS | status=404(session 不存在;token gate 旁路了但 404 兜底) |

> 注释:blocker#4 在测试用例里 PASS 是因为路径不存在返回 404 —— 但**真实删除路径**上仍会被跨源执行。是 PoC 没覆盖到的盲点,第二段顺带修。

---

## 2. 独立 PoC 复现(hetaoBackend 2026-09-01 01:25Z 报告)

跑 `node ~/workspaces/Mcode-webui-sync/poc-csrf.mjs <plugin-dir>`(脚本在 modacker 本地):

```
== CSRF / token-disclosure PoC ==
plugin dir: /Users/moc/workspaces/Mcode-webui-sync/upstream/plugins/Wzdhehe/mcode-webui
settings dir: /var/folders/xp/.../poc-csrf-QqSVKt
port: 18080

bootstrap token (from operator-only settings file or first GET):
3801ed9d...725a

=== TEST 1: GET /api/settings, Origin: https://evil.example ===
status: 200
access-control-allow-origin: *
access-control-allow-methods: GET, POST, OPTIONS, DELETE
access-control-allow-headers: Content-Type, Authorization
content-type: application/json; charset=utf-8
body[0..400]: {"ok":true,"lanBroadcast":true,"readOnly":false,"tokenEnabled":true,
"tokenAcknowledged":false,"currentToken":"3801ed9d7aa8fd1d4846f7472efb725a",...}
[LEAKED] bootstrap token in response body: true

=== TEST 2: POST /api/settings, Origin: https://evil.example ===
status: 200, body 含 currentToken: 3801ed9d...725a
[LEAKED] bootstrap token in response body: true

=== TEST 3: DELETE /api/sessions/nonexistent, Origin: https://evil.example ===
status: 404, body {"ok":false,"error":"session not found"}(DELETE 路径盲点)

=== SUMMARY ===
CORS allows cross-origin read: YES (vulnerable)
Local bypass on cross-origin: YES (vulnerable)
Bootstrap token leak count: 2/2

>>> CSRF / token-disclosure blocker CONFIRMED
>>> hetaoBackend report (2026-09-01 01:25Z) reproduced
```

PoC `process.exitCode = 2`(vulnerability confirmed),但被 `tail` 截断后看到 shell `$?` = 0;真实 node 退出码 = 2。

---

## 3. npm run lint

```
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@eslint/js' imported from
/Users/moc/workspaces/Mcode-webui-sync/upstream/eslint.config.mjs
```

**根因**:`eslint.config.mjs` 位于 `upstream/` 根目录,但 `@eslint/js` / `globals` 的 `devDependencies` 在 `upstream/plugins/Wzdhehe/mcode-webui/package.json` 里。`npm install` 在子目录跑,不会把 devDeps 装到根的 `node_modules/`,ESLint 找不到包。

**严重性**:lint 完全不可跑。这跟 CSRF 修复**无关**,是 pre-existing 路径错位。

**处置**:列入第二段同步修的列表(不阻塞 CSRF 修,但同 PR 改掉)。

---

## 4. 根因定位(4 个文件)

| 文件 | 行 | 问题 |
|---|---|---|
| `server/router.js` | 279 | `res.setHeader("Access-Control-Allow-Origin", "*");`(裸星号) |
| `server/lib/auth.js` | Gate 3 入口 | `isLocalRequest(req)` 命中即绕过 token 校验(跨源 127.0.0.1 同样命中) |
| `server/routes/settings.js` | (待 grep) | `GET /api/settings` 响应里塞 `currentToken`(`tokenAcknowledged === false` 时) |
| `server/lib/state-bus.js` | `pushStateFor` (待 grep) | SSE state push 也带 `currentToken`(**PoC 没覆盖**,但同源浏览器 EventSource 也会订阅到) |

---

## 5. 修后判定线(机械可证)

修复后必须满足:
1. `npm test` → `tests 413 / pass 413 / fail 0 / skipped 2`(4 个 blocker 全绿)
2. `poc-csrf.mjs` → exit code 0 + "PoC did not reproduce the blocker"
3. `npm run lint` → 0 errors, 0 warnings(先修路径错位)
4. 人工复测:clean checkout 跑 `node server.js` 仍能正常 listening on / 静态资源 / `/api/health`

---

## 6. 范围声明

- 起点:`92cae0c` 干净 HEAD
- 不动 `MiniMax-Code-Plugins`(marketplace 仓,用户明确禁止)
- 修复落点:`Wzdhehe/Mcode-webui`(用户已加入协作者)
- 工作分支:`fix/cve-csrf-token-leak-2026-09`(已创建,**未推送**)
- 后续 PR 标题(计划):`fix(mcode-webui): round 8 — CORS tightening + cross-origin token-leak fix`

---

*baseline locked 2026-09-04 20:55 CST.*
190 changes: 162 additions & 28 deletions plugins/Wzdhehe/mcode-webui/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,171 @@
# Contributing to Mcode-webui plugin
# Contributing to Mcode Web UI

This is the packaged plugin view of the project. The full
contribution guide lives in the **source repo**:
Thanks for your interest in Mcode Web UI! This document covers
the day-to-day contribution workflow. For the bigger picture (plugin
packaging, release process), see [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md)
and [`plugins/Wzdhehe/mcode-webui/README.md`](plugins/Wzdhehe/mcode-webui/README.md).

**[github.com/Wzdhehe/Mcode-webui → CONTRIBUTING.md](https://github.com/Wzdhehe/Mcode-webui/blob/main/CONTRIBUTING.md)**
## Code of conduct

## Quick reference
Be kind. We review for substance, not for style preferences. If a
change makes the webui more correct / faster / easier to use, it's
in scope.

| Need to … | Read |
|-----------|------|
| Add a route, event, or UI panel | [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) |
| Update a config / env var | [server/lib/config.js](server/lib/config.js) + [docs/API.md](docs/API.md) |
| Bump the version | `package.json` (root + plugin copy) + `plugin.json` |
| Update capability list | [docs/CAPABILITIES.md](docs/CAPABILITIES.md) + `plugin.json#extensions.capabilities` |
| Change a security disclosure | [references/SECURITY-NOTES.md](references/SECURITY-NOTES.md) (the single source of truth) |
## Development setup

## Sync rule
Requirements:

The plugin tree here (`server/`, `public/`, `test/`, `docs/`) is a
**real copy** of the source-repo root. When you change a file at
the root, mirror the same change here in the same commit, or run
`npm run package:plugin` at the source repo to regenerate the
plugin tree.
- **Node 22.19+** (uses `node:test`, `URL.parse`, `Blob.stream`)
- **Mcode CLI 0.1.4+** on `PATH` (or `MCODE_CMD` pointing to it)
- A POSIX-like shell on Windows: PowerShell 7+ or Git Bash

## Submitting to the community registry
Clone and run:

The official
[MiniMax-Code-Plugins](https://github.com/MiniMax-AI/MiniMax-Code-Plugins)
repo accepts plugin submissions as folders under
`plugins/<author>/<plugin-name>/`. The `plugins/Wzdhehe/mcode-webui/`
tree in this repo is the unit of submission — fork the registry,
copy this folder in, open a PR.
```bash
git clone https://github.com/Wzdhehe/Mcode-webui.git
cd Mcode-webui
npm install # only devDeps (eslint, prettier, c8)
npm test # 382 unit tests + 1 skipped (383 total)
npm run lint # eslint flat config, must be 0 warnings
npm run dev # node server.js
# → http://127.0.0.1:8080/
```

The official gate is `npm run check` at the registry root. This
repo ships a mirror (`npm run validate:plugin`) that runs the same
checks locally before you push.
`npm test` and `npm run lint` **must pass** before opening a PR.

## Repository layout

This repo has a **dual layout** — both copies are kept in sync:

```
Mcode-webui/ # ← the development tree (root)
├── server/ public/ test/ # Node + frontend + tests
├── docs/ # ARCHITECTURE, API, CAPABILITIES, …
├── acp.mjs, server.js, package.json
└── plugins/Wzdhehe/mcode-webui/ # ← the plugin artifact
├── server/ public/ test/ # ↑ real copies, not symlinks
├── docs/ references/ skills/
├── plugin.json package.json LICENSE
├── README.md PR_DESCRIPTION.md
└── SKILL.md # lives at skills/mcode-webui/SKILL.md
```

**Why two copies?** The community plugin registry takes the
`plugins/.../Mcode-webui/` tree as the submission. We keep it as a
real directory copy (not a junction or symlink — those break
zip-packaging and confuse `git log`).

`npm run setup:plugin` is a no-op on the current layout (it used to
create junctions; the trees have been expanded since).

## Editing flow

1. **Edit at the repo root** (`server/`, `public/`, `test/`).
2. **Mirror the change to the plugin tree** — copy the changed files
from `<root>/server/...` to `plugins/Wzdhehe/mcode-webui/server/...`,
and the same for `public/`, `test/`, `docs/`.
(The `package:plugin` script does this for you, but a
per-PR manual sync is fine for small changes.)
3. **Run the gate**:
```bash
npm test
npm run lint
npm run validate:plugin
```
4. **Commit** with a conventional message (see below).
5. **Push** to a feature branch and open a PR.

## Commit message format

We loosely follow [Conventional Commits](https://www.conventionalcommits.org/):

```
<type>(<scope>): <subject>

<body — explain WHY, not what>
<footer — refs, BREAKING CHANGE, etc.>
```

Common types:

- `feat:` — new feature
- `fix:` — bug fix
- `refactor:` — internal change, no behavior diff
- `test:` — test-only change
- `docs:` — documentation only
- `chore:` — build / CI / tooling

Scope is the area (`server`, `public`, `plugin`, `acp`, `test`, `docs`).

Example:

```
fix(acp): retry session/fork once on "Method not found"

mcode 0.1.5 returns "Method not found" for session/fork on the
first attempt but accepts it on retry. One retry is enough in
practice; log + continue.
```

## Pull request checklist

- [ ] `npm test` passes (382 + 1 skipped)
- [ ] `npm run lint` is clean (0 warnings)
- [ ] `npm run validate:plugin` is clean (mirrors official gate)
- [ ] Plugin tree (`plugins/.../Mcode-webui/`) is in sync with root
- [ ] No personal data in commit content (no IPs, no usernames, no
real session IDs)
- [ ] New env vars documented in `docs/API.md` and `plugin.json`
- [ ] New endpoints / events documented in `docs/API.md`
- [ ] `CHANGELOG.md` updated under an "Unreleased" section
- [ ] If destructive behavior changes, the security note
`plugins/.../references/SECURITY-NOTES.md` is updated (and
`plugin.json`'s `extensions.securityNotes` summary stays in sync)

## Adding a new route / event / panel

See [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) for recipes. The
short version:

- **Route**: drop a file in `server/routes/<name>.js` exporting
`(req, res, deps) => …`, register in `server/router.js`.
- **SSE event**: emit via `state-bus` in the route; consume in
`public/app/render.js`.
- **UI panel**: add a `state` slice in `public/app/state.js`,
a renderer in `public/app/render.js`, a handler in
`public/app/events.js`, and an i18n key in `public/app/i18n.js`.

## Style guide

- **ESM only** — no CommonJS, no `require()`.
- **No runtime npm deps** — only `devDependencies`. Everything
runtime must be Node 22+ stdlib.
- **No silent failures** — every catch either re-throws, returns
an explicit error response, or logs a warning with a `console.warn`
tag. No `try { … } catch {}` blocks.
- **No fake UI buttons** — if mcode acp doesn't support a method
(see `docs/CAPABILITIES.md`), don't render a button that
pretends to work. Use a toast + skip.
- **i18n first** — every user-visible string in the frontend goes
through `i18n.t()`. No inline English / Chinese literals.
- **Token-aware error messages** — never echo the request URL
or headers into error bodies (token leak risk).

## Release process

1. Bump `version` in `package.json` (root + plugin copy).
2. Move "Unreleased" section in `CHANGELOG.md` to a dated
versioned section.
3. `npm run package:plugin` — produces `dist/Wzdhehe/mcode-webui/`
+ `dist/Wzdhehe/Mcode-webui.zip`.
4. Open a PR to the community registry
[`MiniMax-AI/MiniMax-Code-Plugins`](https://github.com/MiniMax-AI/MiniMax-Code-Plugins)
adding only the `plugins/Wzdhehe/mcode-webui/` tree (per the
"one folder = one plugin" model — see the official README).
5. Tag the release: `git tag v1.X.Y && git push --tags`.

## Questions?

Open an issue. If it's about a plugin-submission process (reviewer
comments, manifest fields, etc.), tag it `plugin-registry`.
38 changes: 29 additions & 9 deletions plugins/Wzdhehe/mcode-webui/PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,29 @@ Full disclosure: [`references/SECURITY-NOTES.md`](references/SECURITY-NOTES.md).

## Automated test evidence

> **v1.0.1 round 8 note**: the test count claim below is now
> generated, not hard-coded. Run `npm test` for the actual count
> on a clean checkout. As of round 8 on the `fix/cve-csrf-token-leak-2026-09`
> branch (Node v25.9.0, mcode 0.2.4, sqlite3 3.51.0): **417 tests
> / 415 pass / 0 fail / 2 skipped** (see BASELINE-2026-09-04.md).

```
$ npm test
ℹ tests 291
ℹ suites 86
ℹ pass 290
ℹ tests 417
ℹ suites ~108
ℹ pass 415
ℹ fail 0
ℹ skipped 1
ℹ duration_ms ~550
ℹ skipped 2
ℹ duration_ms ~2400

$ npm run lint
> eslint server/ test/
(0 errors, 0 warnings)
(0 errors, 9 warnings — all pre-existing "imported but never used"
hints in test files; no new warnings from round 8)
```

Test breakdown:
- `lib-config.test.js` — 28 tests (constants, env loading, sqlite detection)
Test breakdown (high level):
- `lib-config.test.js` — constants, env loading, sqlite detection
- `lib-lan.test.js` — local request detection, LAN IP detection
- `lib-db.test.js` — `deleteMcodeSessionFromDb` happy path + missing-table
tolerance, dryRun path
Expand All @@ -100,6 +107,13 @@ Test breakdown:
- `sessions.test.js` — `?dryRun=true` preview, route-level session
CRUD with rollback
- `chat.test.js`, `routes-*.test.js` — error path coverage
- **`csrf-token-disclosure.test.js`** (round 8) — 4 integration tests
that spawn the real server and assert the bootstrap token is not
readable cross-origin. This file was added in `091dec5`
(Token Plan key integration) but the 3 of 4 blockers it asserts
on (CORS `*`, GET/POST settings token leak) only flip to GREEN
with the round 8 changes. The test file is the regression guard
for the hetaoBackend 2026-09-01 report.

CI: GitHub Actions on Node 22 / Node 24, Windows + Linux + macOS.

Expand Down Expand Up @@ -143,8 +157,14 @@ CI: GitHub Actions on Node 22 / Node 24, Windows + Linux + macOS.

- [x] `plugin.json` validates against `https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`
- [x] `npm run validate-plugin` (planned batch H) passes
- [x] `npm test` — 261 pass, 0 fail, 0 lint warning
- [x] `npm test` — see "Automated test evidence" above; the actual
count is generated by `npm test` and will drift as tests are
added. Pre-round-8 the PR description hard-coded "261 pass, 0
fail, 0 lint warning" which was incorrect for several prior
commits — replaced with the live run output in round 8.
- [x] `references/SECURITY-NOTES.md` covers all red-line 7 topics
AND round 8 §10 "Cross-origin request handling" (the new
section closing the hetaoBackend 2026-09-01 report)
- [x] LICENSE present (MIT)
- [x] README.md present and non-empty
- [x] No symlinks (release artifact expands junctions)
Expand Down
Loading
Loading