Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
146 lines
4.1 KiB
Python
146 lines
4.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Shared fixtures for tool_guard tests."""
|
|
# pylint: disable=redefined-outer-name,unused-argument,protected-access
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from qwenpaw.security.tool_guard.models import (
|
|
GuardFinding,
|
|
GuardSeverity,
|
|
GuardThreatCategory,
|
|
ToolGuardResult,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Config / env fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config():
|
|
"""Patch ``qwenpaw.config.load_config`` to return a mock
|
|
with tool_guard config."""
|
|
tool_guard_cfg = MagicMock()
|
|
tool_guard_cfg.guarded_tools = None
|
|
tool_guard_cfg.denied_tools = None
|
|
tool_guard_cfg.auto_denied_rules = None
|
|
|
|
security_cfg = MagicMock()
|
|
security_cfg.tool_guard = tool_guard_cfg
|
|
|
|
app_cfg = MagicMock()
|
|
app_cfg.security = security_cfg
|
|
|
|
with patch("qwenpaw.config.load_config", return_value=app_cfg) as patched:
|
|
yield patched
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_env_loader():
|
|
"""Patch ``EnvVarLoader.get_str`` so no real env vars are read."""
|
|
with patch(
|
|
"qwenpaw.constant.EnvVarLoader.get_str",
|
|
return_value="",
|
|
) as patched:
|
|
yield patched
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_workspace(tmp_path: Path):
|
|
"""Provide a tmp_path workspace and patch ``get_current_workspace_dir``."""
|
|
workspace = tmp_path / "workspace"
|
|
workspace.mkdir()
|
|
with patch(
|
|
"qwenpaw.config.context.get_current_workspace_dir",
|
|
return_value=workspace,
|
|
):
|
|
yield workspace
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Model instance fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture
|
|
def guard_findings():
|
|
"""Create sample GuardFinding objects for reuse."""
|
|
critical_finding = GuardFinding(
|
|
id="f-001",
|
|
rule_id="CMD-001",
|
|
category=GuardThreatCategory.COMMAND_INJECTION,
|
|
severity=GuardSeverity.CRITICAL,
|
|
title="Dangerous shell command",
|
|
description="Command contains destructive pattern 'rm -rf /'",
|
|
tool_name="execute_shell_command",
|
|
param_name="command",
|
|
matched_value="rm -rf /",
|
|
matched_pattern=r"rm\s+-rf\s+/",
|
|
snippet="rm -rf /",
|
|
remediation="Reject or sandbox destructive commands",
|
|
guardian="rule_guardian",
|
|
)
|
|
|
|
high_finding = GuardFinding(
|
|
id="f-002",
|
|
rule_id="PATH-001",
|
|
category=GuardThreatCategory.PATH_TRAVERSAL,
|
|
severity=GuardSeverity.HIGH,
|
|
title="Path traversal attempt",
|
|
description="File path escapes workspace boundary",
|
|
tool_name="read_file",
|
|
param_name="path",
|
|
matched_value="../../etc/passwd",
|
|
matched_pattern=r"\.\.[\\/]",
|
|
snippet="../../etc/passwd",
|
|
remediation="Validate paths stay within workspace",
|
|
guardian="file_guardian",
|
|
)
|
|
|
|
medium_finding = GuardFinding(
|
|
id="f-003",
|
|
rule_id="NET-001",
|
|
category=GuardThreatCategory.NETWORK_ABUSE,
|
|
severity=GuardSeverity.MEDIUM,
|
|
title="Suspicious network access",
|
|
description="Tool attempts outbound connection",
|
|
tool_name="execute_shell_command",
|
|
param_name="command",
|
|
matched_value="curl http://evil.com",
|
|
remediation=None,
|
|
guardian="rule_guardian",
|
|
)
|
|
|
|
return {
|
|
"critical": critical_finding,
|
|
"high": high_finding,
|
|
"medium": medium_finding,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def safe_result():
|
|
"""Return a ToolGuardResult with no findings."""
|
|
return ToolGuardResult(
|
|
tool_name="read_file",
|
|
params={"path": "/tmp/safe.txt"},
|
|
findings=[],
|
|
guard_duration_seconds=0.01,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def unsafe_result(guard_findings):
|
|
"""Return a ToolGuardResult with a CRITICAL finding."""
|
|
return ToolGuardResult(
|
|
tool_name="execute_shell_command",
|
|
params={"command": "rm -rf /"},
|
|
findings=[guard_findings["critical"]],
|
|
guard_duration_seconds=0.05,
|
|
)
|