chore: import upstream snapshot with attribution
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
+223
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pre-commit hook to prevent absolute module paths in the codebase.
|
||||
|
||||
Module path configuration values MUST use relative imports (starting with ".")
|
||||
instead of absolute paths (starting with "local_deep_research.").
|
||||
The module whitelist security boundary requires relative paths for search engines,
|
||||
and absolute paths elsewhere are a packaging anti-pattern.
|
||||
|
||||
See: src/local_deep_research/security/module_whitelist.py
|
||||
"""
|
||||
|
||||
import ast
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# The absolute base that must NOT appear as a module path prefix
|
||||
ABSOLUTE_BASE = "local_deep_research"
|
||||
|
||||
# Known legitimate uses of absolute package references that are NOT module_path
|
||||
# config values. These are used for importlib.resources, package= kwargs, and
|
||||
# normalization constants where the full package name is required by Python APIs.
|
||||
LEGITIMATE_ABSOLUTE_REFS = frozenset(
|
||||
{
|
||||
# importlib.resources.files() requires the full package name
|
||||
"local_deep_research.defaults.settings",
|
||||
# importlib.import_module() to avoid circular imports
|
||||
"local_deep_research.utilities",
|
||||
# package= kwarg for relative import resolution
|
||||
"local_deep_research.llm.providers",
|
||||
# package= kwarg for relative import resolution in module_whitelist.py
|
||||
"local_deep_research.web_search_engines",
|
||||
}
|
||||
)
|
||||
|
||||
# Directory patterns where absolute paths are allowed (matched as path components)
|
||||
ALLOWED_DIR_PATTERNS = {
|
||||
"tests", # Tests may verify rejection of absolute paths
|
||||
".pre-commit-hooks", # Hook files themselves
|
||||
}
|
||||
|
||||
# Specific file suffixes where absolute paths are allowed
|
||||
ALLOWED_FILE_SUFFIXES = {
|
||||
# Use os.sep-independent matching via PurePosixPath
|
||||
"security/module_whitelist.py", # Defines the security boundary itself
|
||||
}
|
||||
|
||||
# Basename patterns for test files
|
||||
ALLOWED_BASENAME_PREFIXES = {"test_"} # test_*.py
|
||||
ALLOWED_BASENAME_SUFFIXES = {"_test.py"} # *_test.py
|
||||
|
||||
|
||||
class AbsoluteModulePathChecker(ast.NodeVisitor):
|
||||
"""AST visitor to detect absolute module paths in string literals."""
|
||||
|
||||
def __init__(self, filename: str):
|
||||
self.filename = filename
|
||||
self.errors = []
|
||||
|
||||
def visit_Constant(self, node):
|
||||
"""Check string constants for absolute module paths."""
|
||||
if (
|
||||
isinstance(node.value, str)
|
||||
and node.value.startswith(ABSOLUTE_BASE + ".")
|
||||
and node.value not in LEGITIMATE_ABSOLUTE_REFS
|
||||
):
|
||||
self.errors.append(
|
||||
(
|
||||
node.lineno,
|
||||
f'Absolute module path "{node.value}" found. '
|
||||
f"Use a relative path (starting with '.') instead.",
|
||||
)
|
||||
)
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def _is_file_allowed(filename: str) -> bool:
|
||||
"""Check if this file is allowed to contain absolute module paths."""
|
||||
p = Path(filename)
|
||||
normalized = p.as_posix()
|
||||
parts = p.parts
|
||||
|
||||
# Check directory patterns as path components
|
||||
for pattern in ALLOWED_DIR_PATTERNS:
|
||||
if pattern in parts:
|
||||
return True
|
||||
|
||||
# Check specific file suffixes
|
||||
for suffix in ALLOWED_FILE_SUFFIXES:
|
||||
if normalized.endswith(suffix):
|
||||
return True
|
||||
|
||||
# Check basename patterns
|
||||
basename = p.name
|
||||
for prefix in ALLOWED_BASENAME_PREFIXES:
|
||||
if basename.startswith(prefix):
|
||||
return True
|
||||
for suffix in ALLOWED_BASENAME_SUFFIXES:
|
||||
if basename.endswith(suffix):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_python_file(filename: str) -> bool:
|
||||
"""Check a Python file for absolute module paths in string literals."""
|
||||
if _is_file_allowed(filename):
|
||||
return True
|
||||
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
print(f"Error reading {filename}: {e}")
|
||||
return False
|
||||
|
||||
try:
|
||||
tree = ast.parse(content, filename=filename)
|
||||
checker = AbsoluteModulePathChecker(filename)
|
||||
checker.visit(tree)
|
||||
|
||||
if checker.errors:
|
||||
print(f"\n{filename}:")
|
||||
for line_num, error in checker.errors:
|
||||
print(f" Line {line_num}: {error}")
|
||||
return False
|
||||
|
||||
except SyntaxError:
|
||||
# Skip files with syntax errors (let other tools handle that)
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Error parsing {filename}: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_json_file(filename: str) -> bool:
|
||||
"""Check a JSON config file for absolute module paths in module_path keys."""
|
||||
if _is_file_allowed(filename):
|
||||
return True
|
||||
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error reading {filename}: {e}")
|
||||
return False
|
||||
|
||||
errors = []
|
||||
|
||||
def _check_value(obj, path=""):
|
||||
if isinstance(obj, dict):
|
||||
for key, value in obj.items():
|
||||
current_path = f"{path}.{key}" if path else key
|
||||
# Only check values under module_path keys
|
||||
if key in ("module_path", "full_search_module") and isinstance(
|
||||
value, str
|
||||
):
|
||||
if (
|
||||
value.startswith(ABSOLUTE_BASE + ".")
|
||||
and value not in LEGITIMATE_ABSOLUTE_REFS
|
||||
):
|
||||
errors.append((current_path, value))
|
||||
elif isinstance(value, (dict, list)):
|
||||
_check_value(value, current_path)
|
||||
elif isinstance(obj, list):
|
||||
for i, item in enumerate(obj):
|
||||
_check_value(item, f"{path}[{i}]")
|
||||
|
||||
_check_value(data)
|
||||
|
||||
if errors:
|
||||
print(f"\n{filename}:")
|
||||
for key_path, value in errors:
|
||||
print(
|
||||
f' Key "{key_path}": Absolute module path "{value}" found. '
|
||||
f"Use a relative path (starting with '.') instead."
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Check all provided files for absolute module paths."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: check-absolute-module-paths.py <file1> <file2> ...")
|
||||
sys.exit(1)
|
||||
|
||||
files_to_check = sys.argv[1:]
|
||||
has_errors = False
|
||||
|
||||
for filename in files_to_check:
|
||||
if filename.endswith(".py"):
|
||||
if not check_python_file(filename):
|
||||
has_errors = True
|
||||
elif filename.endswith(".json"):
|
||||
if not check_json_file(filename):
|
||||
has_errors = True
|
||||
|
||||
if has_errors:
|
||||
print("\n" + "=" * 70)
|
||||
print("Module Path Security: Absolute module paths detected!")
|
||||
print("=" * 70)
|
||||
print("\nModule paths must use relative imports (starting with '.')")
|
||||
print("instead of absolute paths starting with 'local_deep_research.'.")
|
||||
print("\nExamples:")
|
||||
print(
|
||||
' BAD: "local_deep_research.web_search_engines'
|
||||
'.engines.search_engine_local"'
|
||||
)
|
||||
print(' GOOD: ".engines.search_engine_local"')
|
||||
print("\nSee: src/local_deep_research/security/module_whitelist.py")
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user