1. 项目概述:当RPA遇上安全测试自动化

如果你正在用RPA(机器人流程自动化)处理那些重复、枯燥但又至关重要的业务流程,比如自动登录系统、填报数据、下载报表,那你一定遇到过这样的困扰:这些自动化流程本身安全吗?它们会不会成为新的攻击入口?比如,RPA机器人使用的账号密码硬编码在脚本里,或者它访问的API接口缺乏鉴权,又或者它处理的数据在传输过程中是明文的。这些问题,单靠功能测试是发现不了的。传统的安全测试往往由专门的团队手动或半自动进行,与快速迭代的自动化开发流程脱节,导致安全漏洞发现晚、修复成本高。

这就是为什么我们需要将安全测试左移,并实现自动化。这个项目要做的,就是把强大的安全测试工具 aqua-security 无缝集成到你的 Python RPA 项目中,并利用 pytest 这个主流的测试框架来驱动整个安全测试流程自动化。简单来说,就是让你在每次RPA脚本开发或回归测试时,都能自动、快速地对代码、依赖、配置乃至运行时行为进行一次“安全体检”,发现问题立即告警,而不是等到上线后酿成事故。

aqua-security 是一个专注于云原生和应用安全的开源工具集,我们这里主要用到它的 trivy 组件。 trivy 是一款简单易用但功能全面的漏洞扫描器,它能扫描容器镜像、文件系统、Git仓库,以及像 Python 这样的编程语言依赖项(如 requirements.txt Pipfile.lock ),找出其中已知的安全漏洞。而 pytest 则提供了灵活的测试组织、运行和报告能力。将它们结合起来,你就能创建可重复、可集成到CI/CD流水线中的安全测试用例。

本指南面向的是已经有一定 Python 和 RPA 开发基础的工程师或测试人员。你将学会如何搭建环境、编写安全测试用例、解析扫描结果,并最终构建一个可靠的安全测试自动化流程。整个过程,我们力求“说人话,做实事”,提供可以直接“抄作业”的代码和配置。

2. 环境准备与工具链搭建

2.1 核心工具安装与验证

工欲善其事,必先利其器。我们的工具链核心是 Python、pytest 和 aqua-security/trivy。

首先,确保你有一个可用的 Python 环境(3.7及以上)。建议使用虚拟环境来隔离项目依赖,避免包冲突。

# 创建并激活虚拟环境(以 venv 为例)
python -m venv venv_rpa_security
# Windows
venv_rpa_security\Scripts\activate
# Linux/macOS
source venv_rpa_security/bin/activate

接下来,安装 pytest。我们还会安装一些常用的插件,让测试更强大。

pip install pytest pytest-html pytest-xdist
  • pytest-html :用于生成美观的HTML测试报告。
  • pytest-xdist :支持并行运行测试,加快扫描速度。

现在安装最关键的安全扫描工具: trivy 。虽然它有PyPI包( aqua-security-trivia ),但为了获得完整功能和最新漏洞数据库,我强烈建议直接安装其独立二进制文件,这样更稳定,扫描能力也更强。

对于 Linux/macOS:

# 使用官方安装脚本
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# 验证安装
trivy --version

对于 Windows:

  1. 访问 Trivy 的 GitHub Releases 页面: https://github.com/aquasecurity/trivy/releases
  2. 下载最新的 trivy_*_Windows-64bit.zip 文件。
  3. 解压,将里面的 trivy.exe 文件放到一个目录(例如 C:\Tools\ )。
  4. 将该目录添加到系统的 PATH 环境变量中。
  5. 打开新的命令行窗口,运行 trivy --version 验证。

注意 :将 trivy 二进制文件路径加入系统 PATH 是必须的,这样 pytest 才能在任意位置调用它。在 Windows 上,有时需要重启终端或电脑才能使 PATH 更改生效。

验证安装成功后,可以运行一个快速测试,更新漏洞数据库并扫描一个示例:

trivy image --download-db-only # 首次使用建议先更新数据库
trivy fs . # 扫描当前目录文件系统

2.2 项目结构规划

一个清晰的项目结构有助于维护。假设你的 RPA 项目目录如下:

