harden security around attachment functionality and explicitly disallow reading sensitive files
This commit is contained in:
@@ -7,6 +7,7 @@ import ssl
|
||||
import asyncio
|
||||
import functools
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from googleapiclient.errors import HttpError
|
||||
@@ -29,6 +30,100 @@ class UserInputError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# Directories from which local file reads are allowed.
|
||||
# The user's home directory is the default safe base.
|
||||
# Override via ALLOWED_FILE_DIRS env var (colon-separated paths).
|
||||
_ALLOWED_FILE_DIRS_ENV = "ALLOWED_FILE_DIRS"
|
||||
|
||||
|
||||
def _get_allowed_file_dirs() -> list[Path]:
|
||||
"""Return the list of directories from which local file access is permitted."""
|
||||
env_val = os.environ.get(_ALLOWED_FILE_DIRS_ENV)
|
||||
if env_val:
|
||||
return [Path(p).resolve() for p in env_val.split(":") if p.strip()]
|
||||
home = Path.home()
|
||||
return [home] if home else []
|
||||
|
||||
|
||||
def validate_file_path(file_path: str) -> Path:
|
||||
"""
|
||||
Validate that a file path is safe to read from the server filesystem.
|
||||
|
||||
Resolves the path canonically (following symlinks), then verifies it falls
|
||||
within one of the allowed base directories. Rejects paths to sensitive
|
||||
system locations regardless of allowlist.
|
||||
|
||||
Args:
|
||||
file_path: The raw file path string to validate.
|
||||
|
||||
Returns:
|
||||
Path: The resolved, validated Path object.
|
||||
|
||||
Raises:
|
||||
ValueError: If the path is outside allowed directories or targets
|
||||
a sensitive location.
|
||||
FileNotFoundError: If the resolved path does not exist.
|
||||
"""
|
||||
resolved = Path(file_path).resolve()
|
||||
|
||||
# Block sensitive file patterns regardless of allowlist
|
||||
resolved_str = str(resolved)
|
||||
file_name = resolved.name.lower()
|
||||
|
||||
# Block .env files and variants (.env, .env.local, .env.production, etc.)
|
||||
if file_name == ".env" or file_name.startswith(".env."):
|
||||
raise ValueError(
|
||||
f"Access to '{resolved_str}' is not allowed: "
|
||||
".env files may contain secrets and cannot be read, uploaded, or attached."
|
||||
)
|
||||
|
||||
# Block well-known sensitive system paths (including macOS /private variants)
|
||||
sensitive_prefixes = (
|
||||
"/proc", "/sys", "/dev",
|
||||
"/etc/shadow", "/etc/passwd",
|
||||
"/private/etc/shadow", "/private/etc/passwd",
|
||||
)
|
||||
for prefix in sensitive_prefixes:
|
||||
if resolved_str == prefix or resolved_str.startswith(prefix + "/"):
|
||||
raise ValueError(
|
||||
f"Access to '{resolved_str}' is not allowed: "
|
||||
"path is in a restricted system location."
|
||||
)
|
||||
|
||||
# Block other credential/secret file patterns
|
||||
sensitive_names = {
|
||||
".credentials", ".credentials.json", "credentials.json",
|
||||
"client_secret.json", "client_secrets.json",
|
||||
"service_account.json", "service-account.json",
|
||||
".npmrc", ".pypirc", ".netrc", ".docker/config.json",
|
||||
}
|
||||
if file_name in sensitive_names:
|
||||
raise ValueError(
|
||||
f"Access to '{resolved_str}' is not allowed: "
|
||||
"this file commonly contains secrets or credentials."
|
||||
)
|
||||
|
||||
allowed_dirs = _get_allowed_file_dirs()
|
||||
if not allowed_dirs:
|
||||
raise ValueError(
|
||||
"No allowed file directories configured. "
|
||||
"Set the ALLOWED_FILE_DIRS environment variable or ensure a home directory exists."
|
||||
)
|
||||
|
||||
for allowed in allowed_dirs:
|
||||
try:
|
||||
resolved.relative_to(allowed)
|
||||
return resolved
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
raise ValueError(
|
||||
f"Access to '{resolved_str}' is not allowed: "
|
||||
f"path is outside permitted directories ({', '.join(str(d) for d in allowed_dirs)}). "
|
||||
"Set ALLOWED_FILE_DIRS to adjust."
|
||||
)
|
||||
|
||||
|
||||
def check_credentials_directory_permissions(credentials_dir: str = None) -> None:
|
||||
"""
|
||||
Check if the service has appropriate permissions to create and write to the .credentials directory.
|
||||
|
||||
Reference in New Issue
Block a user