fix(winrm): resolve System32 path for cmd test on minimal PATH (0.20.25)

WinRM run_cmd(hostname) failed on hosts where System32 is not in PATH.
This commit is contained in:
2026-06-21 20:49:01 +10:00
parent 75eec35365
commit 7397ec30cd
6 changed files with 37 additions and 7 deletions
+23 -1
View File
@@ -211,6 +211,28 @@ def _finalize_winrm_run(
) )
_SYSTEM32_CMDS = {
"hostname": "hostname.exe",
"qwinsta": "qwinsta.exe",
"logoff": "logoff.exe",
}
def _resolve_winrm_cmd(remote_cmd: str) -> str:
"""WinRM cmd shell may have minimal PATH — use explicit System32 for built-ins."""
text = remote_cmd.strip()
if not text:
return text
parts = text.split(None, 1)
name = parts[0].lower()
rest = parts[1] if len(parts) > 1 else ""
exe = _SYSTEM32_CMDS.get(name)
if exe:
suffix = f" {rest}" if rest else ""
return f"%SystemRoot%\\System32\\{exe}{suffix}"
return text
def run_winrm_ps( def run_winrm_ps(
*, *,
target: str, target: str,
@@ -269,7 +291,7 @@ def run_winrm_cmd(
target = target.strip() target = target.strip()
try: try:
session, winrm = _winrm_session(target, user, password, timeout_sec=timeout_sec) session, winrm = _winrm_session(target, user, password, timeout_sec=timeout_sec)
result = session.run_cmd(remote_cmd) result = session.run_cmd(_resolve_winrm_cmd(remote_cmd))
except winrm.exceptions.WinRMTransportError as exc: except winrm.exceptions.WinRMTransportError as exc:
detail = str(exc) detail = str(exc)
hint = "" hint = ""
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI).""" """Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center" APP_NAME = "Security Alert Center"
APP_VERSION = "0.20.24" APP_VERSION = "0.20.25"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+2 -2
View File
@@ -4,6 +4,6 @@ from app.version import APP_NAME, APP_VERSION, APP_VERSION_LABEL
def test_version_constants(): def test_version_constants():
assert APP_VERSION == "0.20.22" assert APP_VERSION == "0.20.25"
assert APP_NAME == "Security Alert Center" assert APP_NAME == "Security Alert Center"
assert APP_VERSION_LABEL == "Security Alert Center v.0.20.22" assert APP_VERSION_LABEL == "Security Alert Center v.0.20.25"
+8
View File
@@ -95,6 +95,14 @@ def test_rdp_bundle_zip_roundtrip(tmp_path: Path, monkeypatch):
assert get_rdp_bundle_zip(token) == zip_bytes assert get_rdp_bundle_zip(token) == zip_bytes
def test_resolve_winrm_cmd_uses_system32_for_builtins():
from app.services.winrm_connect import _resolve_winrm_cmd
assert _resolve_winrm_cmd("hostname") == "%SystemRoot%\\System32\\hostname.exe"
assert _resolve_winrm_cmd("qwinsta") == "%SystemRoot%\\System32\\qwinsta.exe"
assert _resolve_winrm_cmd("logoff 2 /v") == "%SystemRoot%\\System32\\logoff.exe 2 /v"
def test_run_winrm_rdp_monitor_update_downloads_bundle_from_sac(tmp_path: Path): def test_run_winrm_rdp_monitor_update_downloads_bundle_from_sac(tmp_path: Path):
repo = tmp_path / "repo" repo = tmp_path / "repo"
repo.mkdir() repo.mkdir()
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "sac-ui", "name": "sac-ui",
"private": true, "private": true,
"version": "0.11.2", "version": "0.11.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+1 -1
View File
@@ -1,4 +1,4 @@
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */ /** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
export const APP_NAME = "Security Alert Center"; export const APP_NAME = "Security Alert Center";
export const APP_VERSION = "0.20.24"; export const APP_VERSION = "0.20.25";
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;