From c8aba83589453aba228764ee5e674546c00f2c24 Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:05:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(control):=20control.ps1=20=E7=9A=84=20`$sch?= =?UTF-8?q?eme:`=20=E8=A2=AB=E5=BD=93=20drive=20=E5=BC=95=E7=94=A8=20?= =?UTF-8?q?=E2=86=92=20=E6=95=B4=E4=B8=AA=E8=84=9A=E6=9C=AC=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E7=94=A8=EF=BC=88=E7=9C=9F=E6=9C=BA=E8=B8=A9=E5=88=B0?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 现象(Parallels Win11 ARM 真机) `control.ps1 -Action stop/start/status` 一律报: 所在位置 control.ps1:100 字符: 36 + Write-Host " 协议 : $scheme://0.0.0.0:$Port" 变量引用无效。':' 后面的变量名称字符无效。 ## 根因 PowerShell 把 `$scheme:` 解析为 **drive 引用**(如 `env:`),而 `scheme` 不是 drive → 语法错误。 且这是**解析期**错误 → **整个脚本无法运行**(不是只有那一行),stop/start/status 三个子命令全废。 修法:写成 `${scheme}://`(用花括号界定变量名)。 ## 改动 - `scripts/control.ps1:100`:`$scheme://` → `${scheme}://`,并加注释说明为什么必须用花括号 (注释里刻意**不写**危险字面量 —— 否则会被新守卫命中,这点在验证时真踩到了) - 新增守卫(`tests/unit/security-hardening.test.mjs`):扫全部 `.ps1/.psm1`, 禁止 `$ident:` 形式(白名单 `env`/`nuget`/`psitem`/`PSScriptRoot`) ## 验证 - 守卫 **9/9 绿**;全量单测 **274 绿**;ps1-encoding 保持通过(BOM 未被破坏) - **注入故障实测**:把 `${scheme}` 回退成 `$scheme:` → 守卫**立即红**(失败数 1)→ 恢复后绿 (按项目纪律:没验证过会红的守卫等于没有) - 全仓重扫同类模式:**仅此 1 处** --- scripts/control.ps1 | 5 ++++- tests/unit/security-hardening.test.mjs | 27 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/scripts/control.ps1 b/scripts/control.ps1 index 68eb408..313ef7b 100644 --- a/scripts/control.ps1 +++ b/scripts/control.ps1 @@ -97,7 +97,10 @@ function Show-Status { $inp = if ($cfg.allow_input) { "已启用" } else { "已禁用" } Write-Host " 输入控制 : $inp" $scheme = if ($cfg.tls) { "wss" } else { "ws" } - Write-Host " 协议 : $scheme://0.0.0.0:$Port" + Write-Host " 协议 : ${scheme}://0.0.0.0:$Port" + # ⚠️ 变量名必须用 ${ } 包起来:裸写「变量名后紧跟冒号」会被 PowerShell 当成 + # drive 引用(如 env:),属**解析期**错误 → 整个脚本都跑不起来, + # stop/start/status 三个子命令会全部失效(2026-10-11 真机踩到) } catch { Write-Warn "配置文件解析失败: $cfgPath" } diff --git a/tests/unit/security-hardening.test.mjs b/tests/unit/security-hardening.test.mjs index 8e07fc0..cb6128b 100644 --- a/tests/unit/security-hardening.test.mjs +++ b/tests/unit/security-hardening.test.mjs @@ -1,6 +1,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; /** * v2.0.0 连接层防护的**规则**测试(server.ts 的纯逻辑部分)。 @@ -104,3 +105,29 @@ test('自重启任务清理:清理动作带 -ErrorAction SilentlyContinue( '清理动作需容错,否则个别任务异常会中断整个重启流程', ); }); + +// ---------- PowerShell 语法类守卫($ident: 危险变量引用) ---------- + +test('PowerShell 脚本不得含 `$ident:` 形式(会被当 drive 引用 → 整个脚本解析失败)', () => { + // 2026-10-11 真机踩到:control.ps1 里写了 "…$scheme://0.0.0.0:$Port", + // PowerShell 把 `$scheme:` 当 drive 引用 → 报 "变量引用无效", + // 且因为是**解析期**错误,stop/start/status 三个子命令**全部不可用**。 + // 修法是写成 ${scheme}。这条守卫防同类再犯。 + const files = execFileSync('git', ['ls-files'], { encoding: 'utf8' }) + .split('\n') + .filter((f) => f.endsWith('.ps1') || f.endsWith('.psm1')); + assert.ok(files.length >= 3, '应找到若干 .ps1(install/control/rescue 等)'); + + const ALLOW = new Set(['env', 'nuget', 'psitem', 'PSScriptRoot']); + const BAD = /\$([A-Za-z_][A-Za-z0-9_]*):/g; + const offenders = []; + for (const f of files) { + const text = readFileSync(new URL(`../../${f}`, import.meta.url), 'utf8'); + text.split('\n').forEach((line, i) => { + for (const m of line.matchAll(BAD)) { + if (!ALLOW.has(m[1])) offenders.push(`${f}:${i + 1} $${m[1]}:`); + } + }); + } + assert.deepEqual(offenders, [], `发现危险变量引用(改用 \${name}):\n${offenders.join('\n')}`); +});