fix(auth): prevent repository .env files from injecting workspace bindings (Closes #704)
This commit is contained in:
+62
-4
@@ -22,8 +22,62 @@ import gitea_config
|
||||
|
||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Load standard .env if present
|
||||
load_dotenv(os.path.join(PROJECT_ROOT, ".env"))
|
||||
# Reserved runtime-control / workspace-binding environment variables (#704).
|
||||
# Repository .env files MUST NOT populate or override any of these keys.
|
||||
RESERVED_WORKTREE_ENV_KEYS: frozenset[str] = frozenset({
|
||||
"GITEA_ACTIVE_WORKTREE",
|
||||
"GITEA_AUTHOR_WORKTREE",
|
||||
"GITEA_REVIEWER_WORKTREE",
|
||||
"GITEA_MERGER_WORKTREE",
|
||||
"GITEA_RECONCILER_WORKTREE",
|
||||
})
|
||||
|
||||
|
||||
def is_reserved_worktree_env_key(key: str | None) -> bool:
|
||||
"""Return True if *key* is a reserved runtime workspace binding variable (#704)."""
|
||||
if not key:
|
||||
return False
|
||||
k = str(key).upper().strip()
|
||||
return k in RESERVED_WORKTREE_ENV_KEYS or (k.startswith("GITEA_") and k.endswith("_WORKTREE"))
|
||||
|
||||
|
||||
def load_env_file_sanitized(
|
||||
env_path: str,
|
||||
*,
|
||||
target_env: dict | os._Environ | None = None,
|
||||
) -> list[str]:
|
||||
"""Load a .env file without populating or overriding reserved workspace keys (#704).
|
||||
|
||||
Pre-existing process environment values retain their precedence. Reserved
|
||||
runtime-control keys found in repository files are ignored (without logging
|
||||
their values). Returns a list of sanitized rejection reasons.
|
||||
"""
|
||||
if target_env is None:
|
||||
target_env = os.environ
|
||||
if not os.path.exists(env_path) or os.path.isdir(env_path):
|
||||
return []
|
||||
|
||||
rejection_reasons: list[str] = []
|
||||
try:
|
||||
file_vals = dotenv_values(env_path)
|
||||
for key, val in file_vals.items():
|
||||
if not key or val is None:
|
||||
continue
|
||||
if is_reserved_worktree_env_key(key):
|
||||
filename = os.path.basename(env_path)
|
||||
rejection_reasons.append(
|
||||
f"Ignored reserved runtime workspace key '{key}' from repository {filename}"
|
||||
)
|
||||
continue
|
||||
if key not in target_env:
|
||||
target_env[key] = val
|
||||
except Exception:
|
||||
pass
|
||||
return rejection_reasons
|
||||
|
||||
|
||||
# Load standard .env if present (sanitized to prevent repo workspace binding contamination #704)
|
||||
load_env_file_sanitized(os.path.join(PROJECT_ROOT, ".env"))
|
||||
|
||||
# Dictionary to store configurations parsed dynamically from .env.* files
|
||||
DYNAMIC_CONFIGS = {}
|
||||
@@ -37,9 +91,13 @@ for env_path in glob.glob(os.path.join(PROJECT_ROOT, ".env*")):
|
||||
continue
|
||||
try:
|
||||
config_vals = dotenv_values(env_path)
|
||||
site = config_vals.get("GITEA_SITE") or config_vals.get("GITEA_HOST")
|
||||
# Filter out reserved workspace keys from dynamic configs (#704)
|
||||
sanitized_config = {
|
||||
k: v for k, v in config_vals.items() if not is_reserved_worktree_env_key(k)
|
||||
}
|
||||
site = sanitized_config.get("GITEA_SITE") or sanitized_config.get("GITEA_HOST")
|
||||
if site:
|
||||
DYNAMIC_CONFIGS[site.lower().strip()] = config_vals
|
||||
DYNAMIC_CONFIGS[site.lower().strip()] = sanitized_config
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user