fix(ci): bump vulnerable dependencies to unblock CVE audit - #9703
Conversation
Run npm audit fix --package-lock-only to move every dependency with a fixable advisory to its patched version, staying inside declared semver ranges (no --force). The high-severity tar 7.5.19 -> 7.5.22 bump (GHSA-r292-9mhp-454m) unblocks the Dependency CVE audit gate that has failed on every PR since the 2026-08-21 advisory batch; moderate entries (protobufjs, dompurify, echarts, hono, mermaid) and in-range transitive fixes are included in the same pass. uuid 9.x -> 11.x (GHSA-w5hq-g745-h8pq) and the vendored diff 7.0.0 (GHSA-73rr-hh4g-fpgx) need --force / semver-major moves and are left for separate review. Fixes #9699
The CI check 'Check VS Code companion notices are up-to-date' fails when NOTICES.txt drifts from the lockfile; regenerate it for the protobufjs, ws, hono, and tar version bumps. Fixes #9699
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. #9699 documents that Direction: clearly aligned — this unblocks a red gate that is hitting all PRs, and it does so by upgrading to patched versions rather than weakening the gate (no Size: not applicable — the 3,623 changed lines are entirely machine-generated files ( Approach: minimal and right. Risk: no elevated risk signals — only the root lockfile and a license-notices file are touched; no high-risk paths matched. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到,非理论性问题。#9699 记录了自 2026-08-21 起 方向:明确对齐——这解除了一个卡住所有 PR 的红色门槛,且方式是升级到已修复版本而非放宽门槛(无 规模:不适用——3,623 行改动全部是机器生成文件( 方案:最小且正确。 风险:无升级风险信号——只触及根 lockfile 和一个许可声明文件,未命中任何高风险路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewIndependent baseline before looking at the diff: for a retroactively failing What I verified against the diff (
Not verified statically: the idempotency claim ( Test evidence — this PR's own CI on the reviewed commitThis run does not build or execute PR code; the evidence is the PR's own CI fetched via the API. The check that matters most here — Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The before/after 中文说明代码审查:先不看 diff 的独立判断是——对于被追溯判红的 audit 门槛,最小修复就是对根 lockfile 跑 测试证据:本次运行不构建、不执行 PR 代码,证据来自 PR 自身 CI(API 拉取)。最关键的一项—— — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, minimal lockfile fix that does exactly what the gate needs; the only thing outstanding is the unit suite still running on the reviewed commit. Stepping back: this is the rare dependency PR where the scope matches the problem one-to-one. The gate fails because GitHub's advisory database moved under already-locked versions; the fix is to move the locked versions to their patched releases, in-range, without touching the gate. My independent proposal before reading the diff was precisely The diff reads honestly: two generated files, no So: approval deferred until CI lands green on 中文说明置信度:4/5 —— 干净、最小的 lockfile 修复,恰好解决门槛问题;唯一未决项是被审提交上的单测套件仍在运行。 整体看:这是一个范围与问题一一对应的依赖类 PR。门槛失败是因为 GitHub 公告库对已锁定版本追溯更新;修复就是把锁定版本升到范围内已修复的版本,且不动门槛本身。我在看 diff 之前的独立方案正是 Diff 读起来诚实:两个生成文件、无 因此:待 — Qwen Code · qwen3.8-max Reviewed at |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
|
Released in v0.22.0. |
The 2026-08-21 advisory GHSA-r292-9mhp-454m flags tar <= 7.5.20 as high severity, failing the Dependency CVE audit gate. Main already moved to 7.5.22 in QwenLM#9703, but that landed after this branch's last merge of main. Bump the lockfile entry in-range (core/cli declare ^7.5.19) to match main, and regenerate the committed NOTICES.txt artifact whose freshness is enforced by CI.
* feat(channels): add DingTalk Workspace channel
Add a DingTalk Workspace (DWS) channel package so a workspace can be
driven from DingTalk alongside the existing channels.
- packages/channels/dws: new workspace holding the DWS client, event
stream, environment resolution and channel implementation, with the
event-source fixtures used by its tests.
- cli: register DWS in the channel registry and its builtin list.
- web-shell: recognise the DWS platform in the channels UI.
- docs: document the channel and its configuration under
docs/users/features/channels.
- build/release: include the new workspace in the build, clean and
release-version scripts and the vitest project list.
The channel watches native DingTalk todos, routes document and todo
replies back to their originating conversation, bounds notification
retries, and keeps sender identity authoritative for direct messages.
* fix(dws): classify spawn-resource errnos as not sent, and test against base's source
Round 1 review, two Critical findings.
vitest.config.ts — the new package's config was the only channel config
without the `@qwen-code/channel-base` → source alias its five siblings carry,
so `cd packages/channels/dws && npx vitest run` (the workflow AGENTS.md
prescribes) depended on a prior `tsc --build` of base. Reproduced the
reviewer's witness in this worktree with base/dist moved aside: without the
alias vitest dies in `packageEntryFailure` and runs zero tests; with it,
63/63 pass. Even when dist exists it may lag base's source — it did here, by
four days.
dws-client.ts — `DWS_NOT_SENT_ERROR_CODES` listed only the path errnos, so a
`dws` process that never started because of fd or memory exhaustion
(`EMFILE`/`ENFILE`/`ENOMEM`/`EAGAIN` and family) was classified `unknown`.
The todo and document reply paths in dws-channel.ts swallow `unknown` as
"the originating task will not be rerun", so a user's final reply was dropped
permanently on one log line instead of being retried — and the retry is safe,
since the fingerprint is not persisted when delivery fails. The set now
carries the whole `uv_spawn` pre-exec family. Everything else the callback
reports — a non-zero exit (numeric `code`), a timeout kill (`code === null`),
`ABORT_ERR`, a `maxBuffer` overrun — happened with a child already running
and stays `unknown`, because a retry there could duplicate a delivery.
The classification moved into an exported `classifyDwsCommandFailure` so the
table can be driven directly: the resource errnos need real fd or memory
exhaustion to reproduce through a spawn, which no unit test can stage safely.
The existing missing-executable test still covers the wiring end to end.
Verified: packages/channels/dws — 191 passed (5 files). Mutation-verified:
reverting the errno set turns exactly the 12 added codes red (12 failed /
51 passed); dropping the vitest alias with base/dist absent turns the suite
from 63 passed into a collection failure. eslint and prettier clean. The one
tsc error on this branch (`displayText` missing from `Envelope`) is worktree
build skew — base/dist was built 2026-08-10, base/src changed 2026-08-14, and
the field is present in the source; it reproduces identically with these
changes stashed.
* fix(dws): stop a denied sender from consuming a document comment's dedup slot
Round-2 review, R2-4 (Critical).
`notificationKey` is `documentNotificationKey(documentId, commentKey)` — no
sender in it — so a `'denied'` outcome falling into the `else` branch marked
that (document, comment) pair processed for good. Every later notification for
the same comment, live or polled, then hit
`processedMessages.includes(notificationKey)` and returned silently, including
one from a sender who IS allowed. The cursor persists, so the drop survived
restarts.
Concretely, with `senderPolicy: 'allowlist'` and `allowedUsers: ['open-bob']`:
Alice (not allowlisted) @-mentions the bot in a document comment and is denied;
Bob then mentions the bot on the same comment thread — the ordinary
multi-reviewer document flow — and is dropped forever, with no dispatch, no
pairing and no log.
A denied notification is now parked with `rememberPendingDocumentNotification`
like a `'pairing'` one rather than consuming the slot. Replay already skips a
pending entry whose sender fails `gate.isAllowed`, so a denied sender does not
get retried in; and an allowed sender reaching the same comment clears the
entry on the way through.
The existing `applies sender access policy to document mention notifications`
cannot cover this — its denied and allowed notifications are on DIFFERENT
comments, so the shared key is never exercised. New test puts both on the same
comment. Mutation-checked: restoring the old condition reddens it with
`bridge.prompt` called 0 times against an expected 1, reproducing the review's
own witness.
Verification: `npm run build` and `tsc --noEmit` clean in packages/channels/dws;
eslint clean on both changed files; full package suite 192/192 (118 in
dws-channel.test.ts, 1 new).
* fix(dws): stop a poison message, a full pending queue, and an unreachable
replay from pinning the watermark (R2-1, R2-2, R2-4 queue)
Three ways history polling could stall forever, each measured:
**R2-2, poison message.** A message whose turn threw was never marked
processed, so the watermark never advanced and every poll re-ran it as a
full agent turn — one model call per iteration, no cap, no backoff —
while the pinned watermark grew the query window without bound and the
throw starved every newer message behind it. Pending-document replay
already had retry accounting; this path had none. Inbound failures are
now counted per message and persisted in the cursor: under budget the
error still propagates (redelivery retry and the concurrent-duplicate
contract depend on that, and their tests pin it), and once the budget is
spent the message is marked processed and dropped with a logged reason.
**Pending-queue cap.** `rememberPendingDocumentNotification` threw at
MAX_PROCESSED_ITEMS, and the throw aborted the direct-message loop
before the checkpoint, the watermark and `markProcessedMessage` — so
every later poll re-scanned a growing window and re-threw on the same
never-marked message, surviving restarts in the cursor. The queue's only
drain is an allowed sender later processing the same comment, so entries
parked for unapproved senders never leave: one unpaired member
@-mentioning the bot in 5,000 distinct comments broke document history
polling until manual cursor surgery. It now evicts the oldest instead,
which costs at most a pairing prompt nobody approved.
**R2-1, the replay the fixture could not recover.** The test fake
ignored its `startTime`/`endTime`, so it certified a recovery the
production arithmetic cannot perform. Fixed on both sides: the fake now
filters by its window like the real client (and `message()` defaults
`eventTime` to now, since real messages always carry one — six fixtures
were silently relying on epoch 0), and the stale-replay guard now pulls
`notificationWatermark` back to the parked notification's event time. It
parks document notifications UNMARKED on purpose, "for polling to
recover"; on a fresh cursor the watermark started at
`connectionStartedAt` and the window opened at `watermark − 5s` —
exactly the guard's own drop boundary — so everything it parked was
strictly outside every window that watermark would ever produce.
Every fix is mutation-verified: reverting the retry budget re-runs the
poison turn once per poll (8 polls, 8 turns), restoring the queue throw
reproduces the reviewer's stderr and the pinned watermark, and dropping
the watermark pull-back leaves the replayed notification unrecovered.
Suite 194/194 green; `tsc -p packages/channels/dws` clean.
R1-2 (self-identity degradation) is not in this commit — both fixes the
review proposes collide with behaviour this suite pins deliberately; see
the thread.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(dws): budget every inbound surface, and stop restarting a dead source (R4-1, R4-3)
R4-1: round 3 added an inbound failure budget, but wired it into one of the
three `handleInbound` call sites — the mention/live-IM path. The other two kept
the exact unbounded-retry mode the budget's own doc comment says it exists to
close.
- Document notifications (`processDocumentNotification`): a throw escapes
`pollOnce`'s sorted loop and is swallowed by the outer catch, so nothing is
marked processed and `notificationCheckpoint`/`notificationWatermark` — both
assigned after the loop — never advance. Every 5s poll re-ran the same full
agent turn, forever, starving every newer notification behind it.
- Native todos (`pollTodos`): the fingerprint is remembered only on success, so
a todo whose turn keeps throwing was re-fetched and re-run every poll,
forever.
`recordInboundFailure` now takes the drop action as a parameter, because "stop
re-running this" differs per surface: marking the key processed is right for a
message, a document notification carries its own `notificationKey` (and a
pending entry to clear), and a todo is re-fetched by fingerprint. The default
keeps the mention path byte-identical.
R4-3: `retryable: false` is terminal before ready — `retryLimit` returns 0 —
but `scheduleImRestart` never consulted it, and `startImSource` resets
`restartAttempts` to 0 every time a subscription becomes ready. The backoff
exponent therefore stayed at 0, so a permanently denied consumer (permission
revoked, subscription not allowed) was respawned at a constant ~3s forever —
one `dws event consume` child every 2-3s per affected source — while the
channel reported itself connected and delivered nothing for that source.
Post-ready now matches pre-ready: terminal, with a log line saying so.
Verification (`cd packages/channels/dws`):
- `npx vitest run` — 197 passed (was 194; three new tests).
- `npx tsc -p tsconfig.json --noEmit` — clean.
- Mutation checks, one per fix, each turning exactly its own test red and
leaving the other 122 green:
- drop the `retryable === false` guard -> `stops restarting a source that
died permanently after becoming ready` fails.
- drop the document-path budget -> `drops a document notification whose turn
keeps failing, and stops starving newer ones` fails (the newer
notification is never reached).
- drop the todo-path budget -> `drops a native todo whose turn keeps
failing` fails (8 turns instead of 5).
- eslint + prettier clean.
Not addressed in this commit: R4-2 (checkpoint drain overwriting the stale
replay pull-back), R4-4, R1-2, and R4-5..R4-8.
* fix(dws): stop an in-flight poll from clobbering the stale-replay pullback (R4-4)
`handleImMessage` leaves a replayed document notification UNMARKED on purpose,
for history polling to pick up, and pulls `notificationWatermark` back to the
replay's `eventTime` so a future window can reach it. `pollOnce` then wrote
`checkpoint.endTime` over that watermark unconditionally when its own window
finished — and `checkpoint.endTime` is always past the replay's `eventTime`.
The race is not hairline: `runLoop` polls immediately on connect and the IM
subscriptions start before the poll loop, so a startup replay arrives precisely
while poll #1's `listDirectMessages` is awaiting. One clobber puts the parked
replay outside every window the watermark will ever produce — no turn, no log,
no error, and it survives restarts because `saveCursor()` persists it.
`pollOnce` now records whether the watermark was pulled back while its
direct-message fetch was in flight, and on that path drops the window instead of
finishing it: neither the advance nor the paginated checkpoint resume is safe,
because the checkpoint was itself derived from the pre-pullback watermark. The
next poll re-derives a window from the pulled-back value.
Test: `keeps the stale-replay pullback when a poll was already in flight` emits
the replay from inside `listDirectMessages`. Mutation-checked — forcing the
guard false reddens it with `inbound` empty, matching the reviewer's witness
(`dispatched = 0`). It also asserts the second query window opens at or before
the replay's `eventTime`, so a fake that ignored its window could not certify it.
* fix(dws): stop three silent, permanent losses of a document mention (R6-1/R6-2/R6-3)
All three Criticals round 6 raised share a failure shape: a document comment is
consumed by something that had no right to consume it, the user gets no reply,
and nothing is logged. Each is fixed at the point that consumes the slot.
R6-1 — `handleImMessage` pullback (dws-channel.ts): R4-4 rescued a stale replay
by pulling the notification watermark back, but the flag `pollOnce` consults is
cleared at the top of every fetch, so it only ever covered a replay that landed
DURING one. A pullback arriving in the gap between two polls is reset before it
is read; a persisted multi-page `notificationCheckpoint` then resumes a window
that starts after the replay and finishes by writing `checkpoint.endTime` back
over the pulled-back watermark. The replay was left unmarked on purpose, so
after that no window ever reaches it again. The pullback branch now drops the
checkpoint as well, which makes the rescue durable regardless of when the
replay arrived; the in-flight flag still guards the during-a-fetch case.
R6-2 — in-flight awaiter (dws-channel.ts): a pending entry means the in-flight
turn PARKED the comment for a sender it would not serve, which says nothing
about the caller waiting behind it. Marking unconditionally consumed an ALLOWED
sender's mention outright — replay only re-drives a parked entry whose own
`senderId` passes the gate (the denied one never will), and the allowed
sender's marked message key is skipped by every later history poll. The awaiter
now marks only when the comment is genuinely processed, or when this caller is
no more entitled to it than the sender already parked. This is what the
denied-sender comment further down already claimed happened ("an allowed sender
reaching the same comment clears the entry on the way through") — the awaiter
was the path that never let them reach it.
R6-3 — failure-budget drop closure (dws-channel.ts): the closure marked the
sender-agnostic `notificationKey` (`document\0comment`, no sender), so five
failed turns — about 25s of transient model or bridge trouble, since each 5s
poll re-runs an unmarked notification — dropped every FUTURE mention of that
comment from anyone, permanently and across restarts. It now marks only the
failing message's own `key`, which is what stops the window re-running it, so
the R4-1 starvation this budget closes stays closed.
Tests (dws-channel.test.ts), each mutation-verified against the pre-fix code:
- `keeps a stale-replay pullback that arrives between two polls` — persists a
bounded checkpoint, emits the replay with no poll in flight, asserts the
checkpoint is released and the next window reaches back over the replay.
Reverting R6-1: `expected { startTime: … } to be undefined`.
- `lets an allowed sender through while a denied turn on the same comment is in
flight` — the concurrent counterpart to the existing R2-4 test, which lets
the denied turn finish first and so cannot reach the awaiter. Reverting R6-2:
the allowed sender's prompt is never called.
- `lets a later mention of a dropped comment retry with a fresh budget` — five
failing polls, then a different reviewer on the same comment after the
outage. Reverting R6-3: `expected [] to deeply equal [ ObjectContaining{…} ]`.
Verification: `npx vitest run` in packages/channels/dws — 201 passed (5 files);
`npx tsc --noEmit -p packages/channels/dws/tsconfig.json` clean; `npm run build`
in that package clean; eslint and prettier clean on both changed files.
R1-2 is untouched: it still needs a maintainer call on which pinned contract
gives, and is not something this commit should decide.
* fix(dws): resolve the sender gate before reading a mentioned document (R7-1)
`parseDocumentMentionNotification` reconstructs `(documentId, commentKey)`
from rendered message text, so a bare alidocs URL in an ordinary DM forges a
mention card the channel cannot tell apart from a genuine platform
notification. `processDocumentNotification` then called
`readDocumentContext` on that attacker-named document BEFORE `handleInbound`
resolved the sender gate, so under the documented default
`senderPolicy: 'pairing'` an unpaired stranger could force this profile to
perform an authenticated read of any document it can reach — a turn the
channel would never serve them.
Resolve `gate.isAllowed(message.senderId)` first and read only for a sender
this channel will actually answer. The envelope already carries a "Document
Markdown was unavailable" fallback, the `preflightInbound` document branch
still parks the mention exactly as before, and
`replayPendingDocumentNotifications` re-enters this path once the sender is
approved, so an approved turn still gets its document context — just after
the gate instead of before it.
BEHAVIOR FLIP: `replays a pairing-pending document mention after approval`
pinned `readDocument` being called once for the still-unpaired sender and
twice overall. That pinned expectation was the defect: it asserted an
authenticated read driven by a sender the gate had already refused. It now
expects zero reads before approval and one after. Verified by mutation —
reverting the guard turns both this test and the new forged-mention test red.
Still open on this class and NOT addressed here: the pairing-code write into
the attacker-named comment thread. Closing that needs either fail-closed
verification that `commentKey` is a real comment on `documentId` mentioning
this profile (no DWS CLI surface exposes it — `listMentionedMessages` covers
group IM, not document comments) or structured mention events, so it is a
maintainer contract call rather than a local fix.
Verification:
- packages/channels/dws: 202 passed (5 files), including the new
`does not read a forged document mention before the sender gate resolves`
- tsc --noEmit -p packages/channels/dws/tsconfig.json: clean
- eslint + prettier --check on both changed files: clean
* fix(dws): list the dws channel as a cli test build prerequisite
`channel-registry.ts` dynamically imports `@qwen-code/channel-dws`, whose
package.json resolves the bare specifier to `dist/index.js` and which
`packages/cli/vitest.config.ts` does not alias to source. It therefore
belongs in `DIST_PREREQUISITES['packages/cli']` alongside every other
builtin channel, so a cli test run on an unbuilt checkout reports the
actionable "run npm run build" message instead of a raw resolution error.
This is what the required `Test (ubuntu-latest, Node 22.x)` check caught
on 4bf0407: scripts/tests/vitest-global-setup.test.js asserts the list
stays in sync with the registry, and dws was the one registry import
missing from it.
Verified: `npx vitest run scripts/tests/vitest-global-setup.test.js`
29 passed; reverting this one line reproduces the CI assertion exactly
("missing prerequisite entry for packages/channels/dws"), 1 failed | 28
passed. prettier --check and eslint clean.
* fix(dws): close current review blockers
* test(dws): pin fail-closed self identity gate
* fix(dws): preserve retryable inbound work
* fix(dws): preserve in-flight catch-up mentions
* fix(dws): align channel-base on the workspace version so npm ci resolves
`Dependency CVE audit` has failed every run with:
npm ci can only install packages when your package.json and
package-lock.json are in sync.
Missing: @qwen-code/channel-base@0.21.11 from lock file
The diagnosis of "stale base" was right, but the stale file is this PR's
own. `packages/channels/dws` was written when the workspace was at
0.21.11 and pins that version; every sibling channel — dingtalk, feishu,
github, gitlab, qqbot, telegram, wecom, weixin — now says 0.21.14, which
is what `packages/channels/base` actually publishes. A workspace package
cannot satisfy 0.21.11, so npm resolved `@qwen-code/channel-base` for dws
from the REGISTRY instead of linking the sibling, leaving a nested
`packages/channels/dws/node_modules/@qwen-code/channel-base` entry that
`npm ci` refuses. Merging current main cannot fix it: main is not where
the pin lives.
Bump dws to 0.21.14 for both its own version and its channel-base
dependency, matching every sibling, and regenerate the lockfile. The
nested registry entry is gone and dws now links the workspace like the
others. `npm ci --dry-run` completes, and dws typechecks and passes all
211 tests against the workspace channel-base rather than the published
0.21.11 it was resolving before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VgTjRF91xANQh6SY9YGyCf
* fix(dws): replay failed direct messages, and close open review items
* fix(deps): bump tar to 7.5.22 to unblock CVE audit (QwenLM#9394)
The 2026-08-21 advisory GHSA-r292-9mhp-454m flags tar <= 7.5.20 as
high severity, failing the Dependency CVE audit gate. Main already
moved to 7.5.22 in QwenLM#9703, but that landed after this branch's last
merge of main. Bump the lockfile entry in-range (core/cli declare
^7.5.19) to match main, and regenerate the committed NOTICES.txt
artifact whose freshness is enforced by CI.
* fix(dws): unblock npm ci, add publish metadata, and keep todo fetch failures out of the turn budget (R13-1, R13-2, R14-1)
* fix(dws): dedup threaded pairing comments on a persisted marker instead of the rotating code (R15-1)
* fix(dws): clear the todo pairing marker when pairing resolves, not on turn success (R16-1)
* fix(dws): address current review blockers
* fix(dws): satisfy event fixture lint
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
What this PR does
Runs
npm audit fix --package-lock-onlyagainst the root lockfile to bump every dependency with a fixable advisory into its patched version. Onlypackage-lock.jsonchanges — nopackage.jsonrange is touched, and no code changes.Directly fixed (all within declared semver ranges, no
--force):tarprotobufjsdompurifyechartshonomermaidnpm audit fixalso resolved transitive/lockfile-only advisories it could reach inside declared ranges (e.g.minimatch3.1.2 → 3.1.5,ajv6.12.6 → 6.15.0,js-yaml,lodash,ws,postcss,nanoid, plus dev-dependency bumps such asvitest3.2.4 → 3.2.7,vite7.0.0 → 7.3.6,storybook10.2.0 → 10.5.10). That is why the diff looks large for a "bump" — every entry stays inside the range its parentpackage.jsonalready declares, and the result is idempotent (npm install --package-lock-onlyproduces no further changes). Nested duplicate copies ofdiffunderpackages/cli/packages/corewere deduped onto the hoistednode_modules/diff@7.0.0(same version, no behavior change).Why it's needed
The
Dependency CVE auditjob in.github/workflows/security-checks.ymlrunsnpm audit --omit=dev --audit-level=highand has failed on every PR since 2026-08-21, when GitHub published a new advisory batch against already-locked versions (audit checks the live advisory database, so unchanged lockfiles retroactively fail). The gate is intentionally strict and this PR does not loosen it — nocontinue-on-error, no audit level change, no allowlist; the fix is purely upgrading to patched versions.Reviewer Test Plan
How to verify
origin/mainbefore this PR:npm audit --omit=dev --audit-level=highexits1with8 vulnerabilities (1 low, 6 moderate, 1 high)— the high entry istar@7.5.19(GHSA-r292-9mhp-454m).0with2 vulnerabilities (1 low, 1 moderate)— only the two items that require--forceremain (see below).npx vitest run scripts/tests/security-workflows.test.jsstill passes, proving the--audit-level=highgate and its no-continue-on-errorassertion are untouched.package.jsonfiles are unchanged (git diff --statshows onlypackage-lock.json).Evidence (Before & After)
Before (exit 1):
After (exit 0):
Tested on
Environment (optional)
Lockfile-only change. Verified locally with npm 11.17.0 / node v24.19.0 against registry.npmjs.org;
npm cisucceeded from the new lockfile, thennpx vitest run scripts/tests/security-workflows.test.js(2/2 pass) andtsc --noEmittypechecks for@qwen-code/qwen-code-core,@qwen-code/qwen-code(cli),@qwen-code/web-shell, and@qwen-code/webuiall passed.packages/desktopis excluded from the root npm workspace (!packages/desktop, uses its ownbun.lock), so it is not covered by the root lockfile or its audit and needed no change.Risk & Scope
vite7.3.x,storybook10.5.x,vitest3.2.7) and runtime patch/minor bumps (tar,hono,mermaid,echarts,dompurify,protobufjs) land together because that is whatnpm audit fixproduces in one pass; all are inside declared ranges.uuid9.0.1 → 11.x (GHSA-w5hq-g745-h8pq) and the vendoreddiff@7.0.0(GHSA-73rr-hh4g-fpgx) are semver-major and would neednpm audit fix --force; they stay vulnerable here and should get their own reviewed PRs. Full unit-test suite not run locally — relying on CI beyond the checks listed above.packages/desktoptypecheck not run locally (bun-based, separate lockfile, unaffected).package.jsonrange changes.Linked Issues
Fixes #9699
中文说明
这个 PR 做了什么
对根目录 lockfile 执行
npm audit fix --package-lock-only,把所有有修复版本的受影响依赖升到已修复版本。只改package-lock.json,不改任何package.json的版本范围,也没有代码改动。直接修复项(均在已声明的 semver 范围内,未用
--force):tar7.5.19 → 7.5.22(GHSA-r292-9mhp-454m,high,唯一卡门槛的项)、protobufjs7.6.4 → 7.6.5、dompurify3.4.11 → 3.4.14、echarts6.0.0 → 6.1.0、hono4.12.27 → 4.13.3、mermaid11.15.0 → 11.17.0。npm audit fix同时把声明范围内能修的其他传递依赖漏洞一并修掉(如minimatch、ajv、js-yaml、lodash、ws、postcss、nanoid,以及 dev 依赖vitest、vite、storybook等),所以 diff 看起来比"小升级"大;所有条目都不超出各自package.json声明的范围,且结果幂等(再跑npm install --package-lock-only无进一步变化)。packages/cli/packages/core下嵌套的diff副本被去重提升到根node_modules/diff@7.0.0(版本相同,无行为变化)。为什么需要
.github/workflows/security-checks.yml的Dependency CVE audit跑npm audit --omit=dev --audit-level=high,自 2026-08-21 GitHub 针对已锁定版本发布新公告后,每个 PR 都失败(audit 查的是实时公告库,未变更的 lockfile 被追溯判红)。门槛是有意设置的,本 PR 不放宽门槛——不加continue-on-error、不改 audit level、不加白名单,修复方式就是升级到已修复版本。评审验证方式
origin/main:npm audit --omit=dev --audit-level=high退出码1,8 vulnerabilities (1 low, 6 moderate, 1 high),high 项为tar@7.5.19。0,2 vulnerabilities (1 low, 1 moderate),仅剩两个必须--force的项。npx vitest run scripts/tests/security-workflows.test.js仍通过,证明--audit-level=high门槛断言未被改动。package.json无变化(git diff --stat仅package-lock.json)。环境
纯 lockfile 改动。本地用 npm 11.17.0 / node v24.19.0 对 registry.npmjs.org 验证;
npm ci可基于新 lockfile 成功安装,随后npx vitest run scripts/tests/security-workflows.test.js(2/2 通过)以及@qwen-code/qwen-code-core、@qwen-code/qwen-code(cli)、@qwen-code/web-shell、@qwen-code/webui的tsc --noEmittypecheck 全部通过。packages/desktop不在根 npm workspace 内(!packages/desktop,用自己的bun.lock),不受根 lockfile 及其 audit 覆盖,本 PR 无需改动。风险与范围
npm audit fix一次会带上 dev 依赖的 minor 升级(vite7.3.x、storybook10.5.x、vitest3.2.7)和运行时 patch/minor 升级,均在声明范围内。uuid9.0.1 → 11.x(GHSA-w5hq-g745-h8pq)和 vendoreddiff@7.0.0(GHSA-73rr-hh4g-fpgx)是 semver-major,需要--force,本 PR 保持现状,建议单独开 PR 评审;全量单测未本地跑,其余依赖 CI;packages/desktop的 typecheck 未本地跑(bun 体系、独立 lockfile、不受影响)。package.json范围。关联 Issue
Fixes #9699