your_rpa_project/
├── rpa_scripts/          # 存放核心RPA业务流程脚本
│   ├── login_automation.py
│   ├── data_extraction.py
│   └── report_generation.py
├── requirements.txt      # Python项目依赖声明文件
├── config/              # 配置文件(可能含敏感信息)
│   └── settings.yaml
├── tests/               # 测试目录
│   ├── functional/      # 功能测试
│   └── security/        # **安全测试专属目录** - 这是我们重点关注的
│       ├── conftest.py  # pytest共享配置
│       ├── test_dependencies.py
│       ├── test_configs.py
│       └── test_images.py (如果有容器化部署)
└── .github/workflows/   # CI/CD流水线配置(可选)
    └── security-scan.yml

我们将把所有的安全测试用例都放在 tests/security/ 目录下。 conftest.py 文件用于存放 pytest 的 fixture,这是实现测试逻辑复用的关键。

2.3 编写第一个安全测试 Fixture

tests/security/conftest.py 中,我们先创建一个用于执行 trivy 命令并解析结果的 fixture。这个 fixture 会被多个测试用例共用。

# tests/security/conftest.py
import subprocess
import json
import pytest
from pathlib import Path

PROJECT_ROOT = Path(__file__).parent.parent.parent

@pytest.fixture(scope="session")
def trivy_scan():
    """
    一个session级别的fixture,用于执行trivy扫描并返回解析后的结果。
    返回一个函数,该函数接受扫描目标(如文件路径)和扫描类型(如‘fs’, ‘config’)作为参数。
    """
    def _scan(target: str, scan_type: str = "fs") -> dict:
        """
        执行trivy扫描。
        :param target: 扫描目标,如文件/目录路径或镜像名。
        :param scan_type: 扫描类型,'fs'(文件系统),'config'(配置),'image'(镜像)等。
        :return: 解析后的JSON结果字典。
        """
        # 构建命令
        cmd = ["trivy", scan_type, "--format", "json", "--quiet", target]
        
        try:
            # 执行命令,捕获输出
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                check=True, # 如果trivy命令执行失败(非零退出码),抛出异常
                cwd=PROJECT_ROOT # 在项目根目录执行,确保路径正确
            )
            # 解析JSON输出
            return json.loads(result.stdout)
        except subprocess.CalledProcessError as e:
            # trivy发现了漏洞,返回码非零,但输出中仍有JSON结果
            # 我们需要捕获这个输出进行解析
            if e.stdout:
                try:
                    return json.loads(e.stdout)
                except json.JSONDecodeError:
                    # 如果输出不是JSON,可能是其他错误
                    raise RuntimeError(f"Trivy scan failed with error: {e.stderr}")
            else:
                raise RuntimeError(f"Trivy command failed: {e.stderr}")
        except FileNotFoundError:
            raise RuntimeError("Trivy command not found. Please ensure trivy is installed and in your PATH.")
    
    return _scan

这个 trivy_scan fixture 是核心。它封装了调用 trivy 命令的细节,并以 JSON 格式返回结构化的扫描结果。 scope="session" 表示这个 fixture 在整个测试会话中只创建一次,提高了测试效率。

3. 核心安全测试用例设计与实现

有了基础框架,我们现在可以针对 RPA 项目的不同风险点,设计具体的安全测试用例了。

3.1 测试用例一:依赖项漏洞扫描

RPA 脚本严重依赖第三方库。一个存在已知高危漏洞的库,可能就是攻击者入侵的跳板。我们将扫描 requirements.txt 文件。

tests/security/test_dependencies.py 中:

# tests/security/test_dependencies.py
import pytest

# 定义漏洞严重级别阈值(可根据项目要求调整)
SEVERITY_THRESHOLD = "HIGH" # 只关注HIGH及以上级别的漏洞

