fix(scripts): prevent command injection in check-quality-gates.py #10635
@@ -0,0 +1,84 @@
|
||||
Feature: Quality gates script prevents command injection attacks
|
||||
|
|
||||
As a security-conscious developer
|
||||
I want the quality gates script to validate all path arguments
|
||||
So that command injection attacks cannot be executed through path manipulation
|
||||
|
||||
Background:
|
||||
Given the quality gates script is available
|
||||
And the project root is set correctly
|
||||
|
||||
Scenario: Valid paths are accepted
|
||||
When I validate the path "src/cleveragents"
|
||||
Then the path validation should succeed
|
||||
And the validated path should be absolute
|
||||
|
||||
Scenario: Empty paths are rejected
|
||||
When I validate the path ""
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "Path cannot be empty"
|
||||
|
||||
Scenario: Nonexistent paths are rejected
|
||||
When I validate the path "src/cleveragents/not_a_real_path_7286"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "Path does not exist"
|
||||
|
||||
Scenario: Paths with path traversal are rejected
|
||||
When I validate the path "src/../../../etc/passwd"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
@tdd_issue @tdd_issue_7286
|
||||
|
HAL9001
commented
BLOCKER — Missing Per the mandatory bug fix workflow in CONTRIBUTING.md, this is a Fix: Add Automated by CleverAgents Bot **BLOCKER — Missing `@tdd_issue_7286` regression tag.**
Per the mandatory bug fix workflow in CONTRIBUTING.md, this is a `Type/Bug` fix. At least one BDD scenario must be tagged `@tdd_issue @tdd_issue_7286` to serve as a regression test proving the bug existed before the fix was applied.
**Fix:** Add `@tdd_issue @tdd_issue_7286` above at least one injection-prevention scenario (e.g., the semicolon, pipe, or path-traversal rejection scenarios):
```gherkin
@tdd_issue @tdd_issue_7286
Scenario: Paths with semicolon are rejected
When I validate the path "src/cleveragents;rm -rf /"
Then the validation should fail
And the path validation error should contain "dangerous pattern"
```
---
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
|
||||
Scenario: Paths with semicolon are rejected
|
||||
When I validate the path "src/cleveragents;rm -rf /"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with pipe are rejected
|
||||
When I validate the path "src/cleveragents|cat /etc/passwd"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with ampersand are rejected
|
||||
When I validate the path "src/cleveragents&whoami"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with dollar sign are rejected
|
||||
When I validate the path "src/cleveragents$(whoami)"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with backticks are rejected
|
||||
When I validate the path "src/cleveragents`whoami`"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with parentheses are rejected
|
||||
When I validate the path "src/cleveragents(whoami)"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with angle brackets are rejected
|
||||
When I validate the path "src/cleveragents<file"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Paths with newlines are rejected
|
||||
When I validate the path "src/cleveragents\nmalicious"
|
||||
Then the path validation should fail
|
||||
And the path validation error should contain "dangerous pattern"
|
||||
|
||||
Scenario: Security check uses validated paths
|
||||
When I run the security check
|
||||
Then the subprocess should not be called with shell=True
|
||||
And all path arguments should be validated
|
||||
|
||||
Scenario: Dead code check uses validated paths
|
||||
When I run the dead code check
|
||||
Then the subprocess should not be called with shell=True
|
||||
And all path arguments should be validated
|
||||
|
||||
Scenario: Complexity check uses validated paths
|
||||
When I run the complexity check
|
||||
Then the subprocess should not be called with shell=True
|
||||
And all path arguments should be validated
|
||||
@@ -0,0 +1,156 @@
|
||||
"""Step definitions for quality gates command injection tests."""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import types
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from behave import given, then, when
|
||||
from behave.runner import Context
|
||||
|
||||
|
HAL9001
commented
BLOCKER — Module-level execution uses a relative path; this is why unit_tests CI is failing.
Additionally, Fix: Automated by CleverAgents Bot **BLOCKER — Module-level execution uses a relative path; this is why unit_tests CI is failing.**
`_qg = _load_quality_gates_module()` runs at Behave collection time. Inside `_load_quality_gates_module()`, `"scripts/check-quality-gates.py"` is resolved relative to the current working directory at collection time. When run from a directory other than the project root, this causes a `FileNotFoundError` that fails every scenario in this file.
Additionally, `spec.loader` can be `None` per typeshed — calling `spec.loader.exec_module(mod)` without a guard will raise `AttributeError` if loader is absent.
**Fix:**
```python
_SCRIPT_PATH = Path(__file__).parent.parent.parent / "scripts" / "check-quality-gates.py"
def _load_quality_gates_module() -> types.ModuleType:
spec = importlib.util.spec_from_file_location("check_quality_gates", _SCRIPT_PATH)
if spec is None or spec.loader is None:
raise ImportError(f"Cannot load module from {_SCRIPT_PATH}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
```
---
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
|
||||
|
||||
_SCRIPT_PATH = (
|
||||
Path(__file__).parent.parent.parent / "scripts" / "check-quality-gates.py"
|
||||
|
HAL9001
commented
⚠️ BLOCKING (UNRESOLVED from review #6826): Module load uses a relative path and runs at import time, causing Two problems in
Required fix: ⚠️ BLOCKING (UNRESOLVED from review #6826): Module load uses a relative path and runs at import time, causing `unit_tests` CI to fail.
Two problems in `_load_quality_gates_module()`:
1. `"scripts/check-quality-gates.py"` is a relative path — when Behave is invoked from any directory other than the project root, this raises `FileNotFoundError` at module load time, failing the entire step file collection.
2. `spec.loader` can be `None` per typeshed. Calling `spec.loader.exec_module(mod)` without a guard will raise `AttributeError`.
Required fix:
```python
import types
_SCRIPT_PATH = Path(__file__).parent.parent.parent / "scripts" / "check-quality-gates.py"
def _load_quality_gates_module() -> types.ModuleType:
spec = importlib.util.spec_from_file_location("check_quality_gates", _SCRIPT_PATH)
if spec is None or spec.loader is None:
raise ImportError(f"Cannot load module from {_SCRIPT_PATH}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
```
|
||||
)
|
||||
|
||||
|
||||
def _load_quality_gates_module() -> types.ModuleType:
|
||||
"""Load the check-quality-gates.py script as a module via importlib."""
|
||||
spec = importlib.util.spec_from_file_location("check_quality_gates", _SCRIPT_PATH)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"Cannot load module from {_SCRIPT_PATH}")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
_qg = _load_quality_gates_module()
|
||||
|
||||
|
||||
@given("the quality gates script is available")
|
||||
def step_script_available(context: Context) -> None:
|
||||
"""Verify the quality gates script is available."""
|
||||
script_path = Path("scripts/check-quality-gates.py")
|
||||
assert script_path.exists(), f"Script {script_path} does not exist"
|
||||
|
||||
|
||||
@given("the project root is set correctly")
|
||||
def step_project_root_set(context: Context) -> None:
|
||||
"""Set up the project root context."""
|
||||
context.original_cwd = os.getcwd()
|
||||
|
||||
|
||||
@when('I validate the path "{path_str}"')
|
||||
def step_validate_path(context: Context, path_str: str) -> None:
|
||||
"""Validate a path string."""
|
||||
context.path_str = path_str
|
||||
context.validation_error = None
|
||||
context.validated_path = None
|
||||
|
||||
try:
|
||||
context.validated_path = _qg.validate_path(path_str)
|
||||
except ValueError as e:
|
||||
context.validation_error = str(e)
|
||||
|
||||
|
||||
@when('I validate the path ""')
|
||||
def step_validate_empty_path(context: Context) -> None:
|
||||
"""Validate an empty path string."""
|
||||
step_validate_path(context, "")
|
||||
|
||||
|
||||
@then("the path validation should succeed")
|
||||
def step_validation_success(context: Context) -> None:
|
||||
"""Assert that validation succeeded."""
|
||||
assert context.validation_error is None, (
|
||||
f"Expected validation to succeed but got: {context.validation_error}"
|
||||
)
|
||||
assert context.validated_path is not None, "Expected a validated path"
|
||||
|
||||
|
||||
@then("the path validation should fail")
|
||||
def step_validation_fail(context: Context) -> None:
|
||||
"""Assert that validation failed."""
|
||||
assert context.validation_error is not None, (
|
||||
"Expected validation to fail but it succeeded"
|
||||
)
|
||||
|
||||
|
||||
@then("the validated path should be absolute")
|
||||
def step_path_is_absolute(context: Context) -> None:
|
||||
"""Assert that the returned path is absolute."""
|
||||
assert context.validated_path is not None, "No validated path available"
|
||||
assert context.validated_path.is_absolute(), (
|
||||
f"Expected absolute path but got: {context.validated_path}"
|
||||
)
|
||||
|
||||
|
||||
@then('the path validation error should contain "{text}"')
|
||||
def step_error_contains_text(context: Context, text: str) -> None:
|
||||
"""Assert that the path validation error message contains specific text."""
|
||||
assert context.validation_error is not None, "No error message available"
|
||||
assert text.lower() in context.validation_error.lower(), (
|
||||
f"Expected error to contain '{text}' but got: {context.validation_error}"
|
||||
)
|
||||
|
||||
|
||||
@when("I run the security check")
|
||||
def step_run_security_check(context: Context) -> None:
|
||||
"""Run the security check function."""
|
||||
mock_result = MagicMock(returncode=0, stdout='{"results": []}', stderr="")
|
||||
with patch.object(_qg.subprocess, "run", return_value=mock_result) as mock_run:
|
||||
context.security_result = _qg.check_security()
|
||||
context.mock_run = mock_run
|
||||
|
||||
|
||||
@when("I run the dead code check")
|
||||
def step_run_dead_code_check(context: Context) -> None:
|
||||
"""Run the dead code check function."""
|
||||
mock_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with patch.object(_qg.subprocess, "run", return_value=mock_result) as mock_run:
|
||||
context.dead_code_result = _qg.check_dead_code()
|
||||
context.mock_run = mock_run
|
||||
|
||||
|
||||
@when("I run the complexity check")
|
||||
def step_run_complexity_check(context: Context) -> None:
|
||||
"""Run the complexity check function."""
|
||||
mock_result = MagicMock(returncode=0, stdout="{}", stderr="")
|
||||
with patch.object(_qg.subprocess, "run", return_value=mock_result) as mock_run:
|
||||
context.complexity_result = _qg.check_complexity()
|
||||
context.mock_run = mock_run
|
||||
|
||||
|
||||
@then("the subprocess should not be called with shell=True")
|
||||
def step_subprocess_shell_not_true(context: Context) -> None:
|
||||
"""Assert subprocess.run does not enable shell execution."""
|
||||
assert hasattr(context, "mock_run"), "No mock_run available"
|
||||
for call in context.mock_run.call_args_list:
|
||||
kwargs = (
|
||||
call.kwargs
|
||||
if hasattr(call, "kwargs")
|
||||
|
HAL9001
commented
BLOCKER — This step is a no-op; it does not verify paths were actually validated.
Fix: Inspect Automated by CleverAgents Bot **BLOCKER — This step is a no-op; it does not verify paths were actually validated.**
`assert context.mock_run.called` only checks that `subprocess.run` was called at all — not that path arguments went through `validate_path()`. An implementation that bypassed validation entirely would still pass this assertion.
**Fix:** Inspect `call_args_list` to verify that path arguments are absolute (i.e., have been processed by `validate_path()`):
```python
for call in context.mock_run.call_args_list:
cmd = call.args[0] if call.args else call.kwargs.get("args", [])
for arg in cmd:
if isinstance(arg, str) and arg.startswith("/"):
assert Path(arg).is_absolute(), f"Path {arg} is not absolute"
```
---
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
|
||||
else (call[1] if len(call) > 1 else {})
|
||||
)
|
||||
assert kwargs.get("shell") is not True, (
|
||||
f"Expected shell not to be True but got: {kwargs.get('shell')}"
|
||||
)
|
||||
|
||||
|
||||
@then("all path arguments should be validated")
|
||||
def step_all_paths_validated(context: Context) -> None:
|
||||
"""Assert that path arguments were resolved before subprocess execution."""
|
||||
assert hasattr(context, "mock_run"), "No mock_run available"
|
||||
assert context.mock_run.called, "subprocess.run was not called"
|
||||
path_like_args = []
|
||||
for call in context.mock_run.call_args_list:
|
||||
cmd = call.args[0] if call.args else call.kwargs.get("args", [])
|
||||
for arg in cmd:
|
||||
if not isinstance(arg, str):
|
||||
continue
|
||||
if "/" in arg or arg.endswith((".py", ".toml")):
|
||||
path_like_args.append(arg)
|
||||
|
||||
assert path_like_args, "No path-like subprocess arguments were found"
|
||||
for arg in path_like_args:
|
||||
assert Path(arg).is_absolute(), f"Path argument was not validated: {arg}"
|
||||
@@ -5,7 +5,7 @@ Aggregates results from multiple quality tools and produces a summary
|
||||
report. Returns non-zero exit code if any critical gate fails.
|
||||
|
||||
Usage:
|
||||
|
HAL9001
commented
BLOCKER —
Fix: Remove Automated by CleverAgents Bot **BLOCKER — `shell=False` is redundant and is causing the lint CI failure.**
`shell=False` is the default when `subprocess.run()` is called with a list argument. Adding it explicitly is flagged by ruff as a redundant keyword, which is the direct cause of the `CI / lint` gate failure.
**Fix:** Remove `shell=False,` from this call (and all other `subprocess.run()` calls in this file). The list-form invocation already prevents shell interpretation.
---
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
|
||||
python scripts/check-quality-gates.py [--coverage-min 85] [--complexity-max 10]
|
||||
python scripts/check-quality-gates.py [--coverage-min 85] [--complexity-max E]
|
||||
|
||||
Gates checked:
|
||||
1. Coverage >= threshold (default 85%)
|
||||
@@ -17,10 +17,80 @@ Gates checked:
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_SAFE_PATH_RE = re.compile(r"^[A-Za-z0-9_./-]+$")
|
||||
|
HAL9001
commented
Suggestion: The Note: With hardcoded strings, the traversal check is also triggered by the Suggestion: The `project_root` parameter on line 25 is never used — none of the callers pass it, making the containment validation dead code. Either remove the parameter or update all callers to pass the project root for defense-in-depth.
Note: With hardcoded strings, the traversal check is also triggered by the `..` pattern string check (line 42) before resolve, making the `relative_to` check unreachable when `project_root` is the only validation path.
HAL9001
commented
⚠️ BLOCKING (UNRESOLVED from review #6826): The signature accepts Fix options:
Until one of these is done, the containment validation is permanently dead code. ⚠️ BLOCKING (UNRESOLVED from review #6826): `project_root` parameter is dead code.
The signature accepts `project_root: Path | None = None` but no caller passes it. The containment check via `.relative_to(project_root_resolved)` is therefore never executed in any production path.
Fix options:
1. Remove the `project_root` parameter entirely (simplest), OR
2. Update all callers (`check_security`, `check_dead_code`, `check_complexity`) to pass `Path(".").resolve()` so the containment check actually runs.
Until one of these is done, the containment validation is permanently dead code.
|
||||
|
||||
|
||||
def validate_path(path_str: str, project_root: Path | None = None) -> Path:
|
||||
"""Validate and sanitize a path argument to prevent command injection.
|
||||
|
||||
Checks for safe path characters, dangerous shell metacharacters, path
|
||||
traversal patterns, project-root containment, and on-disk existence.
|
||||
|
||||
|
HAL9001
commented
BLOCKER — Issue #7286 Acceptance Criterion explicitly states: "Paths must be resolved via Required fix — add after Automated by CleverAgents Bot **BLOCKER — `validate_path()` does not verify path existence.**
Issue #7286 Acceptance Criterion explicitly states: *"Paths must be resolved via `pathlib.Path` and confirmed to exist."* This implementation calls `.resolve()` but never calls `.is_file()` or `.is_dir()`. A path like `vulture_whitelist.py` that does not exist on disk passes all validation checks here and is passed silently to subprocess, which then fails with a cryptic runtime error rather than a clear validation failure.
**Required fix** — add after `resolved = path_obj.resolve()`:
```python
if not resolved.is_file() and not resolved.is_dir():
raise ValueError(f"Path does not exist: {resolved}")
```
---
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
|
||||
Args:
|
||||
path_str: The path string to validate
|
||||
project_root: Optional project root for relative path validation
|
||||
|
||||
Returns:
|
||||
|
HAL9001
commented
BLOCKER — No caller passes Fix options (choose one):
Automated by CleverAgents Bot **BLOCKER — `project_root` parameter is dead code; containment check never runs.**
No caller passes `project_root`. The `resolved.relative_to(project_root_resolved)` containment check in the block below is never executed in any production code path, making the project root containment protection entirely dead code.
**Fix options (choose one):**
1. Remove the `project_root` parameter entirely, OR
2. Update all three callers (`check_security`, `check_dead_code`, `check_complexity`) to pass `Path(".").resolve()` so containment actually runs.
---
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker
|
||||
A validated, resolved Path object
|
||||
|
||||
Raises:
|
||||
ValueError: If the path is invalid or contains dangerous patterns
|
||||
"""
|
||||
if not path_str:
|
||||
raise ValueError("Path cannot be empty")
|
||||
|
||||
# Check for dangerous patterns that could be used for injection
|
||||
dangerous_patterns = [
|
||||
"..", # Path traversal
|
||||
";", # Command separator
|
||||
"|", # Pipe operator
|
||||
|
HAL9001
commented
⚠️ BLOCKING (UNRESOLVED from review #6826):
Fix: Remove ⚠️ BLOCKING (UNRESOLVED from review #6826): `shell=False` is redundant and is causing the `CI / lint` gate to fail.
`shell=False` is the default behaviour of `subprocess.run()` when the command is passed as a list. Ruff flags this as a redundant keyword argument. This pattern appears in all 5 affected `subprocess.run()` calls in this file and is almost certainly the direct cause of the lint CI failure.
Fix: Remove `shell=False,` from all `subprocess.run()` calls. The list-form invocation already prevents shell interpretation — removing the redundant flag does not reduce security.
|
||||
"&", # Background/AND operator
|
||||
|
HAL9001
commented
Suggestion: Suggestion: `shell=False` is the default for subprocess.run() and therefore redundant. Adding it is likely causing the lint failure. Since the subprocess calls already use the list form (which prevents shell interpretation regardless), the security value is minimal. Remove for lint compatibility, or keep as a defensive comment if desired.
|
||||
"$", # Variable/command substitution
|
||||
"`", # Backtick command substitution (deprecated)
|
||||
"(", # Subshell grouping start
|
||||
")", # Subshell grouping end
|
||||
"<", # Input redirection
|
||||
">", # Output redirection
|
||||
"\n", # Newline injection
|
||||
"\r", # Carriage return injection
|
||||
"\\n", # Escaped newline injection in feature/CLI input
|
||||
"\\r", # Escaped carriage return injection in feature/CLI input
|
||||
]
|
||||
for pattern in dangerous_patterns:
|
||||
if pattern in path_str:
|
||||
|
HAL9001
commented
⚠️ BLOCKING: validate_path() does not verify file/directory existence. The issue specification requires "Paths must be resolved via pathlib.Path and confirmed to exist." A path like "vulture_whitelist.py" passes validation even if it does not exist, causing the subprocess to fail at runtime — masking the real error behind a false validation pass. Add after ⚠️ BLOCKING: validate_path() does not verify file/directory existence. The issue specification requires "Paths must be resolved via pathlib.Path and confirmed to exist." A path like "vulture_whitelist.py" passes validation even if it does not exist, causing the subprocess to fail at runtime — masking the real error behind a false validation pass.
Add after `path.resolve()`:
if not path.is_file() and not path.is_dir():
raise ValueError(f"Path does not exist: {path}")
HAL9001
commented
⚠️ BLOCKING (UNRESOLVED from review #6826): Issue #7286 Acceptance Criterion: "Paths must be resolved via Required fix — add after ⚠️ BLOCKING (UNRESOLVED from review #6826): `validate_path()` does not verify path existence.
Issue #7286 Acceptance Criterion: "Paths must be resolved via `pathlib.Path` and confirmed to exist." The `.resolve()` call is present but `.is_file()` / `.is_dir()` is never called. A path like `vulture_whitelist.py` that does not exist on disk passes all validation and is silently forwarded to the subprocess, which then fails with a cryptic runtime error rather than a clear `ValueError`.
Required fix — add after `resolved = path_obj.resolve()`:
```python
if not resolved.is_file() and not resolved.is_dir():
raise ValueError(f"Path does not exist: {resolved}")
```
|
||||
raise ValueError(f"Path contains dangerous pattern: {pattern}")
|
||||
|
||||
if _SAFE_PATH_RE.fullmatch(path_str) is None:
|
||||
raise ValueError("Path contains characters outside the safe allowlist")
|
||||
|
||||
# Convert to Path object
|
||||
path_obj = Path(path_str)
|
||||
|
||||
# Resolve to absolute path to prevent traversal
|
||||
try:
|
||||
resolved = path_obj.resolve()
|
||||
except (OSError, RuntimeError) as e:
|
||||
raise ValueError(f"Cannot resolve path: {e}") from e
|
||||
|
||||
if not resolved.is_file() and not resolved.is_dir():
|
||||
raise ValueError(f"Path does not exist: {resolved}")
|
||||
|
||||
# If project_root is provided, ensure path is within it
|
||||
if project_root is not None:
|
||||
project_root_resolved = project_root.resolve()
|
||||
try:
|
||||
resolved.relative_to(project_root_resolved)
|
||||
except ValueError as e:
|
||||
msg = f"Path {resolved} is outside project root {project_root_resolved}"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
return resolved
|
||||
|
||||
|
||||
def check_coverage(min_coverage: float) -> tuple[bool, str]:
|
||||
"""Check test coverage meets minimum threshold."""
|
||||
@@ -66,13 +136,21 @@ def check_typecheck() -> tuple[bool, str]:
|
||||
|
||||
def check_security() -> tuple[bool, str]:
|
||||
"""Check bandit finds no high-severity issues."""
|
||||
# Validate the path argument to prevent command injection
|
||||
try:
|
||||
project_root = Path(".").resolve()
|
||||
config_path = validate_path("pyproject.toml", project_root)
|
||||
src_path = validate_path("src/cleveragents", project_root)
|
||||
except ValueError as e:
|
||||
return False, f"Security: invalid path - {e}"
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bandit",
|
||||
"-c",
|
||||
"pyproject.toml",
|
||||
str(config_path),
|
||||
"-r",
|
||||
"src/cleveragents",
|
||||
str(src_path),
|
||||
"--severity-level",
|
||||
"high",
|
||||
"--format",
|
||||
@@ -96,15 +174,21 @@ def check_security() -> tuple[bool, str]:
|
||||
|
||||
def check_dead_code() -> tuple[bool, str]:
|
||||
"""Check vulture finds no dead code."""
|
||||
# Validate path arguments to prevent command injection
|
||||
try:
|
||||
project_root = Path(".").resolve()
|
||||
src_path = validate_path("src/cleveragents", project_root)
|
||||
whitelist_path = validate_path("vulture_whitelist.py", project_root)
|
||||
except ValueError as e:
|
||||
return False, f"Dead code: invalid path - {e}"
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"vulture",
|
||||
"src/cleveragents",
|
||||
"vulture_whitelist.py",
|
||||
str(src_path),
|
||||
str(whitelist_path),
|
||||
"--min-confidence",
|
||||
"80",
|
||||
"--exclude",
|
||||
"src/cleveragents/discovery",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -118,11 +202,18 @@ def check_dead_code() -> tuple[bool, str]:
|
||||
|
||||
def check_complexity(max_grade: str = "E") -> tuple[bool, str]:
|
||||
"""Check no functions exceed maximum complexity grade."""
|
||||
# Validate path argument to prevent command injection
|
||||
try:
|
||||
project_root = Path(".").resolve()
|
||||
src_path = validate_path("src/cleveragents", project_root)
|
||||
except ValueError as e:
|
||||
return False, f"Complexity: invalid path - {e}"
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"radon",
|
||||
"cc",
|
||||
"src/cleveragents",
|
||||
str(src_path),
|
||||
"--min",
|
||||
max_grade,
|
||||
"--show-complexity",
|
||||
|
||||
⚠️ BLOCKING (UNRESOLVED from review #6826): Missing mandatory
@tdd_issue_7286regression tag.Per the TDD bug fix workflow in CONTRIBUTING.md, this is a
Type/Bugfix and at least one BDD scenario must be tagged@tdd_issue @tdd_issue_7286to serve as the regression test. The feature file has no tags on any scenario.Add
@tdd_issue @tdd_issue_7286above the most representative injection-prevention scenario, for example: