refuse profile names that Win32 resolves onto another account's directory

Win32 strips a trailing dot off a path component and python's normalisation
does not, so such a name is not the directory it reads as. REWARDS_ACCOUNTS=...
resolved to data-dir itself, which is the default profile the resolved-path
check was added to keep named accounts out of, and personal,personal. passed
the duplicate check as two entries while sharing one profile on disk. Both
break the one-directory-per-account guarantee this module exists for.

Reject the shape by name, and resolve with realpath rather than abspath so a
link or a junction under data-dir is followed to where it really goes.
This commit is contained in:
Ethan Stoner
2026-08-28 08:53:37 -07:00
parent 3e9e953c75
commit 0f18795f51
+12 -5
View File
@@ -50,9 +50,11 @@ def _named(name: str) -> Account:
# The name passed the character check, but that only constrains the
# characters, not where they end up pointing. Confirm against the resolved
# path, which is the thing Edge is actually handed.
root = os.path.abspath(USER_DATA_DIR)
resolved = os.path.abspath(user_data_dir)
# path, which is the thing Edge is actually handed. realpath rather than
# abspath, so a link or a junction under the profile directory is followed
# to where it really goes instead of being taken at face value.
root = os.path.realpath(USER_DATA_DIR)
resolved = os.path.realpath(user_data_dir)
if os.path.commonpath([root, resolved]) != root or resolved == root:
raise ValueError(
@@ -87,10 +89,15 @@ def configured() -> list[Account]:
accounts: list[Account] = []
for name in names:
if not SAFE_NAME.match(name) or name in RESERVED_NAMES:
# The trailing dot is not cosmetic. Win32 strips one off a path
# component and python's normalisation does not, so such a name means a
# different directory than it reads as: "work." is "work", and "..." is
# the profile directory itself. Either way two entries end up sharing
# one profile, which is the one thing this module exists to prevent.
if not SAFE_NAME.match(name) or name in RESERVED_NAMES or name.endswith("."):
raise ValueError(
f"{ENV_VAR} entry {name!r} is not usable as a directory name; "
"use letters, digits, dot, dash or underscore"
"use letters, digits, dot, dash or underscore, and do not end in a dot"
)
# Duplicates would run the same profile twice, which earns nothing the