def test_no_critical_vulnerabilities_in_dependencies(trivy_scan):
    """测试项目依赖文件中是否存在超过阈值的漏洞。"""
    target_file = "requirements.txt"
    
    # 执行trivy文件系统扫描,指定目标为依赖文件
    # trivy 会自动识别 requirements.txt 并扫描其中的包
    scan_result = trivy_scan(target_file, scan_type="fs")
    
    # 解析结果,提取漏洞
    vulnerabilities = []
    # Trivy对文件系统的扫描结果结构
    for result in scan_result.get('Results', []):
        if 'Vulnerabilities' in result:
            vulnerabilities.extend(result['Vulnerabilities'])
    
    # 过滤出超过严重级别阈值的漏洞
    high_or_critical_vulns = [
        vuln for vuln in vulnerabilities 
        if vuln.get('Severity') in ['CRITICAL', 'HIGH']
    ]
    
    # 断言:不应该存在CRITICAL或HIGH级别的漏洞
    # 如果存在,测试失败,并打印出详细信息
    error_msg = f"发现 {len(high_or_critical_vulns)} 个高危及以上漏洞:\n"
    for vuln in high_or_critical_vulns:
        error_msg += (f"- [{vuln['Severity']}] {vuln.get('PkgName', 'N/A')}: "
                     f"{vuln.get('VulnerabilityID', 'N/A')} - {vuln.get('Title', 'No title')}\n"
                     f"  固定版本: {vuln.get('FixedVersion', 'N/A')}\n")
    
    assert len(high_or_critical_vulns) == 0, error_msg

# 你可以添加更多细粒度的测试
def test_no_known_exploits(trivy_scan):
    """测试是否存在已知有公开利用代码的漏洞(如果trivy支持此元数据)。"""
    target_file = "requirements.txt"
    scan_result = trivy_scan(target_file, scan_type="fs")
    
    vulnerabilities_with_exploit = []
    for result in scan_result.get('Results', []):
        for vuln in result.get('Vulnerabilities', []):
            # 检查漏洞元数据中是否标记为有公开利用
            # 注意:trivy的JSON输出结构可能随版本变化,这里是一个示例逻辑
            if vuln.get('PrimaryURL', '').find('exploit') != -1:
                vulnerabilities_with_exploit.append(vuln)
    
    assert len(vulnerabilities_with_exploit) == 0, f"发现 {len(vulnerabilities_with_exploit)} 个有公开利用的漏洞"

实操心得 trivy requirements.txt 的扫描,本质上是检查文件中声明的每个包及其版本,在漏洞数据库(如NVD)中是否有匹配的CVE记录。这里的关键是 assert 语句的失败信息 ( error_msg )。我们精心构造了错误信息,当测试失败时,开发者能立刻看到是哪个库、哪个CVE编号、严重程度以及建议的修复版本,极大提升了排查效率。你可以根据团队的安全策略,灵活调整 SEVERITY_THRESHOLD ,比如在开发分支允许 MEDIUM ,但在主分支或发布前必须清零。

3.2 测试用例二:敏感配置检查

RPA 脚本经常需要处理账号、密码、API密钥等敏感信息。硬编码或在配置文件中明文存储是重大安全风险。我们可以用 trivy config 扫描类型来检查配置文件。

tests/security/test_configs.py 中:

# tests/security/test_configs.py
import pytest
import yaml # 需要 pip install PyYAML
from pathlib import Path

PROJECT_ROOT = Path(__file__).parent.parent.parent
CONFIG_DIR = PROJECT_ROOT / "config"

def test_config_files_for_secrets(trivy_scan):
    """扫描配置文件目录,检测是否存在硬编码的密钥、密码等敏感信息。"""
    if not CONFIG_DIR.exists():
        pytest.skip(f"配置目录 {CONFIG_DIR} 不存在,跳过测试。")
    
    # 扫描整个config目录
    scan_result = trivy_scan(str(CONFIG_DIR), scan_type="config")
    
    # trivy config扫描会检查已知的密钥模式、JWT令牌、密码等
    misconfigurations = []
    for result in scan_result.get('Results', []):
        misconfigurations.extend(result.get('Misconfigurations', []))
    
    # 我们只关心高严重性的错误配置
    critical_misconfigs = [m for m in misconfigurations if m.get('Severity') in ['CRITICAL', 'HIGH']]
    
    error_msg = f"在配置文件中发现 {len(critical_misconfigs)} 个高危错误配置或敏感信息泄露:\n"
    for mis in critical_misconfigs:
        error_msg += (f"- [{mis['Severity']}] {mis.get('Title', 'N/A')}\n"
                     f"  文件: {mis.get('CauseMetadata', {}).get('Resource', 'N/A')}\n"
                     f"  描述: {mis.get('Description', 'No description')}\n")
    
    assert len(critical_misconfigs) == 0, error_msg

