fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner - P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var - P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup - P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns - P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs) - P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import - P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default - P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed - P2: 28 frontend TODOs (hardcoded constants, deprecated notification API) - P3: dead code, duplicates, deprecated imports, private attr, __import__ inline - P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n) - ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix) - F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String) - Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
This commit is contained in:
@@ -119,6 +119,10 @@ def check_file(filepath: Path) -> list[str]:
|
||||
except ValueError:
|
||||
src_plugin = ""
|
||||
|
||||
# Core files (outside builtins) cannot declare plugin dependencies —
|
||||
# any cross-plugin import from Core is always a violation.
|
||||
is_core_file = src_plugin == ""
|
||||
|
||||
with open(filepath, encoding="utf-8") as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
line_stripped = line.strip()
|
||||
@@ -143,6 +147,30 @@ def check_file(filepath: Path) -> list[str]:
|
||||
if tgt_plugin == src_plugin:
|
||||
continue
|
||||
|
||||
# Core files cannot import plugin internals — always a violation
|
||||
# regardless of any 'Declared dependency' annotation.
|
||||
if is_core_file:
|
||||
violations.append(
|
||||
f"{rel_path}:{line_num}: {line_stripped}\n"
|
||||
f" → Core→Plugin import forbidden: '{tgt_plugin}.{tgt_module}'. "
|
||||
f"Use contracts: get_contract(\"{tgt_plugin}\")"
|
||||
)
|
||||
continue
|
||||
|
||||
# Plugin→Plugin: check if declared in manifest.dependencies
|
||||
if not is_core_file and src_plugin:
|
||||
import re as _re
|
||||
manifest_path = BUILTINS_DIR / src_plugin / "plugin.py"
|
||||
declared_deps: set[str] = set()
|
||||
if manifest_path.exists():
|
||||
with open(manifest_path, encoding="utf-8") as mf:
|
||||
manifest_content = mf.read()
|
||||
dep_match = _re.search(r'dependencies=\[([^\]]*)\]', manifest_content)
|
||||
if dep_match:
|
||||
declared_deps = set(_re.findall(r'"([^"]+)"', dep_match.group(1)))
|
||||
if tgt_plugin in declared_deps:
|
||||
continue # Declared dependency — allowed
|
||||
|
||||
# Skip if it's a contract import via the central registry
|
||||
if CONTRACT_IMPORT_PATTERN.match(line):
|
||||
continue
|
||||
@@ -158,17 +186,38 @@ def check_file(filepath: Path) -> list[str]:
|
||||
|
||||
|
||||
def find_python_files(search_path: Path | None = None) -> list[Path]:
|
||||
"""Find all Python files in the search path."""
|
||||
if search_path is None:
|
||||
search_path = BUILTINS_DIR
|
||||
"""Find all Python files in the search path.
|
||||
|
||||
Default: scans both app/plugins/builtins/ (Plugin→Plugin) and
|
||||
app/core/, app/services/, app/routes/, app/commands/, app/ai/ (Core→Plugin).
|
||||
"""
|
||||
if search_path is not None:
|
||||
files: list[Path] = []
|
||||
for root, dirs, fnames in os.walk(search_path):
|
||||
dirs[:] = [d for d in dirs if d not in EXEMPT_DIRS]
|
||||
for fname in fnames:
|
||||
if fname.endswith(".py"):
|
||||
files.append(Path(root) / fname)
|
||||
return sorted(files)
|
||||
|
||||
# Default: scan both plugin and core directories
|
||||
files: list[Path] = []
|
||||
for root, dirs, fnames in os.walk(search_path):
|
||||
# Skip exempt directories
|
||||
dirs[:] = [d for d in dirs if d not in EXEMPT_DIRS]
|
||||
for fname in fnames:
|
||||
if fname.endswith(".py"):
|
||||
files.append(Path(root) / fname)
|
||||
scan_dirs = [
|
||||
BUILTINS_DIR,
|
||||
PROJECT_ROOT / "app" / "core",
|
||||
PROJECT_ROOT / "app" / "services",
|
||||
PROJECT_ROOT / "app" / "routes",
|
||||
PROJECT_ROOT / "app" / "commands",
|
||||
PROJECT_ROOT / "app" / "ai",
|
||||
]
|
||||
for scan_dir in scan_dirs:
|
||||
if not scan_dir.exists():
|
||||
continue
|
||||
for root, dirs, fnames in os.walk(scan_dir):
|
||||
dirs[:] = [d for d in dirs if d not in EXEMPT_DIRS]
|
||||
for fname in fnames:
|
||||
if fname.endswith(".py"):
|
||||
files.append(Path(root) / fname)
|
||||
return sorted(files)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user