Legacy Review Regression Security Rules¶
These rules codify the first-wave security findings from the PR-review audit. They are intentionally narrow: the goal is to catch repeated, review-derived path-safety mistakes without turning the detector pack into a generic security linter.
Rule Class Coverage¶
This detector pack owns two patterns:
- Unguarded direct
Path(...)construction in API and web code that normally sits behind an allow-root enforcement boundary. - Validation bypasses where a route handler validates request path fields with
resolve_path()and then later passes the raw request object or raw request path fields downstream.
Approved Safe Patterns¶
The detector pack treats the following path-handling shapes as approved:
- Module-local path constants derived from
__file__, such asBASE_DIR = Path(__file__).resolve().parent. - Basename extraction from untrusted names, such as
Path(upload.filename).name.strip(). - Configuration-controlled roots that are paired with an explicit
codeql[py/path-injection]review note. - Wrapping an already-reviewed path-like model field for metadata helpers, such as
file_info_from_path(Path(info.path)). - Returning
Path(...)from the path-validation boundary itself, such as the finalreturn Path(resolved_str)insideresolve_path(). - Service-layer wrappers that explicitly document their string path parameters as pre-validated at the API boundary.
Everything else in guarded API/web contexts should either:
- call
resolve_path()before the value is used, or - be documented as a reviewed exception in the relevant path-safety contract.
Validation Bypass Rule¶
Once a route handler has produced validated aliases such as:
input_path = resolve_path(request.input_dir, settings.allowed_paths)
output_path = resolve_path(request.output_dir, settings.allowed_paths)
downstream calls must use those validated aliases or a copied sanitized request object. Two common safe shapes are:
and:
safe_request = request.model_copy(
update={"input_dir": str(input_path), "output_dir": str(output_path)}
)
background_tasks.add_task(run_job, "job-1", safe_request)
The detector intentionally flags the opposite pattern:
background_tasks.add_task(run_job, "job-1", request)
organizer.organize(input_path=request.input_dir, output_path=request.output_dir)
because the path validation happened, but the validated values were not the ones that reached the downstream sink.