Skip to content

fix(#2766): durable classifiers refuse assistant-role messages — model echo cannot take over a durable identity - #2774

Merged
ranxianglei merged 1 commit into
masterfrom
2026-10-11_durable-role-gate
Oct 11, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-10-11_durable-role-gate

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What

#2766: the four dsh durable classifiers matched on contentType === "text" + marker text with no role boundary. An assistant message echoing a carrier marker (<available_skills>, Instructions from:, <mcp_catalog>, Current runtime context) projected to the same durable/<name> virtual path as the real carrier. Under the default latest-per-identity policy (protectedLatestTools: ["durable/*"], #2736) collectLatestProtected's lastByPath takes the last match — so a later model echo took the identity over from the real host injection: the echo got pinned, the real carrier demoted to foldable, one compression later the forged/partial copy was the only survivor.

Adversarial variant: an injected "list the catalog" instruction makes the model emit a fake <mcp_catalog> with attacker-chosen server names → the forgery survives, the real inventory folds — #2446's failure mode (model hunting servers by wrong names), attacker-authored.

Fix

Every classifier now refuses assistant-role messages first:

return msg.role !== "assistant" && msg.contentType === "text" && ...

Tests

tests/issue2766-durable-role-gate.test.ts — 11 cases, all green:

  • per carrier (×4): assistant echo never projects (individual classifier + lane-composed durableMessageToolPaths.dsh); same content user-role still projects;
  • system-role carrier still projects (gate direction documented);
  • kernel: collectLatestProtected over [real carrier, filler, assistant echo] keeps the real carrier as the latest instance, echo not in msgIds, isMessageLatestProtected true for the carrier / false for the echo;
  • kernel: an echo between two real runtime-context snapshots does not break succession (newest real snapshot pinned).

Adjacent green (post-rebase on master da6315ec): durable-tool-identity (8), issue2446/2447/2481 suites (13 — their e2e sections unaffected), typecheck clean.

Closes #2766

…l echo cannot take over a durable identity

The four dsh carriers are host INJECTIONS into the user channel, but the
classifiers matched on contentType+text alone: an assistant message
echoing a carrier marker (<available_skills>, Instructions from:,
<mcp_catalog>, Current runtime context) projected to durable/<name> too.
Under the default latest-per-identity policy (#2736) the LAST match wins,
so a later model echo took the identity over from the real carrier: the
echo got pinned, the real carrier demoted to foldable, and one
compression later the forged/partial copy was the only survivor —
including the adversarial variant where an injected instruction makes
the model emit a fake <mcp_catalog> with attacker-chosen server names
(the #2446 failure mode, attacker-authored).

Anything visible to the model eventually shows up in its output
(bili-chain tags, #2483 render tags, #933/#1039 echo noise), so the gate
cannot be the marker text. Every classifier now refuses assistant-role
messages first (system/user injections stay covered); zero legitimate
protection loss — no carrier is ever emitted as assistant output.

Kernel unchanged: virtualToolPathOf stays role-agnostic by design; the
boundary belongs to the classifiers that own the evidence (KDD #9).

tests/issue2766-durable-role-gate.test.ts: 11 cases — per-carrier echo
never projects (individual + lane-composed), user-role still projects,
system-role still projects, collectLatestProtected keeps the real
carrier as the latest instance across a later echo, echo between two
real snapshots does not break succession.
@github-actions

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-11_durable-role-gate (004518d)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.3051-pr.2774.62054ed

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr2774.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei
ranxianglei merged commit 70d651c into master Oct 11, 2026
12 of 14 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.281-master.70d651c (rolling master tag).

npm install -g billion-context@0.0.281-master.70d651c  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

ranxianglei added a commit that referenced this pull request Oct 11, 2026
…EBUSY/EPERM teardown)

Every SessionStore created under BILI_STORAGE_UNIFIED holds the index.db
SQLite handle until GC; cancelAll() never closes the shadow, and Windows
cannot unlink an open file — the engproof teardown then pins the temp dir
in EBUSY (index.db) / EPERM (dir) on every run of the new fcac9e3 suite
(#1915 rmrf family; POSIX unlinks open files, hence linux-green). Switch
the last lifecycle call of each store to closeQuietly(); the corrupt-index
test closes only after its final writeNow (write-after-close would test a
different thing). Unblocks #2764 / #2774 windows legs.
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.

durable 身份接管缺口:classifier 无角色边界,模型回声可夺取 durable/* latest 身份

1 participant