diff --git a/src/accounts.py b/src/accounts.py index 642f773..dce2725 100644 --- a/src/accounts.py +++ b/src/accounts.py @@ -19,9 +19,15 @@ from constants import USER_DATA_DIR, PROFILE_NAME ENV_VAR = "REWARDS_ACCOUNTS" # Names become directory names, so keep them to something a filesystem and a -# command line both handle without quoting. +# command line both handle without quoting. The character set alone is not +# enough: "." and ".." are made of allowed characters and still walk out of the +# directory, so they are rejected by name below and the resolved path is +# checked as well. SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]+$") +# Reserved by every filesystem that has directories at all. +RESERVED_NAMES = {".", ".."} + @dataclass(frozen=True) class Account: @@ -40,9 +46,22 @@ def _named(name: str) -> Account: # Each account gets its own directory under the configured one, so the # existing data-dir stays where it is and the new ones sit beside the # profile it already holds. + user_data_dir = os.path.join(USER_DATA_DIR, name) + + # 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) + + if os.path.commonpath([root, resolved]) != root or resolved == root: + raise ValueError( + f"{ENV_VAR} entry {name!r} resolves outside the profile directory" + ) + return Account( name=name, - user_data_dir=os.path.join(USER_DATA_DIR, name), + user_data_dir=user_data_dir, profile_name=PROFILE_NAME, ) @@ -68,7 +87,7 @@ def configured() -> list[Account]: accounts: list[Account] = [] for name in names: - if not SAFE_NAME.match(name): + if not SAFE_NAME.match(name) or name in RESERVED_NAMES: raise ValueError( f"{ENV_VAR} entry {name!r} is not usable as a directory name; " "use letters, digits, dot, dash or underscore" diff --git a/src/main.py b/src/main.py index 074a99c..8ce6b7a 100644 --- a/src/main.py +++ b/src/main.py @@ -5,8 +5,6 @@ import sys import log_utils import accounts import rewards_tasks -import mouse_trajectory -import mimic_typing from selenium import webdriver from selenium.common.exceptions import SessionNotCreatedException @@ -54,9 +52,6 @@ def run_account(account: accounts.Account) -> bool: return False try: - mouse = mouse_trajectory.MouseUtils(driver) - keyboard = mimic_typing.KeyboardUtils(driver) - rewards = rewards_tasks.RewardsTaskUtils(driver) rewards.complete_all_tasks() finally: diff --git a/src/queries.py b/src/queries.py index 6f166e7..cca3efd 100644 --- a/src/queries.py +++ b/src/queries.py @@ -49,7 +49,7 @@ def search_query_for_task(task_description: str) -> str: # Every feed was unreachable. The description still contains the topic, # so a trimmed version beats skipping the card entirely. - logger.warning("No query source reachable, using the task description as written.") + logger.warning("No query source reachable, using the lowercased task description.") return task_description.lower()