A pytest plugin for signing and publishing JUnit XML test reports to the Jux REST API
pytest-jux is a client-side pytest plugin that automatically signs JUnit XML test reports using XML digital signatures (XMLDSig) and publishes them to a Jux REST API backend for storage and analysis. It enables system administrators, integrators, and infrastructure engineers to maintain a chain-of-trust for test results across local and distributed environments.
pytest-jux is the client component in a client-server architecture:
- pytest-jux (this project): Client-side plugin for signing and publishing test reports
- Jux API Server (separate project): Server-side backend for storing, querying, and visualizing reports
This separation allows pytest-jux to be lightweight and focused on test report signing, while the Jux API Server handles data persistence, deduplication, and web interfaces.
- Automatic Report Signing: Signs JUnit XML reports with XML digital signatures after test execution
- XML Canonicalization: Uses C14N for generating canonical hashes
- Chain-of-Trust: Cryptographic signatures ensure report integrity and provenance
- REST API Publishing: Publishes signed reports to Jux API backend
- Local Storage & Caching: XDG-compliant storage with offline queue for unreliable networks
- Flexible Storage Modes: LOCAL (filesystem only), API (server only), BOTH (dual), CACHE (offline queue)
- Configuration Management: Multi-source configuration (CLI, environment, files) with precedence
- pytest Integration: Seamless integration via pytest hooks (post-session processing)
- Standalone CLI Tools: Key generation, signing, verification, inspection, cache, and config utilities
- Environment Metadata: Captures test environment context (hostname, user, platform)
- Custom Metadata Support: Add custom metadata to reports via pytest-metadata integration
- Security Framework: Comprehensive security with automated scanning and threat modeling
The following features are provided by the Jux API Server (separate project):
- Report Storage: Persistent storage in SQLite (local) or PostgreSQL (distributed)
- Duplicate Detection: Canonical hash-based deduplication prevents redundant storage
- Signature Verification: Server-side validation of XMLDSig signatures
- Query API: REST API for retrieving and filtering stored reports
- Web UI: Browser-based interface for visualizing test results
- Multi-tenancy: Support for multiple projects and users
pytest-jux implements a comprehensive security framework with SLSA Build Level 2 compliance:
All pytest-jux releases include cryptographic provenance attestations:
- ✅ Build Integrity: Packages built on GitHub Actions (not developer workstations)
- ✅ Source Traceability: Cryptographic link to exact source code commit
- ✅ Tamper Detection: Any modification invalidates the signature
- ✅ Independent Verification: Verify packages with
slsa-verifier
Verify a release:
# Install SLSA verifier
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
# Download and verify
pip download pytest-jux==0.4.1 --no-deps
curl -L -O https://github.com/jux-tools/pytest-jux/releases/download/v0.4.1/pytest-jux-0.4.1.intoto.jsonl
slsa-verifier verify-artifact \
--provenance-path pytest-jux-0.4.1.intoto.jsonl \
--source-uri github.com/jux-tools/pytest-jux \
pytest_jux-0.4.1-py3-none-any.whlSee SLSA Verification Guide for complete instructions.
- Automated Scanning: pip-audit, Ruff (security rules), Safety, Trivy
- Threat Modeling: STRIDE methodology with 19 identified threats
- Cryptographic Standards: NIST-compliant algorithms (RSA-SHA256, ECDSA-SHA256)
- Supply Chain: SLSA L2, Dependabot, OpenSSF Scorecard
- Vulnerability Reporting: Coordinated disclosure with 48-hour response time
See Security Policy for vulnerability reporting and Security Framework for complete details.
This project uses Architecture Decision Records (ADRs) to track significant architectural decisions:
- ADR-0001: Record architecture decisions
- ADR-0002: Adopt development best practices
- ADR-0003: Use Python 3 with pytest, lxml, signxml, and SQLAlchemy stack
- ADR-0004: Adopt Apache License 2.0
- ADR-0005: Adopt Python Ecosystem Security Framework
- ADR-0006: Adopt SLSA Build Level 2 Compliance
See the docs/adr/ directory for complete decision history.
- Python 3.11+
- pytest 7.4+ or 8.x
- Supported OS: Debian 12/13, latest openSUSE, latest Fedora
# From PyPI (when published)
uv pip install pytest-jux
# From source (development)
cd pytest-jux
uv pip install -e ".[dev,security]"# Run tests with JUnit XML generation and auto-publish
pytest --junit-xml=report.xml \
--jux-publish \
--jux-api-url=https://jux.example.com/api/v1 \
--jux-key=~/.jux/private_key.pem[pytest]
addopts = --junit-xml=report.xml
jux_api_url = https://jux.example.com/api/v1
jux_key_path = ~/.jux/private_key.pem--jux-publish: Enable pytest-jux plugin (default: disabled)--jux-api-url URL: Jux REST API endpoint--jux-key PATH: Path to private key for signing (PEM format)--jux-storage-mode MODE: Storage mode (local, api, both, cache)--jux-storage-path PATH: Custom storage directory path
pytest-jux integrates with pytest-metadata to add custom metadata to your test reports:
# Add metadata via command line
pytest --junit-xml=report.xml \
--jux-sign \
--jux-key ~/.jux/signing_key.pem \
--metadata ci_build_id 12345 \
--metadata environment productionThe metadata appears as property tags in the JUnit XML:
<testsuite>
<properties>
<property name="ci_build_id" value="12345"/>
<property name="environment" value="production"/>
</properties>
...
</testsuite>See How to Add Metadata to Reports for complete documentation including CI/CD integration, JSON metadata, and programmatic usage.
pytest-jux provides flexible storage options for test reports, supporting both local filesystem storage and API publishing. This enables offline operation, network-resilient workflows, and local inspection of test results.
pytest-jux supports four storage modes:
- LOCAL: Store reports locally only (no API publishing)
- API: Publish to API only (no local storage)
- BOTH: Store locally AND publish to API
- CACHE: Store locally, publish when API available (offline queue)
Reports are stored in XDG-compliant directories:
- macOS:
~/Library/Application Support/jux/reports/ - Linux:
~/.local/share/jux/reports/ - Windows:
%LOCALAPPDATA%\jux\reports\
Custom storage paths can be specified via --jux-storage-path or JUX_STORAGE_PATH environment variable.
Local storage only (no API required):
pytest --junit-xml=report.xml \
--jux-enabled \
--jux-sign \
--jux-key=~/.jux/private_key.pem \
--jux-storage-mode=localAPI publishing with local backup:
pytest --junit-xml=report.xml \
--jux-enabled \
--jux-sign \
--jux-key=~/.jux/private_key.pem \
--jux-api-url=https://jux.example.com/api/v1 \
--jux-storage-mode=bothOffline queue mode (auto-publish when API available):
pytest --junit-xml=report.xml \
--jux-enabled \
--jux-sign \
--jux-key=~/.jux/private_key.pem \
--jux-api-url=https://jux.example.com/api/v1 \
--jux-storage-mode=cacheThe jux-cache command provides tools for inspecting and managing cached reports.
List cached reports:
# Text output
jux-cache list
# JSON output
jux-cache list --jsonShow report details:
# View specific report
jux-cache show sha256:abc123...
# JSON output
jux-cache show sha256:abc123... --jsonView cache statistics:
# Text output
jux-cache stats
# JSON output
jux-cache stats --jsonClean old reports:
# Dry run (preview what would be deleted)
jux-cache clean --days 30 --dry-run
# Actually delete reports older than 30 days
jux-cache clean --days 30Custom storage path:
jux-cache --storage-path /custom/path listpytest-jux supports multi-source configuration with precedence: CLI arguments > environment variables > configuration files > defaults.
Configuration can be loaded from:
- Command-line arguments (highest precedence)
- Environment variables (prefixed with
JUX_) - Configuration files:
~/.jux/config(user-level).jux.conf(project-level)pytest.ini(pytest configuration)/etc/jux/config(system-level, Linux/Unix)
Configuration files use INI format with a [jux] section:
[jux]
# Core settings
enabled = true
sign = true
publish = false
# Storage settings
storage_mode = local
# storage_path = ~/.local/share/jux/reports
# Signing settings
# key_path = ~/.jux/signing_key.pem
# cert_path = ~/.jux/signing_key.crt
# API settings
# api_url = https://jux.example.com/api/v1
# api_key = your-api-key-hereThe jux-config command provides tools for managing configuration.
List all configuration options:
# Text output
jux-config list
# JSON output
jux-config list --jsonDump current effective configuration:
# Text output (shows sources)
jux-config dump
# JSON output
jux-config dump --jsonView configuration files:
# View specific file
jux-config view ~/.jux/config
# View all configuration files
jux-config view --allInitialize configuration file:
# Create minimal config at default path (~/.jux/config)
jux-config init
# Create full config with all options
jux-config init --template full
# Create at custom path
jux-config init --path /custom/path/config
# Force overwrite existing file
jux-config init --forceValidate configuration:
# Basic validation
jux-config validate
# Strict validation (check dependencies)
jux-config validate --strict
# JSON output
jux-config validate --jsonAll configuration options can be set via environment variables:
export JUX_ENABLED=true
export JUX_SIGN=true
export JUX_KEY_PATH=~/.jux/private_key.pem
export JUX_STORAGE_MODE=local
export JUX_API_URL=https://jux.example.com/api/v1
export JUX_API_KEY=your-api-key-hereMinimal configuration (local storage only):
[jux]
enabled = true
sign = false
storage_mode = localDevelopment configuration (local storage + API publishing):
[jux]
enabled = true
sign = true
key_path = ~/.jux/dev_key.pem
storage_mode = both
api_url = http://localhost:4000/api/v1Production configuration (signed reports with offline queue):
[jux]
enabled = true
sign = true
key_path = ~/.jux/prod_key.pem
cert_path = ~/.jux/prod_key.crt
storage_mode = cache
api_url = https://jux.example.com/api/v1pytest-jux provides standalone CLI commands for key management, signing, verification, and administration:
Key generation:
jux-keygen --output ~/.jux/private_key.pem --algorithm rsaOffline signing:
jux-sign report.xml --key ~/.jux/private_key.pem --output signed_report.xmlSignature verification:
jux-verify signed_report.xmlReport inspection:
jux-inspect report.xmlCache management:
jux-cache list
jux-cache show sha256:abc123...
jux-cache stats
jux-cache clean --days 30Configuration management:
jux-config list
jux-config dump
jux-config init
jux-config validateManual publishing:
# Publish single report to Jux API
jux-publish --file report.xml --api-url https://jux.example.com/api/v1
# Publish all queued reports
jux-publish --queue --api-url https://jux.example.com/api/v1
# Dry-run mode (preview without publishing)
jux-publish --queue --api-url https://jux.example.com/api/v1 --dry-run
# JSON output for scripting
jux-publish --queue --api-url https://jux.example.com/api/v1 --jsonSee CLI tool documentation for complete usage details.
Complete Documentation Index: See docs/INDEX.md for a complete map of all available documentation.
This project follows the Diátaxis framework, organizing documentation into four categories:
Step-by-step guides to learn pytest-jux from beginner to advanced:
- Quick Start - Get started in 5 minutes
- Setting Up Signing Keys - Generate and configure cryptographic keys (10 minutes)
- First Signed Report - Complete beginner walkthrough with tamper detection (15-20 min)
- Integration Testing - Multi-environment setup and CI/CD integration (30-45 min)
- Custom Signing Workflows - Programmatic API usage and batch processing (30-40 min)
Practical solutions to specific problems:
Key Management:
Storage & Configuration:
Integration:
- CI/CD Deployment - GitHub Actions, GitLab CI, Jenkins
- Integrate with pytest Plugins
- Add Metadata to Reports
Troubleshooting:
- Troubleshooting Guide - Diagnose and fix common issues
Complete technical reference documentation:
API Reference:
- API Index - Overview of all modules
- Canonicalizer API - XML canonicalization and hashing
- Signer API - XMLDSig signature generation
- Verifier API - Signature verification
- Storage API - Report caching and storage
- Config API - Configuration management
- Metadata API - Environment metadata collection
- Plugin API - pytest plugin hooks
CLI Reference:
- CLI Index - All CLI commands with examples
Configuration:
- Configuration Reference - All configuration options
- Error Code Reference - Complete error catalog with solutions
Conceptual understanding and design discussions:
- Understanding pytest-jux - High-level overview and use cases
- Architecture - System design, components, and design decisions
- Security - Why sign test reports, threat model, security best practices
- Performance - Performance characteristics, benchmarks, optimization
- Security Policy - Vulnerability reporting
- Threat Model - Threat analysis and mitigation
- Cryptographic Standards - Algorithms and standards
- SLSA Verification - Supply chain security
I want to...
- Get started → Quick Start (5 minutes)
- Set up CI/CD → CI/CD Deployment
- Troubleshoot issues → Troubleshooting Guide
- Look up CLI commands → CLI Reference
- Understand security → Security Explanation
- Browse all docs → Complete Documentation Index
# Clone repository
git clone https://github.com/jux-tools/pytest-jux.git
cd pytest-jux
# Create virtual environment
uv venv
source .venv/bin/activate # or: .venv\Scripts\activate on Windows
# Install development dependencies
uv pip install -e ".[dev,security]"
# Install pre-commit hooks
pre-commit install# Format code
make format
# Lint code
make lint
# Type checking
make type-check
# All quality checks
make quality# Run all tests
make test
# Run tests with coverage
make test-cov
# Run security tests
make test-security# Run security scanners
make security-scan
# Run security test suite
make security-test
# Complete security validation
make security-full# Validate C4 DSL architecture model
podman run --rm -v "$(pwd)/docs/architecture:/usr/local/structurizr" \
structurizr/cli validate -workspace workspace.dsl
# Generate architecture diagrams
podman run --rm -p 8080:8080 \
-v "$(pwd)/docs/architecture:/usr/local/structurizr" structurizr/lite
# Open http://localhost:8080pytest-jux/
├── pytest_jux/ # Plugin source code
│ ├── __init__.py
│ ├── plugin.py # pytest hooks
│ ├── signer.py # XML signing
│ ├── verifier.py # Signature verification
│ ├── canonicalizer.py # C14N operations
│ ├── config.py # Configuration management (Sprint 3)
│ ├── metadata.py # Environment metadata (Sprint 3)
│ ├── storage.py # Local storage & caching (Sprint 3)
│ ├── api_client.py # REST API client (Sprint 4)
│ └── commands/ # CLI commands
│ ├── keygen.py # Key generation
│ ├── sign.py # Offline signing
│ ├── verify.py # Signature verification
│ ├── inspect.py # Report inspection
│ ├── cache.py # Cache management (Sprint 3)
│ ├── config_cmd.py # Config management (Sprint 3)
│ └── publish.py # Manual publishing (Sprint 4)
├── tests/ # Test suite
│ ├── test_plugin.py
│ ├── test_signer.py
│ ├── test_verifier.py
│ ├── test_canonicalizer.py
│ ├── test_config.py # Config tests (Sprint 3)
│ ├── test_metadata.py # Metadata tests (Sprint 3)
│ ├── test_storage.py # Storage tests (Sprint 3)
│ ├── test_api_client.py # API client tests (Sprint 4)
│ ├── commands/ # CLI command tests
│ │ ├── test_cache.py # Cache command tests (Sprint 3)
│ │ ├── test_config_cmd.py # Config command tests (Sprint 3)
│ │ └── test_publish.py # Publish command tests (Sprint 4)
│ ├── security/ # Security tests
│ └── fixtures/ # JUnit XML fixtures & test keys
├── docs/ # Documentation
│ ├── tutorials/ # Learning-oriented
│ ├── howto/ # Problem-oriented
│ ├── reference/ # Information-oriented
│ ├── explanation/ # Understanding-oriented
│ ├── adr/ # Architecture decisions
│ ├── architecture/ # C4 DSL models
│ ├── sprints/ # Sprint documentation
│ └── security/ # Security documentation
├── .github/
│ └── workflows/
│ └── security.yml # Security scanning workflow
├── LICENSE # Apache License 2.0
├── NOTICE # Copyright notices
├── Makefile # Development commands
├── pyproject.toml # Project metadata
└── README.md # This file
Note: This project does not include database models (models.py) or database integration. These are handled by the Jux API Server.
┌─────────────────────────────────┐ ┌──────────────────────────┐
│ pytest-jux (Client) │ │ Jux API Server │
│ │ │ │
│ 1. Run tests │ │ 6. Receive signed XML │
│ 2. Generate JUnit XML │ │ 7. Verify signature │
│ 3. Canonicalize (C14N) │ HTTP │ 8. Check for duplicates │
│ 4. Sign with XMLDSig │ ─POST─> │ 9. Store in database │
│ 5. Publish to API │ │ 10. Return status │
│ │ │ │
│ • XML signing │ │ • Report storage │
│ • Environment metadata │ │ • Duplicate detection │
│ • API client │ │ • Signature verification│
│ │ │ • Query API │
│ │ │ • Web UI │
└─────────────────────────────────┘ └──────────────────────────┘
pytest-jux integrates with pytest via the pytest_sessionfinish hook, processing JUnit XML reports after test execution completes.
- Generate: pytest creates JUnit XML report (
--junit-xml) - Canonicalize: Convert XML to canonical form (C14N) and compute SHA-256 hash
- Sign: Generate XMLDSig signature using private key
- Capture Metadata: Collect environment information (hostname, platform, etc.)
- Publish: POST signed report + metadata to Jux REST API
- Receive: Accept signed XML report via REST API
- Verify: Validate XMLDSig signature
- Deduplicate: Check canonical hash against existing reports
- Store: Save to database (SQLite or PostgreSQL)
- Index: Make report queryable via API and Web UI
The project's architecture is documented using C4 DSL models in docs/architecture/. See the architecture documentation for:
- System context: pytest-jux in the Jux ecosystem
- Container view: Plugin components and REST API integration
- Component view: Internal module structure
- This project follows AI-Assisted Project Orchestration patterns
- ADRs provide AI context across development sessions
- Sprint documentation maintains development continuity (see
docs/sprints/) - C4 DSL models provide visual architecture context
- TDD approach guides AI-assisted test and implementation generation
- AI assistance for boilerplate generation (pytest hooks, SQLAlchemy models)
- Human review required for cryptographic code (security-critical)
- Quality gates: all tests pass, type checking clean, code coverage >85%
- Context management: ADRs and sprint docs enable session continuity
Contributions welcome! Please:
- Read the Architecture Decision Records to understand project direction
- Follow the development best practices
- Review security guidelines for security-sensitive changes
- Ensure all tests pass and coverage remains >85%
- Run security scanners:
make security-full - Update documentation for new features
- Use conventional commits for clear changelog generation
Copyright 2025 Georges Martin
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
See LICENSE for the full license text.
Source of Truth: The canonical API specifications live in jux-openapi.
- OpenAPI specs:
jux-openapi/specs/v1/- API changelog:
jux-openapi/docs/CHANGELOG.md
pytest-jux v0.4.0+ is compliant with jux-openapi API v1.0.0.
The Jux API Server is the server-side component that works with pytest-jux. It is a separate project that provides:
Core Functionality:
- REST API endpoints for receiving signed test reports
- XMLDSig signature verification
- Report storage in SQLite (local) or PostgreSQL (distributed)
- Canonical hash-based duplicate detection
- Query API for retrieving stored reports
User Interfaces:
- Web UI for browsing and visualizing test results
- CLI tools for querying reports
- API documentation (OpenAPI/Swagger)
Technology Stack:
- Elixir/Phoenix framework
- PostgreSQL or SQLite database
- RESTful API design
Deployment Options:
- Local development (SQLite)
- Single-server deployment (PostgreSQL)
- Distributed/multi-tenant deployment (PostgreSQL cluster)
For more information about the Jux API Server, refer to its separate repository and documentation.
Latest Release: v0.4.1 - Documentation & C4 Model Updates (2026-01-08)
Release Notes: See docs/release-notes/ for detailed release notes for all versions.
Changelog: See CHANGELOG.md for a complete version history.
- Documentation: docs/
- Security: Security Policy
- Issues: GitHub Issues
- Discussions: GitHub Discussions