mirror of
https://github.com/User0332/rewards-farmer.git
synced 2026-09-16 09:31:36 +00:00
reject profile names that resolve outside data-dir
The character check allowed "." and "..", which are made entirely of allowed characters and still walk out of the directory, so the traversal guard only stopped the cases containing a separator. Reject both by name and check the resolved path against the profile root as well, since the character set constrains the characters rather than where they point. Also drop the MouseUtils and KeyboardUtils built in run_account and never used, RewardsTaskUtils builds its own, along with the two imports that leaves unused, and say "lowercased" in the no-source warning, which is what the code returns.
This commit is contained in:
+22
-3
@@ -19,9 +19,15 @@ from constants import USER_DATA_DIR, PROFILE_NAME
|
|||||||
ENV_VAR = "REWARDS_ACCOUNTS"
|
ENV_VAR = "REWARDS_ACCOUNTS"
|
||||||
|
|
||||||
# Names become directory names, so keep them to something a filesystem and a
|
# 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._-]+$")
|
SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]+$")
|
||||||
|
|
||||||
|
# Reserved by every filesystem that has directories at all.
|
||||||
|
RESERVED_NAMES = {".", ".."}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Account:
|
class Account:
|
||||||
@@ -40,9 +46,22 @@ def _named(name: str) -> Account:
|
|||||||
# Each account gets its own directory under the configured one, so the
|
# 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
|
# existing data-dir stays where it is and the new ones sit beside the
|
||||||
# profile it already holds.
|
# 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(
|
return Account(
|
||||||
name=name,
|
name=name,
|
||||||
user_data_dir=os.path.join(USER_DATA_DIR, name),
|
user_data_dir=user_data_dir,
|
||||||
profile_name=PROFILE_NAME,
|
profile_name=PROFILE_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,7 +87,7 @@ def configured() -> list[Account]:
|
|||||||
accounts: list[Account] = []
|
accounts: list[Account] = []
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
if not SAFE_NAME.match(name):
|
if not SAFE_NAME.match(name) or name in RESERVED_NAMES:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"{ENV_VAR} entry {name!r} is not usable as a directory name; "
|
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"
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import sys
|
|||||||
import log_utils
|
import log_utils
|
||||||
import accounts
|
import accounts
|
||||||
import rewards_tasks
|
import rewards_tasks
|
||||||
import mouse_trajectory
|
|
||||||
import mimic_typing
|
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.common.exceptions import SessionNotCreatedException
|
from selenium.common.exceptions import SessionNotCreatedException
|
||||||
|
|
||||||
@@ -54,9 +52,6 @@ def run_account(account: accounts.Account) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mouse = mouse_trajectory.MouseUtils(driver)
|
|
||||||
keyboard = mimic_typing.KeyboardUtils(driver)
|
|
||||||
|
|
||||||
rewards = rewards_tasks.RewardsTaskUtils(driver)
|
rewards = rewards_tasks.RewardsTaskUtils(driver)
|
||||||
rewards.complete_all_tasks()
|
rewards.complete_all_tasks()
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ def search_query_for_task(task_description: str) -> str:
|
|||||||
|
|
||||||
# Every feed was unreachable. The description still contains the topic,
|
# Every feed was unreachable. The description still contains the topic,
|
||||||
# so a trimmed version beats skipping the card entirely.
|
# 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()
|
return task_description.lower()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user