fix(control): control.ps1 的 $scheme: 被当 drive 引用 → 整个脚本不可用(真机踩到)
## 现象(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 处**
This commit is contained in:
1 parent
3b05f5b373
commit
c8aba83589
2 files changed
+31
-1
No files matched your search
+4
-1
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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')}`);
|
||||
});
|
||||
Reference in new issue
Block a user