def test_no_hardcoded_credentials_in_python_scripts():
    """一个简单的正则匹配示例,用于在Python脚本中查找可能的硬编码凭证。"""
    # 这是一个补充检查,因为trivy的config扫描可能不覆盖所有代码文件
    import re
    credential_patterns = [
        r'password\s*=\s*[\'\"][^\'\"]+[\'\"]',
        r'api[_-]?key\s*=\s*[\'\"][^\'\"]+[\'\"]',
        r'secret\s*=\s*[\'\"][^\'\"]+[\'\"]',
        r'token\s*=\s*[\'\"][^\'\"]+[\'\"]',
    ]
    
    scripts_dir = PROJECT_ROOT / "rpa_scripts"
    problematic_files = []
    
    for py_file in scripts_dir.rglob("*.py"):
        with open(py_file, 'r', encoding='utf-8') as f:
            content = f.read()
            for pattern in credential_patterns:
                if re.search(pattern, content, re.IGNORECASE):
                    problematic_files.append((py_file, pattern))
                    break # 一个文件找到一个问题就够
    
    error_msg = f"在Python脚本中发现疑似硬编码凭证:\n"
    for file, pattern in problematic_files:
        error_msg += f"- {file.relative_to(PROJECT_ROOT)} 匹配到模式: {pattern}\n"
    
    assert len(problematic_files) == 0, error_msg + "\n建议:将敏感信息移至环境变量或安全的配置管理服务。"

重要提示 trivy config 扫描非常有用,但它只是一个基于规则的检测。最根本的安全实践是 永远不要 将真实的密钥提交到代码仓库。应该使用环境变量、密钥管理服务(如AWS Secrets Manager, HashiCorp Vault)或至少在CI/CD流水线中注入。这个测试是一个重要的安全网,用于捕获开发过程中的疏忽。

3.3 测试用例三:自定义安全规则与扩展

