dsh-crwu-workbench 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -35
- package/lib/client.js +1 -1
- package/lib/index.js +96 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,102 @@
|
|
|
7
7
|
`cordis_define` + `cordis_run` 装配,版本号用 DSH 的 `pkg-N`);它已在本仓收尾时删除
|
|
8
8
|
(见 `0.0.1` 一节),下面 `legacy · pkg-43` 及更早的记录是它的历史。
|
|
9
9
|
|
|
10
|
+
## package · 0.0.17 · 2026-09-29 · fix · 登录失败归因分两支 + 登录临时目录由插件指定
|
|
11
|
+
|
|
12
|
+
员工在 Windows 上点「扫码登录氚云 / 钉钉登录」持续失败。开发者诊断给出的结构化事实是
|
|
13
|
+
`req = res = ran = danger-full-access`、`denied=false`、`runnerFailed=false`、`started=true` ——
|
|
14
|
+
**DSH 没有拦这次调用**;原文却是
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
create fallback browser profile directory: mkdir C:\Users\51019\AppData\Local\crwu: Access is denied.
|
|
18
|
+
(system TEMP failed: mkdir C:\Users\51019\AppData\Local\Temp\crwu-scan-556823159: Access is denied.)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
两条路径都在 `AppData\Local` 下、都被拒,而同一时刻 `dws auth status` 是 ok(`~/.dws` 可写)。
|
|
22
|
+
本版据此做两件事,都只动插件侧:
|
|
23
|
+
|
|
24
|
+
1. **登录的临时目录改由插件指定**(`src/host/system/auth-scratch.ts`):先建
|
|
25
|
+
`<home>/.dsh/crwu-workbench/auth-tmp`,再把 `TMPDIR`/`TMP`/`TEMP` 指过去
|
|
26
|
+
(POSIX `mkdir -p … && TMPDIR=… <cmd>`;PowerShell `New-Item -Force …; $env:TMP=…; & <cmd>`)。
|
|
27
|
+
依据是员工机器上的事实:系统 TEMP 与用户缓存目录都被拒,而插件状态目录(`<home>/.dsh/…`)可写。
|
|
28
|
+
**不动 `LOCALAPPDATA`**(Chromium 自己的组件目录仍走系统默认);主目录未知时原样返回命令,不伪造。
|
|
29
|
+
`runCrwu` 增加可选 `scratchDir`(只有会就地起浏览器的那条命令用)。
|
|
30
|
+
2. **归因分两支,不许合成一句**(`LoginAdvice.policyBlocked`):结构化事实说降级 / 拒绝 / 实际受限
|
|
31
|
+
→ 「被文件策略挡在工作区之外」,动作是切「完全权限」;**事实干净却照样 `Access is denied`
|
|
32
|
+
→ 「被这台机器拒绝(不是 DSH 的文件策略)」**,明说"切权限不会有用",并给退路
|
|
33
|
+
(氚云 `crwu h3yun session bind --token`、钉钉先试「设备码登录(无浏览器时)」)。
|
|
34
|
+
上一版把两支合成一句"请切完全权限" —— 员工按提示切了,而日志证明根本不是策略问题(误导)。
|
|
35
|
+
3. 面板「账号连接」的前置说明同步改口径,并回到"设备码登录(**无浏览器时**)"
|
|
36
|
+
(它不是"沙箱挡浏览器"的退路:同样要抢 `<HOME>/.dws` 的锁)。
|
|
37
|
+
|
|
38
|
+
**已证伪**:把两支合成策略支 → 5 条变红;把 scratch 的 env 前缀去掉(只建目录不指过去)→ 2 条变红。
|
|
39
|
+
|
|
40
|
+
### ① 登录失败归因:两支分开,不许合成一句
|
|
41
|
+
|
|
42
|
+
**症状**:员工第一次用(Windows 与 macOS 都报)时,点「扫码登录氚云」「钉钉登录」浏览器起不来;
|
|
43
|
+
面板给的却是 CLI 的原话 —— 氚云 `mkdir C:\…\Temp\crwu-scan-1799287186: Access is denied.`
|
|
44
|
+
(看着像 crwu 坏了)、钉钉 `acquiring file lock: …\.dws\.data.lock: Access is denied.`
|
|
45
|
+
(看着像 dws 自己的 bug)。
|
|
46
|
+
|
|
47
|
+
**根因(两条入口同一条边界)**:登录**必须写工作区之外的路径** ——
|
|
48
|
+
|
|
49
|
+
| 入口 | 必须写的东西 |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `crwu h3yun session login` | `$TMPDIR`/`%TEMP%` 下的临时浏览器 profile(`crwu-scan-*`),再经 CDP 读会话 |
|
|
52
|
+
| `dws auth login`(含 `--device`) | `<HOME>/.dws/.data.lock`(拿登录态之前先抢文件锁) |
|
|
53
|
+
| 两者收尾 | 操作系统凭据存储(钥匙串 / Credential Manager) |
|
|
54
|
+
|
|
55
|
+
宿主按完全访问跑、操作系统的 ACL/受限令牌仍然拒绝时报的就是上面那两句;受限沙箱下浏览器即使
|
|
56
|
+
起来了也会在几毫秒内 renderer 崩溃(CDP 只回 `close 1006` / `Target crashed`)。
|
|
57
|
+
|
|
58
|
+
- **新增纯函数判据** `src/host/system/login-failure.ts`:`loginFailureAdvice()` / `describeLoginAdvice()`。
|
|
59
|
+
顺序是「结构化事实优先,文本最后」:① `runnerFailed` → ② 提权被降级(`requested !== resolved`)
|
|
60
|
+
→ ③ `denied === true` 或实际跑在受限模式 → ④ **事实干净但原文点名了登录必须写的工作区外目标
|
|
61
|
+
且带拒绝字样**(Windows 实测就是这一形态)。
|
|
62
|
+
- **第 ④ 条是 `sandboxDenialNote()` 的唯一例外**,判据本身变强了:目标不是"任意路径",而是登录
|
|
63
|
+
流程必须写的那几个已知目标。**目标词与拒绝字样必须同时命中** —— 只命中一个不归因(否则
|
|
64
|
+
`secret not found in keyring` 这种"真没条目 / 钥匙串被锁"会被说成"去切完全权限")。
|
|
65
|
+
- **`dwsLogin` 以前完全不做归因**(只有 `runDws` 做),现在两条登录都返回 `sandboxBlocked` + `advice`,
|
|
66
|
+
并把「在输入框下方的访问模式里选「完全权限」再点一次」写进 `error`,**原始报错保留**在末尾。
|
|
67
|
+
- **设备码不再是"退路"**:它同样要抢 `~/.dws` 的锁,所以按钮文案改回「设备码登录(无浏览器时)」,
|
|
68
|
+
归因文案里明说"在这个模式下也走不通"。
|
|
69
|
+
- **界面**:「账号连接」页在登录按钮下方**先说前置条件**(`envLoginSandboxHint`);
|
|
70
|
+
被归因时不再追加 stdout 尾巴(否则那句「正在打开浏览器窗口…」会把真正的动作挤掉)。
|
|
71
|
+
|
|
72
|
+
**已证伪**:把第 ④ 条整段关掉 → 7 条变红;把「目标词」那一半去掉(只认拒绝字样)→
|
|
73
|
+
误报用例变红。`cp` 还原后转绿。
|
|
74
|
+
|
|
75
|
+
### ② 登录的临时目录由插件指定(不再依赖系统 TEMP)
|
|
76
|
+
|
|
77
|
+
员工在 Windows 上用 0.0.15/0.0.16 再报一次,原文是**两条路径都被拒**:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
create fallback browser profile directory: mkdir C:\Users\51019\AppData\Local\crwu: Access is denied.
|
|
81
|
+
(system TEMP failed: mkdir C:\Users\51019\AppData\Local\Temp\crwu-scan-556823159: Access is denied.)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
而同一时刻开发者诊断里 `h3yun.session.login` 是
|
|
85
|
+
`req = res = ran = danger-full-access`、`denied=false`、`runnerFailed=false`、`started=true` ——
|
|
86
|
+
**DSH 没有拦它**,`%TEMP%` 与用户缓存目录却都被这台机器拒绝。两处修正:
|
|
87
|
+
|
|
88
|
+
1. **临时目录改由插件指定**(`src/host/system/auth-scratch.ts`):登录命令先建
|
|
89
|
+
`<home>/.dsh/crwu-workbench/auth-tmp`,再把 `TMPDIR`/`TMP`/`TEMP` 指过去
|
|
90
|
+
(POSIX `mkdir -p … && TMPDIR=… <cmd>`;PowerShell `New-Item -Force …; $env:TMP=…; …; & <cmd>`)。
|
|
91
|
+
依据是**这台机器自己的事实**:系统 TEMP 与缓存目录都被拒,而插件状态目录(`<home>/.dsh/…`)
|
|
92
|
+
是它一直在写的地方。**不动 `LOCALAPPDATA`**(Chromium 自己的组件目录仍走系统默认);
|
|
93
|
+
主目录未知时**原样返回命令**,不伪造一个没建出来的目录。
|
|
94
|
+
`runCrwu` 增加可选的 `scratchDir`(只有会就地起浏览器的那条命令用)。
|
|
95
|
+
2. **归因分两支,不许合成一句**:`LoginAdvice.policyBlocked`。
|
|
96
|
+
- `true`(结构化事实:降级 / 拒绝 / 实际受限 / runner 挂)→「被文件策略挡在工作区之外」,
|
|
97
|
+
动作是切「完全权限」;
|
|
98
|
+
- `false`(事实干净、原文点名登录必须写的目标且带拒绝字样)→「**被这台机器拒绝**(不是 DSH 的
|
|
99
|
+
文件策略)」,明确写「切权限不会有用」,并给退路:氚云用 `crwu h3yun session bind --token`、
|
|
100
|
+
钉钉先试「设备码登录(无浏览器时)」。
|
|
101
|
+
|
|
102
|
+
上一版把两支合成了一句"请切完全权限"——员工按提示切了,而日志证明根本不是策略问题(**误导**)。
|
|
103
|
+
|
|
104
|
+
**已证伪**:把两支合成策略支 → 5 条变红;把 scratch 的 env 前缀去掉(只建目录不指过去)→ 2 条变红。
|
|
105
|
+
|
|
10
106
|
## package · 0.0.16 · 2026-09-29 · fix · Windows 氚云扫码临时目录回退
|
|
11
107
|
|
|
12
108
|
- 随包 `crwu.exe` 的 `h3yun session login` 在系统 `%TEMP%` 被 Windows 安全策略拒绝时,
|
|
@@ -220,41 +316,6 @@
|
|
|
220
316
|
② `crwu_audit_case_bootstrap` 的 `caseDir` 虽被钉在本轮案例目录,但 `objectId` 仍是提交进来的 ——
|
|
221
317
|
子会话可以用**别的** objectId 把别人的记录取进自己的案例目录;现在审核子会话调用它一律拒绝。
|
|
222
318
|
|
|
223
|
-
### G 段:登录失败归因 ——「登录必须写工作区之外」不再是原文(2026-09-29)
|
|
224
|
-
|
|
225
|
-
**症状**:员工第一次用(Windows 与 macOS 都报)时,点「扫码登录氚云」「钉钉登录」浏览器起不来;
|
|
226
|
-
面板给的却是 CLI 的原话 —— 氚云 `mkdir C:\…\Temp\crwu-scan-1799287186: Access is denied.`
|
|
227
|
-
(看着像 crwu 坏了)、钉钉 `acquiring file lock: …\.dws\.data.lock: Access is denied.`
|
|
228
|
-
(看着像 dws 自己的 bug)。
|
|
229
|
-
|
|
230
|
-
**根因(两条入口同一条边界)**:登录**必须写工作区之外的路径** ——
|
|
231
|
-
|
|
232
|
-
| 入口 | 必须写的东西 |
|
|
233
|
-
| --- | --- |
|
|
234
|
-
| `crwu h3yun session login` | `$TMPDIR`/`%TEMP%` 下的临时浏览器 profile(`crwu-scan-*`),再经 CDP 读会话 |
|
|
235
|
-
| `dws auth login`(含 `--device`) | `<HOME>/.dws/.data.lock`(拿登录态之前先抢文件锁) |
|
|
236
|
-
| 两者收尾 | 操作系统凭据存储(钥匙串 / Credential Manager) |
|
|
237
|
-
|
|
238
|
-
宿主按完全访问跑、操作系统的 ACL/受限令牌仍然拒绝时报的就是上面那两句;受限沙箱下浏览器即使
|
|
239
|
-
起来了也会在几毫秒内 renderer 崩溃(CDP 只回 `close 1006` / `Target crashed`)。
|
|
240
|
-
|
|
241
|
-
- **新增纯函数判据** `src/host/system/login-failure.ts`:`loginFailureAdvice()` / `describeLoginAdvice()`。
|
|
242
|
-
顺序是「结构化事实优先,文本最后」:① `runnerFailed` → ② 提权被降级(`requested !== resolved`)
|
|
243
|
-
→ ③ `denied === true` 或实际跑在受限模式 → ④ **事实干净但原文点名了登录必须写的工作区外目标
|
|
244
|
-
且带拒绝字样**(Windows 实测就是这一形态)。
|
|
245
|
-
- **第 ④ 条是 `sandboxDenialNote()` 的唯一例外**,判据本身变强了:目标不是"任意路径",而是登录
|
|
246
|
-
流程必须写的那几个已知目标。**目标词与拒绝字样必须同时命中** —— 只命中一个不归因(否则
|
|
247
|
-
`secret not found in keyring` 这种"真没条目 / 钥匙串被锁"会被说成"去切完全权限")。
|
|
248
|
-
- **`dwsLogin` 以前完全不做归因**(只有 `runDws` 做),现在两条登录都返回 `sandboxBlocked` + `advice`,
|
|
249
|
-
并把「在输入框下方的访问模式里选「完全权限」再点一次」写进 `error`,**原始报错保留**在末尾。
|
|
250
|
-
- **设备码不再是"退路"**:它同样要抢 `~/.dws` 的锁,所以按钮文案改回「设备码登录(无浏览器时)」,
|
|
251
|
-
归因文案里明说"在这个模式下也走不通"。
|
|
252
|
-
- **界面**:「账号连接」页在登录按钮下方**先说前置条件**(`envLoginSandboxHint`);
|
|
253
|
-
被归因时不再追加 stdout 尾巴(否则那句「正在打开浏览器窗口…」会把真正的动作挤掉)。
|
|
254
|
-
|
|
255
|
-
**已证伪**:把第 ④ 条整段关掉 → 7 条变红;把「目标词」那一半去掉(只认拒绝字样)→
|
|
256
|
-
误报用例变红。`cp` 还原后转绿。
|
|
257
|
-
|
|
258
319
|
### 测试
|
|
259
320
|
|
|
260
321
|
新增/扩写的测试文件:`host-access-consent` / `host-access-broker` / `host-access-classify` /
|
package/lib/client.js
CHANGED
|
@@ -2524,7 +2524,7 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
2524
2524
|
* 临时浏览器 profile、`<HOME>/.dws` 的登录态、操作系统凭据存储。文件策略不给时一条都走不通,
|
|
2525
2525
|
* 而这一点在前端看不出来(表现只是"浏览器没起来")。说在点之前,别让员工靠试错发现。
|
|
2526
2526
|
*/
|
|
2527
|
-
envLoginSandboxHint: "
|
|
2527
|
+
envLoginSandboxHint: "登录要在工作区之外起临时浏览器 / 更新登录态文件。报「拒绝访问」时看面板那句话:说「被文件策略挡下」就把访问模式切到「完全权限」再点一次;说「被这台机器拒绝」就是本机安全软件或临时目录权限的问题,切权限没有用。",
|
|
2528
2528
|
envDetailsTitle: "排查详情(维护者看)",
|
|
2529
2529
|
envDetailsTools: "插件内置组件明细",
|
|
2530
2530
|
envDetailsRuntime: "DSH 脚本运行时明细",
|
package/lib/index.js
CHANGED
|
@@ -231,7 +231,7 @@ const PLUGIN_INJECT = [
|
|
|
231
231
|
* 还会让打包器的 JSON 插件成为隐式依赖。代价是升版本时要同时改这里 ——
|
|
232
232
|
* `tests/unit/host-package.test.mjs` 有一条断言盯着它必须等于 `package.json` 的 version。
|
|
233
233
|
*/
|
|
234
|
-
const PLUGIN_VERSION = "0.0.
|
|
234
|
+
const PLUGIN_VERSION = "0.0.17";
|
|
235
235
|
/**
|
|
236
236
|
* `ping` / `boot` 应答里的版本指纹,形如 `pkg-0.0.5`。
|
|
237
237
|
*
|
|
@@ -3037,6 +3037,64 @@ function chmodCommand(mode, path, platform) {
|
|
|
3037
3037
|
return `chmod ${mode} ${shellQuote(path, platform)}`;
|
|
3038
3038
|
}
|
|
3039
3039
|
//#endregion
|
|
3040
|
+
//#region src/host/system/auth-scratch.ts
|
|
3041
|
+
/**
|
|
3042
|
+
* 登录命令的**私有 scratch 目录**(协议 19 · G 段,2026-09-29)。
|
|
3043
|
+
*
|
|
3044
|
+
* ## 为什么要由插件指定,而不是让 CLI 用系统 TEMP
|
|
3045
|
+
*
|
|
3046
|
+
* 扫码登录要就地起一个临时浏览器 profile,`crwu` 用 `os.MkdirTemp("", …)` 建它 ——
|
|
3047
|
+
* 也就是**系统 TEMP**。真实员工机器上实测(2026-09-29,Windows):
|
|
3048
|
+
*
|
|
3049
|
+
* ```
|
|
3050
|
+
* create fallback browser profile directory: mkdir C:\Users\51019\AppData\Local\crwu: Access is denied.
|
|
3051
|
+
* (system TEMP failed: mkdir C:\Users\51019\AppData\Local\Temp\crwu-scan-556823159: Access is denied.)
|
|
3052
|
+
* ```
|
|
3053
|
+
*
|
|
3054
|
+
* 两条路径都在 `AppData\Local` 下、都被拒,而**插件的日志说明 DSH 没有拦它**
|
|
3055
|
+
* (`requested = resolved = ran = danger-full-access`、`denied=false`)—— 拒它的是这台机器
|
|
3056
|
+
* 自己(安全软件的按二进制规则 / 低完整性令牌 / 该目录的 ACL)。同一条命令在
|
|
3057
|
+
* `%USERPROFILE%` 下(例如 `~/.dws`)却是通的。
|
|
3058
|
+
*
|
|
3059
|
+
* 所以插件不再把"我自己的 scratch 放哪"交给系统 TEMP:给它一个**插件自己长期在写、
|
|
3060
|
+
* 已经确认可写**的位置 —— `<home>/.dsh/crwu-workbench/auth-tmp`(同目录下已经有
|
|
3061
|
+
* `ifind-credential.json`),并在**同一条命令**里先把目录建出来、再把 `TMPDIR`/`TMP`/`TEMP`
|
|
3062
|
+
* 指过去。这样 CLI 与它拉起的浏览器 profile 都落在同一条已知可写的路径上。
|
|
3063
|
+
*
|
|
3064
|
+
* ## 三条约束
|
|
3065
|
+
*
|
|
3066
|
+
* 1. **不改 `LOCALAPPDATA`**:Chromium 自己的组件目录仍按系统默认走,改它只会让浏览器行为
|
|
3067
|
+
* 更难解释;这里只动"临时文件"那三个变量。
|
|
3068
|
+
* 2. **目录必须是插件状态目录下的固定叶子**(不接受任何调用方/模型输入):它与
|
|
3069
|
+
* `state/persist.ts` 推导的状态文件同根,越界无从谈起。
|
|
3070
|
+
* 3. **不伪造成功**:设不上(`home` 为空)就原样返回命令 —— 由 CLI 自己报它的 TEMP 错误,
|
|
3071
|
+
* 而不是我们假装指到了某个目录。
|
|
3072
|
+
*/
|
|
3073
|
+
/** scratch 目录相对主目录的叶子路径(唯一推导点)。 */
|
|
3074
|
+
const AUTH_SCRATCH_LEAF = [
|
|
3075
|
+
".dsh",
|
|
3076
|
+
"crwu-workbench",
|
|
3077
|
+
"auth-tmp"
|
|
3078
|
+
];
|
|
3079
|
+
/** 登录命令的 scratch 目录;主目录未知时返回空串(调用方据此走原命令)。 */
|
|
3080
|
+
function authScratchDir(home) {
|
|
3081
|
+
if (home === "") return "";
|
|
3082
|
+
return joinLocalPath(home, ...AUTH_SCRATCH_LEAF);
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* 把一条已经拼好的命令包成"先建 scratch 目录、再把临时目录指向它"。
|
|
3086
|
+
*
|
|
3087
|
+
* 两种方言各用自己那条 `mkdir`(`mkdir -p` / `New-Item -Force`),都来自
|
|
3088
|
+
* `platform/shell.ts` 的同一个生成器 —— 这里不再手写第三条。
|
|
3089
|
+
*/
|
|
3090
|
+
function withAuthScratch(command, dir, platform) {
|
|
3091
|
+
if (dir === "") return command;
|
|
3092
|
+
const quoted = shellQuote(dir, platform);
|
|
3093
|
+
const ensure = mkdirCommand(dir, platform);
|
|
3094
|
+
if (shellDialect(platform) === "powershell") return `${ensure}; $env:TMP=${quoted}; $env:TEMP=${quoted}; $env:TMPDIR=${quoted}; ${command}`;
|
|
3095
|
+
return `${ensure} && TMPDIR=${quoted} TMP=${quoted} TEMP=${quoted} ${command}`;
|
|
3096
|
+
}
|
|
3097
|
+
//#endregion
|
|
3040
3098
|
//#region src/host/platform/command.ts
|
|
3041
3099
|
/**
|
|
3042
3100
|
* 把「按名字调用的自带命令」解析成**包内绝对路径**;解析不到就原样回退那个名字。
|
|
@@ -3168,12 +3226,12 @@ async function runCrwu(ctx, argv, options) {
|
|
|
3168
3226
|
const operation = crwuOperationOf(clean);
|
|
3169
3227
|
if (operation === null) return failed(`该 crwu 子命令还没有登记本机访问操作,拒绝执行:${describeCrwuCommand(clean)}`);
|
|
3170
3228
|
const platform = options.platform ?? "";
|
|
3171
|
-
const
|
|
3229
|
+
const command = withAuthScratch(shellInvoke(await resolveBundledCommand(ctx, platform, "crwu"), clean.slice(1), platform), options.scratchDir ?? "", platform);
|
|
3172
3230
|
const result = await options.access.runShell({
|
|
3173
3231
|
operation,
|
|
3174
3232
|
source: options.source,
|
|
3175
3233
|
...options.workdir === void 0 ? {} : { workdir: options.workdir }
|
|
3176
|
-
},
|
|
3234
|
+
}, command, {
|
|
3177
3235
|
...options.workdir === void 0 ? {} : { workdir: options.workdir },
|
|
3178
3236
|
timeoutMs: options.timeoutMs ?? 6e4,
|
|
3179
3237
|
...options.stdoutMaxBytes === void 0 ? {} : { stdoutMaxBytes: options.stdoutMaxBytes },
|
|
@@ -12088,14 +12146,23 @@ const DENIED_MARKERS = [
|
|
|
12088
12146
|
"不允许",
|
|
12089
12147
|
"被拒绝"
|
|
12090
12148
|
];
|
|
12091
|
-
const
|
|
12092
|
-
h3yun: "氚云扫码登录要在工作区之外起一个临时浏览器配置目录(crwu-scan-*),当前文件策略把它挡下了",
|
|
12093
|
-
dws: "钉钉登录要先更新 <HOME>/.dws 里的登录态,当前文件策略把它挡下了"
|
|
12094
|
-
};
|
|
12095
|
-
const ACTIONS = {
|
|
12149
|
+
const POLICY_ACTIONS = {
|
|
12096
12150
|
h3yun: "在输入框下方的访问模式里选「完全权限」(或输入 /permission danger-full-access),回到「账号连接」再点一次「扫码登录氚云」。",
|
|
12097
12151
|
dws: "在输入框下方的访问模式里选「完全权限」(或输入 /permission danger-full-access),回到「账号连接」再点一次「钉钉登录」。设备码登录同样要更新 <HOME>/.dws 的登录态,在这个模式下也走不通。"
|
|
12098
12152
|
};
|
|
12153
|
+
/**
|
|
12154
|
+
* ② 策略允许、**这台机器**拒绝(2026-09-29 Windows 实测):
|
|
12155
|
+
* `requested = resolved = ran = danger-full-access`、`denied=false`,照样 `Access is denied`。
|
|
12156
|
+
* 这种时候叫人去切权限是**误导** —— 切了也没用。
|
|
12157
|
+
*/
|
|
12158
|
+
const MACHINE_REASONS = {
|
|
12159
|
+
h3yun: "氚云扫码登录要就地建一个临时浏览器配置目录(crwu-scan-*),而这台机器不让这条命令创建它",
|
|
12160
|
+
dws: "钉钉登录要先更新 <HOME>/.dws 里的登录态,而这台机器不让这条命令写它"
|
|
12161
|
+
};
|
|
12162
|
+
const MACHINE_ACTIONS = {
|
|
12163
|
+
h3yun: "这不是工作区权限问题:DSH 这次是以完全访问跑的(请求与实际都是 danger-full-access),切权限不会有用。请先看这台机器的安全软件(企业管控 / 杀软)是否拦了这条命令,以及临时目录的权限;确实起不了浏览器时,改用本机终端里的 `crwu h3yun session bind --token <JWT>` 绑定会话(令牌不要发到对话里)。",
|
|
12164
|
+
dws: "这不是工作区权限问题:DSH 这次是以完全访问跑的(请求与实际都是 danger-full-access),切权限不会有用。请先看这台机器的安全软件是否拦了这条命令,以及 <HOME>/.dws 的权限;也可以先试「设备码登录(无浏览器时)」,它同样写 <HOME>/.dws,但不开浏览器。"
|
|
12165
|
+
};
|
|
12099
12166
|
function textList(values) {
|
|
12100
12167
|
return values.map((value) => text(value)).filter((value) => value !== "").join("\n").toLowerCase();
|
|
12101
12168
|
}
|
|
@@ -12114,6 +12181,7 @@ function deniedOutsideWorkspace(kind, facts) {
|
|
|
12114
12181
|
function none() {
|
|
12115
12182
|
return {
|
|
12116
12183
|
blocked: false,
|
|
12184
|
+
policyBlocked: false,
|
|
12117
12185
|
reason: "",
|
|
12118
12186
|
action: ""
|
|
12119
12187
|
};
|
|
@@ -12131,28 +12199,33 @@ function loginFailureAdvice(kind, facts) {
|
|
|
12131
12199
|
const confined = (value) => value !== "" && value !== FULL_ACCESS;
|
|
12132
12200
|
if (sandbox?.runnerFailed === true) return {
|
|
12133
12201
|
blocked: true,
|
|
12202
|
+
policyBlocked: true,
|
|
12134
12203
|
reason: "DSH 的沙箱 runner 在命令跑起来之前就失败了(不是命令本身的业务失败)",
|
|
12135
|
-
action:
|
|
12204
|
+
action: POLICY_ACTIONS[kind]
|
|
12136
12205
|
};
|
|
12137
12206
|
if (requested !== "" && resolved !== "" && requested !== resolved) return {
|
|
12138
12207
|
blocked: true,
|
|
12208
|
+
policyBlocked: true,
|
|
12139
12209
|
reason: `提权请求被降级:请求 ${requested},执行器实际按 ${resolved} 跑`,
|
|
12140
|
-
action:
|
|
12210
|
+
action: POLICY_ACTIONS[kind]
|
|
12141
12211
|
};
|
|
12142
12212
|
if (sandbox?.denied === true) return {
|
|
12143
12213
|
blocked: true,
|
|
12214
|
+
policyBlocked: true,
|
|
12144
12215
|
reason: `DSH 沙箱拒绝了这次操作(${ran || resolved || requested || "当前模式"})`,
|
|
12145
|
-
action:
|
|
12216
|
+
action: POLICY_ACTIONS[kind]
|
|
12146
12217
|
};
|
|
12147
12218
|
if (confined(resolved) || confined(ran)) return {
|
|
12148
12219
|
blocked: true,
|
|
12220
|
+
policyBlocked: true,
|
|
12149
12221
|
reason: `登录进程实际跑在 ${ran || resolved} 下,写不了工作区之外的登录目标`,
|
|
12150
|
-
action:
|
|
12222
|
+
action: POLICY_ACTIONS[kind]
|
|
12151
12223
|
};
|
|
12152
12224
|
if (deniedOutsideWorkspace(kind, facts)) return {
|
|
12153
12225
|
blocked: true,
|
|
12154
|
-
|
|
12155
|
-
|
|
12226
|
+
policyBlocked: false,
|
|
12227
|
+
reason: MACHINE_REASONS[kind],
|
|
12228
|
+
action: MACHINE_ACTIONS[kind]
|
|
12156
12229
|
};
|
|
12157
12230
|
return none();
|
|
12158
12231
|
}
|
|
@@ -12164,7 +12237,7 @@ function loginFailureAdvice(kind, facts) {
|
|
|
12164
12237
|
*/
|
|
12165
12238
|
function describeLoginAdvice(kind, advice, original) {
|
|
12166
12239
|
if (!advice.blocked) return text(original);
|
|
12167
|
-
const head =
|
|
12240
|
+
const head = advice.policyBlocked ? `本机访问被文件策略挡在工作区之外:${advice.reason}。` : `本机访问被这台机器拒绝(不是 DSH 的文件策略):${advice.reason}。`;
|
|
12168
12241
|
const body = text(original).trim();
|
|
12169
12242
|
const evidence = body === "" ? "" : `(原始报错:${body.slice(0, 300)})`;
|
|
12170
12243
|
return `${head}${advice.action}${evidence}`;
|
|
@@ -12244,18 +12317,19 @@ function tails(run) {
|
|
|
12244
12317
|
};
|
|
12245
12318
|
}
|
|
12246
12319
|
/** 钉钉登录。`--device` 走设备码流程(无浏览器时用)。 */
|
|
12247
|
-
async function dwsLogin(deps, args) {
|
|
12320
|
+
async function dwsLogin(deps, args, home) {
|
|
12248
12321
|
const argv = [
|
|
12249
12322
|
await resolveBundledCommand(deps.ctx, deps.platform, "dws"),
|
|
12250
12323
|
"auth",
|
|
12251
12324
|
"login"
|
|
12252
12325
|
];
|
|
12253
12326
|
if (args.device === true) argv.push("--device");
|
|
12327
|
+
const command = withAuthScratch(shellInvoke(argv[0] ?? "", argv.slice(1), deps.platform), authScratchDir(home), deps.platform);
|
|
12254
12328
|
const run = await deps.access.runShell({
|
|
12255
12329
|
operation: "dws.auth.login",
|
|
12256
12330
|
source: "panel",
|
|
12257
12331
|
workdir: await deps.workdir()
|
|
12258
|
-
},
|
|
12332
|
+
}, command, {
|
|
12259
12333
|
timeoutMs: 3e5,
|
|
12260
12334
|
summary: "dws auth login"
|
|
12261
12335
|
});
|
|
@@ -12279,7 +12353,7 @@ async function dwsLogin(deps, args) {
|
|
|
12279
12353
|
};
|
|
12280
12354
|
}
|
|
12281
12355
|
/** 氚云重新登录(`crwu h3yun session login` 在提权白名单里)。 */
|
|
12282
|
-
async function relogin(deps) {
|
|
12356
|
+
async function relogin(deps, home) {
|
|
12283
12357
|
const run = await runCrwu(deps.ctx, [
|
|
12284
12358
|
"crwu",
|
|
12285
12359
|
"h3yun",
|
|
@@ -12290,7 +12364,8 @@ async function relogin(deps) {
|
|
|
12290
12364
|
timeoutMs: 3e5,
|
|
12291
12365
|
access: deps.access,
|
|
12292
12366
|
source: "panel",
|
|
12293
|
-
platform: deps.platform
|
|
12367
|
+
platform: deps.platform,
|
|
12368
|
+
scratchDir: authScratchDir(home)
|
|
12294
12369
|
});
|
|
12295
12370
|
const advice = loginFailureAdvice("h3yun", {
|
|
12296
12371
|
error: run.error,
|
|
@@ -14920,7 +14995,7 @@ function createCoreOperations(ctx, config, state, world, resolvers, extra = {})
|
|
|
14920
14995
|
access: resolvers.access,
|
|
14921
14996
|
platform: await world.platform(),
|
|
14922
14997
|
workdir: () => world.workdir()
|
|
14923
|
-
});
|
|
14998
|
+
}, await world.home());
|
|
14924
14999
|
gate.invalidate();
|
|
14925
15000
|
return result;
|
|
14926
15001
|
},
|
|
@@ -14961,7 +15036,7 @@ function createCoreOperations(ctx, config, state, world, resolvers, extra = {})
|
|
|
14961
15036
|
access: resolvers.access,
|
|
14962
15037
|
platform: await world.platform(),
|
|
14963
15038
|
workdir: () => world.workdir()
|
|
14964
|
-
}, args);
|
|
15039
|
+
}, args, await world.home());
|
|
14965
15040
|
gate.invalidate();
|
|
14966
15041
|
return result;
|
|
14967
15042
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-crwu-workbench",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "中瑞世联工作台 / CRWU audit workbench for DeepSeek Harness: pick a pending audit report, dispatch one AI audit subagent, watch and stop/restart it, auto-upload deliverables to Aliyun OSS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|