Files
nodeagent/scripts/install.cmd
T
jkooe 921ff89edf feat(v23): 连接层三项防护 + 安装期密钥改 stdin(堵住最后一条密钥暴露面)
## ① 安装期密钥不再走命令行(路线图方向三,最后一条暴露面)
此前 install.cmd 用 -Key 传参,**同机其他用户可从 Win32_Process.CommandLine
明文读到**。现改为管道喂 stdin:
- install.ps1 新增 -KeyFromStdin:先判 [Console]::IsInputRedirected 再读
  (没管道时不阻塞)、Trim 去 CRLF、hex 16-128 校验;优先级 -Key > stdin > 复用既有 > 新生成
- install.cmd:echo %PSK%| powershell ... -KeyFromStdin(管道符前不留空格,
  否则空格会被 echo 进密钥 —— 老坑)

## ② 握手失败封禁(此前只计数,攻击者可无限重试)
- 同来源连续失败达 max_attempts(默认 5)→ 拉黑 ban_ms × 2^(N-max),指数退避封顶 24h;
  封禁期过计数保留,继续翻倍
- 5 个失败路径统一记账(挑战过期/nonce 不匹配/未注册/Ed25519 失败/PSK 失败);
  **认证成功即清零**(不误伤手滑的控制端)
- 白名单来源永不封禁;归一化剥离 ::ffff: 前缀(否则同来源被当两个键)
- 在连接入口挡(TLS 之后、业务握手之前),错误响应带 retry_after_ms
- 封禁状态放 core(handleAuth 在内),AgentCore 暴露 banRemainingMs/states

## ③ 连接上限 + 空闲断开
- max_connections 默认 8(超限 close 1013);idle_timeout_ms 默认 30 分钟
- 空闲清扫**保守**:只要还有任务在跑或还有事件订阅就不断 —— 不把挂着长任务
  等结果的控制端踢下线(新增 runningTaskCount / watchCount 两个判据)
- states 由 WeakMap 改 Map(需要 size 与遍历),close 时手动 delete 防泄漏
- 新增审计类型 session.idle_close(console 的 AuditType 同步,契约测试守住)

## 验证
- 单测 **227 全绿**(新增 security-hardening 6 例:退避公式翻倍/封顶、IP 归一化、
  白名单、hex 校验、配置形状);e2e **30** 全绿
- **本地真机三场景**:正确密钥可连 → 连续错误被拒 → 封禁期连正确密钥也被拒 ✓
- ⚠️ 待 Windows 真机:install.cmd 双击首装(改了安装入口,必须验管道喂钥)
2026-10-07 21:23:37 +08:00

113 lines
5.1 KiB
Batchfile