除了使用 trivy 的内置规则,我们还可以根据项目特定的安全要求,编写自定义的检查。例如,检查 RPA 脚本是否使用了不安全的网络协议(如 http:// ),或者是否引入了被禁止的库。

tests/security/test_custom_rules.py 中:

# tests/security/test_custom_rules.py
import ast
import pytest
from pathlib import Path

PROJECT_ROOT = Path(__file__).parent.parent.parent
SCRIPTS_DIR = PROJECT_ROOT / "rpa_scripts"

class SecurityASTVisitor(ast.NodeVisitor):
    """使用AST(抽象语法树)遍历Python代码,进行自定义安全规则检查。"""
    def __init__(self):
        self.issues = []
    
    def visit_Call(self, node):
        """检查函数调用,例如是否使用了不安全的pickle.loads"""
        if isinstance(node.func, ast.Name):
            if node.func.id == 'pickle' and isinstance(node.func.ctx, ast.Load):
                # 检查是否调用了 pickle.loads
                # 这里需要更精确的检查,简化示例
                self.issues.append({
                    'line': node.lineno,
                    'issue': '使用不安全的pickle反序列化,可能导致代码执行。建议使用json或更安全的序列化库。'
                })
        self.generic_visit(node)
    
    def visit_Assign(self, node):
        """检查变量赋值,例如是否直接使用了eval"""
        if isinstance(node.value, ast.Call):
            if isinstance(node.value.func, ast.Name) and node.value.func.id == 'eval':
                self.issues.append({
                    'line': node.lineno,
                    'issue': '使用eval函数是危险的,可能导致任意代码执行。'
                })
        self.generic_visit(node)

def test_custom_ast_security_checks():
    """使用AST检查Python脚本中的不安全代码模式。"""
    if not SCRIPTS_DIR.exists():
        pytest.skip("RPA脚本目录不存在,跳过测试。")
    
    all_issues = []
    for py_file in SCRIPTS_DIR.rglob("*.py"):
        try:
            with open(py_file, 'r', encoding='utf-8') as f:
                tree = ast.parse(f.read(), filename=str(py_file))
        except SyntaxError:
            continue # 跳过语法错误的文件
        
        visitor = SecurityASTVisitor()
        visitor.visit(tree)
        
        if visitor.issues:
            for issue in visitor.issues:
                issue['file'] = py_file.relative_to(PROJECT_ROOT)
            all_issues.extend(visitor.issues)
    
    error_msg = "发现自定义安全规则违规:\n"
    for issue in all_issues:
        error_msg += f"- 文件: {issue['file']}, 行: {issue['line']}\n  问题: {issue['issue']}\n"
    
    assert len(all_issues) == 0, error_msg

def test_banned_libraries():
    """检查是否引入了项目禁止使用的库。"""
    banned_libs = {
        'paramiko': '已知存在多个历史漏洞,建议使用更现代的SSH库如fabric或asyncssh。',
        # 可以根据需要添加其他库
    }
    
    # 这里可以读取requirements.txt或解析import语句
    # 简化示例:检查requirements.txt内容
    req_file = PROJECT_ROOT / "requirements.txt"
    if not req_file.exists():
        pytest.skip("requirements.txt 不存在,跳过测试。")
    
    found_banned = []
    with open(req_file, 'r') as f:
        for line in f:
            line = line.strip().split('#')[0] # 移除注释
            if not line:
                continue
            # 简单匹配包名(实际中应使用更精确的解析,如packaging库)
            pkg_name = line.split('==')[0].split('>=')[0].split('<=')[0].strip().lower()
            if pkg_name in banned_libs:
                found_banned.append((pkg_name, banned_libs[pkg_name]))
    
    error_msg = "发现禁止使用的库:\n"
    for pkg, reason in found_banned:
        error_msg += f"- {pkg}: {reason}\n"
    
    assert len(found_banned) == 0, error_msg

使用 AST 进行代码分析是一种非常强大的静态应用安全测试(SAST)方法。虽然这里只是简单示例,但你可以扩展 SecurityASTVisitor 类来检查 SQL 注入风险(如字符串拼接的 SQL)、命令注入风险(如使用 os.system 拼接用户输入)等复杂模式。

4. 测试执行、报告与CI/CD集成

4.1 本地执行与报告生成

编写完测试用例后,你可以在项目根目录下运行它们。

运行所有安全测试:

pytest tests/security/ -v

-v 参数显示详细信息。

运行特定测试文件:

pytest tests/security/test_dependencies.py -v

并行运行以加快速度(利用 pytest-xdist):

pytest tests/security/ -n auto # 自动检测CPU核心数并行

生成HTML报告:

pytest tests/security/ --html=security_test_report.html --self-contained-html

这会在当前目录生成一个独立的 security_test_report.html 文件,用浏览器打开即可查看美观的测试结果汇总,包括通过/失败数量、错误详情等。这对于向团队展示安全状态非常有用。

4.2 集成到CI/CD流水线(以GitHub Actions为例)

自动化安全测试的价值在CI/CD流水线中才能最大化体现。每次代码推送或合并请求时,自动运行安全测试,阻止含有已知高危漏洞的代码进入主分支。

.github/workflows/security-scan.yml 中:

name: Security Scan

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  security-test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'

    - name: Install Trivy
      run: |
        sudo apt-get install -y wget apt-transport-https gnupg lsb-release
        wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
        echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
        sudo apt-get update
        sudo apt-get install -y trivy
        trivy --version

    - name: Install Python dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pytest pytest-html pytest-xdist
        # 安装项目运行依赖(如果需要)
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

    - name: Update Trivy vulnerability database
      run: trivy --download-db-only

    - name: Run Security Tests with pytest
      run: |
        pytest tests/security/ -v --html=security-report.html --self-contained-html
      continue-on-error: true # 即使测试失败,也继续执行后续步骤,以便上传报告

    - name: Upload Security Test Report
      uses: actions/upload-artifact@v3
      if: always() # 无论测试成功与否,都上传报告
      with:
        name: security-test-report
        path: security-report.html

这个工作流做了以下几件事:

  1. 检出代码,安装 Python 和 trivy。
  2. 更新 trivy 漏洞数据库(确保扫描结果最新)。
  3. 运行我们编写的所有安全测试用例。
  4. 无论测试通过与否,都将生成的 HTML 报告保存为工件(Artifact),供后续下载查看。

continue-on-error: true if: always() 的配置确保了即使测试失败(即发现了安全漏洞),流水线也会完成并上传报告,让你能清楚看到问题所在。你可以在仓库的 Settings -> Branches -> Branch protection rules 中,为 main 分支设置规则,要求 security-test 这个 job 必须通过才能合并 Pull Request,从而强制实施安全门禁。

4.3 进阶:失败阈值与基线管理

有时,项目中可能暂时存在一些无法立即修复的历史漏洞。为了不阻塞开发,我们可以设置一个“失败阈值”,允许一定数量和级别的漏洞存在,但超过阈值则失败。

我们可以修改 fixture 或测试逻辑来实现。例如,在 conftest.py 中增加一个解析函数,并在测试中设定允许的漏洞基线:

# 在 conftest.py 或测试文件中
def evaluate_vulnerabilities(vulnerabilities, baseline):
    """
    根据基线评估漏洞扫描结果。
    :param vulnerabilities: 漏洞列表
    :param baseline: 字典,例如 {'CRITICAL': 0, 'HIGH': 2, 'MEDIUM': 10}
    :return: (bool, str) 是否通过,错误信息
    """
    from collections import defaultdict
    counts = defaultdict(int)
    for vuln in vulnerabilities:
        sev = vuln.get('Severity')
        if sev in baseline:
            counts[sev] += 1
    
    failed_severities = []
    for sev, allowed in baseline.items():
        if counts[sev] > allowed:
            failed_severities.append(f"{sev}: 发现 {counts[sev]} 个 (允许 {allowed} 个)")
    
    if failed_severities:
        return False, "漏洞数量超过基线:\n" + "\n".join(failed_severities)
    return True, ""

# 在测试用例中使用
def test_dependencies_against_baseline(trivy_scan):
    target_file = "requirements.txt"
    scan_result = trivy_scan(target_file, scan_type="fs")
    
    all_vulns = []
    for result in scan_result.get('Results', []):
        all_vulns.extend(result.get('Vulnerabilities', []))
    
    # 定义基线:允许0个CRITICAL,2个HIGH,10个MEDIUM,LOW不限
    baseline = {'CRITICAL': 0, 'HIGH': 2, 'MEDIUM': 10}
    
    passed, message = evaluate_vulnerabilities(all_vulns, baseline)
    assert passed, message

这种方法为技术债务管理提供了灵活性,但基线应该随着时间推移而收紧,最终目标是将所有已知高危漏洞清零。

5. 常见问题、排查技巧与优化建议

在实际操作中,你肯定会遇到各种问题。下面是我踩过的一些坑和总结的技巧。

5.1 Trivy 扫描常见问题与解决

问题1: trivy 命令未找到或执行失败。

  • 排查 :确保 trivy 已正确安装并加入 PATH。在终端直接输入 trivy --version 测试。在 CI/CD 环境中,安装步骤必须正确无误(如上面 GitHub Actions 示例所示)。
  • 技巧 :在 conftest.py 的 fixture 中,我们通过捕获 FileNotFoundError 给出了明确的错误提示。

问题2:扫描速度慢,特别是第一次。

  • 原因 trivy 需要下载漏洞数据库,第一次运行或长时间未更新时会比较慢。
  • 解决
    • 预下载数据库 :在测试开始前,显式运行 trivy --download-db-only 。在 CI/CD 中,可以将此步骤缓存,避免每次下载。
    • 使用离线模式 :在内网环境,可以搭建一个内部的 trivy 数据库镜像服务器,然后通过 --db-repository 参数指定。
    • 并行扫描 :使用 pytest-xdist 并行运行测试时,注意 trivy 命令本身可能不是线程安全的(如果同时写入同一个缓存目录)。可以为每个测试进程设置独立的缓存目录,或使用 pytest --dist=loadscope 参数来合理分配测试。

问题3:误报或漏洞已修复但扫描结果仍显示。

  • 原因
    1. 漏洞数据库有延迟,新修复的漏洞可能还未同步。
    2. 依赖关系解析有误。例如,你的 requirements.txt 中写的是 requests>=2.25.0 ,而当前安装的是 2.28.0 (已修复漏洞),但 trivy 扫描文件时可能只根据声明的约束条件判断,而不是实际安装的版本。
  • 解决
    • 扫描实际环境 :对于 Python 项目,除了扫描 requirements.txt ,更准确的是扫描虚拟环境目录(如 venv/ )或生成的 Pipfile.lock 。使用 trivy fs /path/to/venv/lib/python3.9/site-packages
    • 使用 --ignore-unfixed 参数 :只报告有修复版本的漏洞,避免那些暂无补丁的漏洞干扰(虽然仍需关注)。
    • 手动忽略 :对于确认的误报或已通过其他方式缓解的漏洞,可以在项目根目录创建 .trivyignore 文件,按 CVE ID 忽略。但需谨慎使用,并定期复审。

5.2 Pytest 集成优化技巧

技巧1:合理使用 Fixture 作用域。 我们的 trivy_scan fixture 是 session 作用域,意味着所有测试共用同一个 fixture 实例。这很好,因为启动 trivy 进程有一定开销。但如果测试会修改扫描目标(例如临时创建有漏洞的文件),则需要使用 function 作用域或确保测试相互独立。

技巧2:动态生成测试用例。 如果你的 RPA 项目有很多独立的脚本或模块,可以为每个都生成一个测试用例,而不是写死。

import pytest
from pathlib import Path

SCRIPTS_DIR = Path("rpa_scripts")

# 收集所有.py文件
python_files = list(SCRIPTS_DIR.rglob("*.py"))

@pytest.mark.parametrize("script_file", python_files, ids=lambda p: p.name)
def test_each_script_for_secrets(script_file, trivy_scan):
    """为每个Python脚本单独运行一次敏感信息扫描。"""
    result = trivy_scan(str(script_file), scan_type="config")
    # ... 断言逻辑 ...

这样,pytest 会为每个 .py 文件生成一个独立的测试项,报告会更清晰。

技巧3:优雅处理测试失败。 安全测试的目标是发现问题,所以测试失败是常态。我们要让失败信息尽可能有用。除了前面构造详细的 error_msg ,还可以利用 pytest pytest.fail() 或自定义异常来提供更丰富的上下文。

5.3 安全测试策略进阶思考

1. 左移与右移结合 :本文主要讲的是“左移”(Shift-Left),即在开发阶段进行静态扫描。完整的RPA安全还应包括“右移”:

  • 动态测试(DAST) :对运行中的RPA机器人或它调用的API进行渗透测试。可以使用 ZAP Burp Suite 等工具,同样可以尝试用 pytest 驱动其API进行自动化扫描。
  • 运行时保护 :监控RPA机器人的异常行为,如异常登录、高频失败、数据泄露等。

2. 秘密管理是重中之重 :再次强调,对于RPA,账号、令牌、密钥是最高风险点。除了扫描,必须建立制度:

  • 使用环境变量或云服务商的密钥管理服务。
  • 在CI/CD中通过安全的方式注入密钥。
  • 定期轮换密钥。

3. 将安全测试作为质量门禁 :在团队中推行“安全即代码”的文化。让安全测试和单元测试、集成测试一样,成为提交代码前的必选项。在Git的 pre-commit 钩子中加入快速的安全检查(如依赖扫描、简单密钥检测),在合并请求时运行完整的测试套件。

4. 定期更新与维护 :安全威胁日新月异。你需要:

  • 定期(如每天)在CI中更新 trivy 的漏洞数据库。
  • 定期(如每季度)复审自定义的安全规则和基线阈值。
  • 关注所用RPA平台(如UiPath, Blue Prism, 影刀RPA)自身的安全公告。

pytest aqua-security/trivy 集成,为Python RPA项目构建自动化的安全测试流程,就像为你的自动化流水线安装了一个“安全防火墙”。它不能保证100%安全,但能极大地降低因已知漏洞、配置错误和不良编码实践带来的风险。从今天开始,选一个现有的RPA项目,尝试加入一个最简单的依赖漏洞扫描测试,你会发现,安全自动化并没有想象中那么复杂,而它带来的安心感,是值得投入的。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