# 问题
审计发现 DSH 自报 mode_enforcement=native,而实测它的 Landlock 沙箱受内核 ABI
版本限制、拦截覆盖不完整(PLAN.md L5 自己写的就是 dsh = Landlock partial)。
只有 native / advisory 两个取值时,这个平台无论标哪个都是在说假话:
- 标 native → 人会以为 plan 档是硬保证,把它当安全边界依赖;
- 标 advisory → 又低估了它(确实在拦),而「平台无法强制」会让模型
在本可依赖的边界上过度保守。
多一个取值比多说一句假话便宜。
# 改动
- Go models:EnforcementPartial = "partial",ValidEnforcement 接受它;
NormalizeEnforcement 对显式自报值一律原样保留(partial 降级到任一极端都是假话),
未知值仍然 fail-closed 到 advisory。
- 三桥共用 lib/permission-mode.js(逐字节同源):ENFORCE_PARTIAL +
modeBriefing 三态措辞。partial 版必须同时做到两件事:
说清「覆盖不完整」,并收回 native 那句「都会被平台拦下」的承诺
—— 否则模型会以为越界一定被拦,于是不必自己小心。
- 前端 PermissionChip:三个点形区分(实心 / 靶心 / 空心)+ 三套 tooltip 文案;
认不出的强制力按 advisory(与后端同方向)。
- DSH 插件心跳改报 partial。
- 顺带修正活跃 DSH 会话的历史快照:那批 native 是插件当时的**误报**,
不是能力变化,因此把 status<>'archived' 的 dsh 会话改为 partial;
归档会话按设计保留(不重写已结束的历史)。改前已 sqlite3 .backup 备份。
# 验证
- agents.mode_enforcement:dsh 由 native 变为 partial(心跳生效)
- Go 全量、三桥插件 320/362/409、前端 196 全绿(新增 PermissionChip 11 例)
- 三桥共用模块同源校验通过
- 关键判据:partial 的措辞与 native/advisory 两两不同,且不含「无法强制」
102 lines
4.2 KiB
TypeScript
102 lines
4.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { render, screen } from '@testing-library/react';
|
||
import React from 'react';
|
||
|
||
import PermissionChip, {
|
||
permissionModeHint,
|
||
ENFORCEMENT_LABEL,
|
||
} from '../../src/components/PermissionChip';
|
||
|
||
/**
|
||
* 权限档位徽标。
|
||
*
|
||
* 判据集中在「三个强制力必须可区分」——因为把它显示错的代价不对称:
|
||
* - 高估(partial 显示成 native):人以为档位是硬保证,把它当安全边界依赖
|
||
* - 低估(partial 显示成 advisory):人以为平台完全不拦,在能依赖的边界上过度保守
|
||
* 这张徽标是这两个事实唯一的对外呈现,所以点形与文案都必须区分。
|
||
*/
|
||
|
||
describe('PermissionChip 档位显示', () => {
|
||
it('档位为空或非法时不渲染(人→人的信、旧会话)', () => {
|
||
const { container: c1 } = render(React.createElement(PermissionChip, {}));
|
||
expect(c1.firstChild).toBeNull();
|
||
const { container: c2 } = render(React.createElement(PermissionChip, { mode: 'nonsense' }));
|
||
expect(c2.firstChild).toBeNull();
|
||
});
|
||
|
||
it('三个档位各有中文短标签', () => {
|
||
for (const [mode, label] of [['plan', '只读'], ['workspace', '目录内'], ['full', '全权']]) {
|
||
const { unmount } = render(
|
||
React.createElement(PermissionChip, { mode, enforcement: 'native' })
|
||
);
|
||
expect(screen.getByText(label)).toBeInTheDocument();
|
||
unmount();
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('PermissionChip 强制力三态', () => {
|
||
it('native 用实心点,不带 partial/advisory 的标记', () => {
|
||
render(React.createElement(PermissionChip, { mode: 'plan', enforcement: 'native' }));
|
||
expect(screen.queryByTestId('chip-partial')).toBeNull();
|
||
expect(screen.queryByTestId('chip-advisory')).toBeNull();
|
||
});
|
||
|
||
it('partial 渲染为靶心(与实心、空心都不同形)', () => {
|
||
render(React.createElement(PermissionChip, { mode: 'plan', enforcement: 'partial' }));
|
||
expect(screen.getByTestId('chip-partial')).toBeInTheDocument();
|
||
expect(screen.queryByTestId('chip-advisory')).toBeNull();
|
||
});
|
||
|
||
it('advisory 渲染为空心点', () => {
|
||
render(React.createElement(PermissionChip, { mode: 'plan', enforcement: 'advisory' }));
|
||
expect(screen.getByTestId('chip-advisory')).toBeInTheDocument();
|
||
expect(screen.queryByTestId('chip-partial')).toBeNull();
|
||
});
|
||
|
||
it('认不出的强制力按 advisory 处理(保守方向,与后端 Normalize 同语义)', () => {
|
||
render(React.createElement(PermissionChip, { mode: 'plan', enforcement: 'PARTIAL' }));
|
||
expect(screen.getByTestId('chip-advisory')).toBeInTheDocument();
|
||
expect(screen.queryByTestId('chip-partial')).toBeNull();
|
||
});
|
||
|
||
it('tooltip 里给出强制力的中文标签', () => {
|
||
render(React.createElement(PermissionChip, { mode: 'workspace', enforcement: 'partial' }));
|
||
const chip = screen.getByText('目录内').closest('span');
|
||
expect(chip?.getAttribute('title')).toContain(ENFORCEMENT_LABEL.partial);
|
||
});
|
||
});
|
||
|
||
describe('permissionModeHint 三态文案', () => {
|
||
it('三种强制力的说明两两不同', () => {
|
||
for (const mode of ['plan', 'workspace']) {
|
||
const nat = permissionModeHint(mode, 'native');
|
||
const par = permissionModeHint(mode, 'partial');
|
||
const adv = permissionModeHint(mode, 'advisory');
|
||
expect(par).not.toBe(nat);
|
||
expect(par).not.toBe(adv);
|
||
}
|
||
});
|
||
|
||
it('partial 必须说明覆盖不完整,且不能说「平台不强制」', () => {
|
||
for (const mode of ['plan', 'workspace']) {
|
||
const par = permissionModeHint(mode, 'partial');
|
||
// 「不完整」或「缺口」必须出现:否则人无法知道拦截不可完全依赖
|
||
expect(par).toMatch(/不完整|缺口/);
|
||
// 「平台不强制」是 advisory 的说法,对 partial 是假话
|
||
expect(par).not.toMatch(/平台不强制/);
|
||
}
|
||
});
|
||
|
||
it('advisory 仍明说平台不强制', () => {
|
||
expect(permissionModeHint('plan', 'advisory')).toMatch(/平台不强制/);
|
||
expect(permissionModeHint('workspace', 'advisory')).toMatch(/平台不强制/);
|
||
});
|
||
|
||
it('full 档不提强制力差异(全权档本来就不需要授权)', () => {
|
||
for (const e of ['native', 'partial', 'advisory']) {
|
||
expect(permissionModeHint('full', e)).toMatch(/全权/);
|
||
}
|
||
});
|
||
});
|