@echo off
REM ============================================================================
REM nodeagent - one-click installer launcher (Windows)
REM
REM Double-click this file. It will:
REM 1) self-elevate to Administrator (UAC prompt) if not already elevated
REM 2) make sure a pre-shared key exists in PSK.txt (generate one if absent)
REM 3) run install.ps1, which registers the scheduled task, opens the
REM firewall and starts the agent
REM
REM Usage: install.cmd [port]
REM port defaults to 8765. Pass a port only when the default is taken.
REM
REM IMPORTANT - why this file is pure ASCII:
REM The CMD.exe code page (936/GBK on Chinese Windows) can NOT round-trip
REM UTF-8 Chinese reliably. Any non-ASCII byte here risks mojibake or a
REM broken command. All Chinese-facing text is delegated to install.ps1,
REM which is saved as UTF-8 **with BOM** so Windows PowerShell 5.1 reads
REM it correctly. Keep this launcher ASCII-only.
REM
REM NOTE - install.ps1 MUST keep its UTF-8 BOM. PowerShell 5.1 parses
REM BOM-less .ps1 as ANSI/GBK, and the Chinese comments then break the
REM string boundaries -> a cascade of bogus syntax errors.
REM ============================================================================
setlocal EnableExtensions
set "HERE=%~dp0"
set "PS1=%HERE%install.ps1"
set "PSKFILE=%HERE%PSK.txt"
set "PORT=%~1"
if not defined PORT set "PORT=8765"
if not exist "%PS1%" (
echo [x] install.ps1 not found next to this file.
echo Folder: "%HERE%"
echo Keep install.ps1, node.exe, agent.mjs and this launcher together.
if not defined CI pause
exit /b 1
)
REM --- 1) elevate to Administrator -------------------------------------------
REM fltmc is used instead of `net session` because the latter depends on the
REM Server service, which is disabled on some builds - that would make an
REM already-elevated shell look unelevated and trigger a pointless UAC loop.
fltmc >nul 2>&1
if errorlevel 1 (
echo [*] Requesting Administrator rights...
echo A new window will open. Continue there; this one closes by itself.
REM Start-Process re-launches this .cmd elevated. %PORT% (already resolved to
REM its default) is forwarded so "install.cmd 18770" survives the elevation.
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"Start-Process -FilePath '%~f0' -ArgumentList '%PORT%' -Verb RunAs" >nul 2>&1
if errorlevel 1 (
echo [x] Elevation was declined - cannot register a scheduled task.
if not defined CI pause
exit /b 1
)
exit /b 0
)
REM --- 2) make sure the pre-shared key exists ---------------------------------
set "PSK="
if exist "%PSKFILE%" (
REM Read + Trim in one shot: a stray CR from CRLF or an editor-added trailing
REM space would otherwise end up inside -Key and break authentication.
for /f "delims=" %%K in ('powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"if (Test-Path -LiteralPath '%PSKFILE%') { (Get-Content -LiteralPath '%PSKFILE%' -Raw).Trim() }"') do set "PSK=%%K"
)
if defined PSK (
echo [+] Using key from PSK.txt
) else (
REM No PSK.txt -> let install.ps1 handle it: it reuses the key from an existing
REM agent.json when present, otherwise generates one (in PowerShell, where quoting
REM is safe). Do NOT generate here in cmd: routing a PS one-liner with pipes through
REM `for /f` requires ^| escaping that leaks into PowerShell and breaks the parse
REM (real machine 2026-10-07: "Unexpected token '^'" - that is exactly the
REM first-run path, since PSK.txt is no longer shipped in the zip).
echo [*] No usable PSK.txt found - install.ps1 will reuse or generate the key
)
REM --- 3) install -------------------------------------------------------------
echo.
echo [*] Installing nodeagent ^(node: %COMPUTERNAME%, port %PORT%, input control ON^)...
echo.
REM v23: the key now travels via PIPE (stdin), not the -Key command-line arg.
REM Why: command lines are readable by other users on this machine
REM (Win32_Process.CommandLine / Task Manager show them in clear text).
REM NOTE: there must be NO space before the pipe - `echo key |` would fold the
REM space into the echoed value and corrupt the key.
if defined PSK (
echo %PSK%| powershell -NoProfile -ExecutionPolicy Bypass -File "%PS1%" -NodeId "%COMPUTERNAME%" -AllowInput -Port %PORT% -KeyFromStdin
) else (
powershell -NoProfile -ExecutionPolicy Bypass -File "%PS1%" -NodeId "%COMPUTERNAME%" -AllowInput -Port %PORT%
)
set "RC=%ERRORLEVEL%"
echo.
if "%RC%"=="0" (
echo [+] Done. The agent now starts automatically at every logon.
echo To manage it later, double-click control.cmd in this folder.
) else (
echo [x] install.ps1 exited with code %RC%.
echo If the error mentions a missing file, re-extract the whole package.
echo If Windows Firewall or a security suite blocked something, allow it and retry.
)
echo.
REM Keep the window open so the user can read the output. CI sets CI=true,
REM where `pause` would hang forever waiting for a keypress.
if not defined CI pause
exit /b %RC%