fix(auth): prevent repository .env files from injecting workspace bindings (Closes #704)

This commit is contained in:
2026-07-25 19:12:54 -04:00
parent 2b4e43042a
commit ef34d938bf
2 changed files with 228 additions and 4 deletions
+62 -4
View File
@@ -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