fix(lark): keep Windows SID lookup working with non-ASCII account names (#7845)

* Keep the Windows SID lookup alive on non-UTF-8 console output

`_resolve_current_user_sid` calls `whoami /user /fo csv /nh` in text mode
without an encoding. `make dev` and `make gateway` start the Gateway with
PYTHONUTF8=1, so on a Chinese Windows host the interpreter decodes as UTF-8
while whoami writes the account name in the console's ANSI code page. The
strict decode raises inside subprocess's reader thread; the exception is not
re-raised, so stdout comes back as None and the credential-tree hardening
walk dies with `AttributeError: 'NoneType' object has no attribute 'strip'`
before any directory is created or hardened.

Pin the encoding and keep an errors policy. The SID is the second CSV field
and is pure ASCII, as are the delimiters, so replacing the undecodable
account name is harmless. #6590 pinned the seven lark-cli/npm captures in
this file; this is the eighth text-mode capture and, unlike those, does not
emit UTF-8, so the errors policy is the part that matters here.

Add a cross-platform regression test next to the #6590 encoding tests.

* docs(lark): correct the SID capture wording

`undecodable` is the spelling used elsewhere in the repository, and the
comment about the console code page reads better as "which does not need to
match". No behaviour change.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
dengzi 2026-11-05 15:09:13 +08:00 committed by GitHub
parent bef8c9c2e3
commit 24d93399fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 0 deletions

View File

@ -488,6 +488,13 @@ def _resolve_current_user_sid() -> str:
["whoami", "/user", "/fo", "csv", "/nh"],
capture_output=True,
text=True,
encoding="utf-8",
# whoami writes the account name in the console's ANSI code page, which does
# not need to match the interpreter's encoding (e.g. UTF-8 mode on a Chinese Windows).
# Without errors="replace" the decoder raises inside subprocess's reader
# thread and leaves stdout as None. Only the SID (ASCII) and the CSV
# delimiters matter here, so replacing the account name is harmless.
errors="replace",
check=False,
)
if result.returncode != 0:

View File

@ -17,6 +17,12 @@ that names no codec is decoded with one that mangles UTF-8, while a call that
pins its own ``encoding`` is passed through untouched. That reproduces the
behaviour of the pre-fix call sites on a cp936/cp1252 host even when the test
host itself is UTF-8.
The Windows SID lookup is a separate capture in the same module: ``whoami``
writes the account name in the console's ANSI code page rather than UTF-8, so a
pinned UTF-8 decode still needs an ``errors`` policy. Only the SID (ASCII) and
the CSV delimiters are used, but a strict decode kills the pipe reader thread
and turns ``stdout`` into ``None`` before the SID can be read.
"""
from __future__ import annotations
@ -28,6 +34,7 @@ import subprocess
import sys
import textwrap
from pathlib import Path
from types import SimpleNamespace
import pytest
@ -180,3 +187,38 @@ def test_unpinned_capture_corrupts_the_same_output(fake_cli) -> None:
# A UTF-8 host locale: the locale decode happens to be correct here.
return
assert EXPECTED_USER not in raw
def test_windows_sid_resolution_tolerates_non_utf8_account_name(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The Windows credential-tree walk keeps its SID when the account name is CJK.
``whoami /user /fo csv /nh`` prints ``"<domain>\\<user>","<sid>"`` with the
account name in the console's ANSI code page (the bytes below are a GBK user
name). A text-mode capture that names no codec is decoded with the
interpreter's encoding; on a UTF-8-mode host the strict decode raises inside
subprocess's reader thread and ``stdout`` comes back as ``None``.
``ensure_lark_cli_credential_tree`` then fails with ``AttributeError`` before
it can harden any credential directory. The SID itself is ASCII, so replacing
the undecodable account name is enough to keep the CSV parse intact.
"""
raw = b'"DESKTOP-ABC\\\xb5\xca\xd7\xd3","S-1-5-21-1-2-3-1001"\r\n'
expected_sid = "S-1-5-21-1-2-3-1001"
def ansi_whoami(_argv, **kwargs):
if kwargs.get("text") or kwargs.get("universal_newlines"):
encoding = kwargs.get("encoding")
if encoding is None:
# Emulate the reader thread dying on a strict locale decode.
return SimpleNamespace(returncode=0, stdout=None, stderr="")
stdout = raw.decode(encoding, kwargs.get("errors") or "strict")
else:
stdout = raw
return SimpleNamespace(returncode=0, stdout=stdout, stderr="")
# Control: without an errors policy this capture really does lose stdout.
assert ansi_whoami(["whoami"], text=True).stdout is None
monkeypatch.setattr(lark_cli.subprocess, "run", ansi_whoami)
assert lark_cli._resolve_current_user_sid() == expected_sid