mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-23 04:17:45 +00:00
Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
187 lines
6.3 KiB
Python
187 lines
6.3 KiB
Python
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.component.environment import env_base_dir, sanitize_env_path
|
|
|
|
|
|
def test_none_input_returns_none():
|
|
"""Test that None input returns None."""
|
|
assert sanitize_env_path(None) is None
|
|
|
|
|
|
def test_empty_string_returns_none():
|
|
"""Test that empty string returns None."""
|
|
assert sanitize_env_path("") is None
|
|
|
|
|
|
def test_valid_relative_path():
|
|
"""Test that valid relative path within base dir is accepted."""
|
|
result = sanitize_env_path("project1.env")
|
|
assert result is not None
|
|
assert result.startswith(env_base_dir)
|
|
assert result.endswith("project1.env")
|
|
|
|
|
|
def test_valid_absolute_path_within_base_dir():
|
|
"""Test that absolute path within base directory is accepted."""
|
|
valid_path = os.path.join(env_base_dir, "valid.env")
|
|
result = sanitize_env_path(valid_path)
|
|
assert result == os.path.abspath(valid_path)
|
|
|
|
|
|
def test_path_traversal_attack_rejected():
|
|
"""Test that path traversal attempts are rejected."""
|
|
malicious_paths = [
|
|
"../../../etc/passwd",
|
|
"../../.ssh/id_rsa.env",
|
|
"../outside.env",
|
|
"subdir/../../outside.env",
|
|
]
|
|
for path in malicious_paths:
|
|
result = sanitize_env_path(path)
|
|
# Path traversal should either be rejected
|
|
# or normalized within base_dir
|
|
if result:
|
|
assert result.startswith(env_base_dir), (
|
|
f"Path traversal not blocked: {path} -> {result}"
|
|
)
|
|
|
|
|
|
def test_absolute_path_outside_base_dir_rejected():
|
|
"""Test that absolute paths outside base directory are rejected."""
|
|
malicious_paths = [
|
|
"/etc/passwd",
|
|
"/tmp/evil.env",
|
|
"/root/.env",
|
|
str(Path.home() / "evil.env"),
|
|
]
|
|
for path in malicious_paths:
|
|
result = sanitize_env_path(path)
|
|
assert result is None, (
|
|
f"Absolute path outside base dir not rejected: {path}"
|
|
)
|
|
|
|
|
|
def test_non_env_extension_rejected():
|
|
"""Test that files without .env extension are rejected."""
|
|
invalid_paths = [
|
|
"config.txt",
|
|
"settings.json",
|
|
"environment",
|
|
".bashrc",
|
|
"script.py",
|
|
]
|
|
for path in invalid_paths:
|
|
result = sanitize_env_path(path)
|
|
assert result is None, f"Non-.env file not rejected: {path}"
|
|
|
|
|
|
def test_nested_valid_path():
|
|
"""Test that valid nested paths within base dir are accepted."""
|
|
result = sanitize_env_path("projects/project1/config.env")
|
|
assert result is not None
|
|
assert result.startswith(env_base_dir)
|
|
assert result.endswith("config.env")
|
|
|
|
|
|
def test_symlink_escape_attempt():
|
|
"""Test that symlinks cannot be used to escape base directory."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
# Create a symlink pointing outside base_dir
|
|
link_path = os.path.join(env_base_dir, "evil_link.env")
|
|
target_path = os.path.join(tmpdir, "outside.env")
|
|
|
|
# This test only makes sense if we can create symlinks
|
|
try:
|
|
if os.path.exists(link_path):
|
|
os.unlink(link_path)
|
|
os.symlink(target_path, link_path)
|
|
|
|
# The sanitized path should resolve the symlink
|
|
result = sanitize_env_path("evil_link.env")
|
|
|
|
# Either rejected or stays within base_dir after resolution
|
|
if result:
|
|
resolved = Path(result).resolve()
|
|
base_resolved = Path(env_base_dir).resolve()
|
|
# Check if resolved path is under base directory
|
|
try:
|
|
resolved.relative_to(base_resolved)
|
|
# If this succeeds, symlink stayed in base (acceptable)
|
|
except ValueError:
|
|
# Symlink escaped - should have been rejected
|
|
pytest.fail(f"Symlink escape not prevented: {result}")
|
|
except (OSError, NotImplementedError):
|
|
# Symlinks not supported on this system, skip test
|
|
pytest.skip("Symlinks not supported")
|
|
finally:
|
|
# Cleanup
|
|
if os.path.exists(link_path):
|
|
os.unlink(link_path)
|
|
|
|
|
|
def test_dot_env_in_filename():
|
|
"""Test that .env can appear in the middle of filename."""
|
|
result = sanitize_env_path("project.env.backup.env")
|
|
assert result is not None
|
|
assert result.endswith(".env")
|
|
|
|
|
|
def test_case_sensitivity():
|
|
"""Test handling of different case extensions."""
|
|
# Only .env (lowercase) should be accepted
|
|
assert sanitize_env_path("config.ENV") is None
|
|
assert sanitize_env_path("config.Env") is None
|
|
assert sanitize_env_path("config.env") is not None
|
|
|
|
|
|
def test_special_characters_in_path():
|
|
"""Test handling of special characters in valid paths."""
|
|
# These should be accepted if they end with .env and stay in base_dir
|
|
valid_special_chars = [
|
|
"my-project.env",
|
|
"project_name.env",
|
|
"project.2024.env",
|
|
]
|
|
for path in valid_special_chars:
|
|
result = sanitize_env_path(path)
|
|
assert result is not None, (
|
|
f"Valid path with special chars rejected: {path}"
|
|
)
|
|
assert result.startswith(env_base_dir)
|
|
|
|
|
|
def test_whitespace_handling():
|
|
"""Test handling of whitespace in paths."""
|
|
# Whitespace should be preserved in valid paths
|
|
result = sanitize_env_path("my project.env")
|
|
assert result is not None
|
|
assert "my project.env" in result
|
|
|
|
|
|
def test_current_directory_traversal():
|
|
"""Test that ./ and current directory references are handled."""
|
|
result = sanitize_env_path("./project.env")
|
|
assert result is not None
|
|
assert result.startswith(env_base_dir)
|
|
|
|
result = sanitize_env_path("././project.env")
|
|
assert result is not None
|
|
assert result.startswith(env_base_dir)
|