Releases: bradh11/certmonitor
Release v0.3.0
📦 CertMonitor v0.3.0 – Zero-Dependency Milestone & Chain Validator
Release Date: April 15, 2026
Repository: bradh11/certmonitor
🚀 Overview
CertMonitor v0.3.0 is a zero-dependency milestone. The Rust extension's entire X.509 / DER parser is now written in-house against the Rust standard library — no third-party parsing crates in the runtime dependency tree. The Rust dependency count drops from 48 crates to 20, with every remaining crate being pyo3 or a build-time helper.
The parser is annotated #![forbid(unsafe_code)] at the crate root, returns Result on every code path (no panics on malformed input), and has been fuzz-tested against 1.7 billion adversarial byte sequences with zero crashes.
This release also ships the chain validator for structural inspection of TLS certificate chains, and fixes two latent bugs in the public key info output.
✨ Added
chainvalidator (#14): structural validation of the full TLS certificate chain. Flags missing intermediates, out-of-order chains, expired members, weak signature algorithms (SHA-1, MD5), non-CA intermediates, and unexpected self-signed leaves. Registered but disabled by default — opt in viaenabled_validators=["chain"]orENABLED_VALIDATORS=...,chain. Chain retrieval requires Python 3.10+; returns a clear error on older interpreters.certinfo.analyze_chain(Rust): parses a full DER chain in a single PyO3 call and returns per-cert details plus subject/issuer and SKI/AKI linkage.SSLHandler.fetch_raw_certnow additionally returnschain_derandchain_error, populated viaSSLSocket.get_verified_chain()on Python 3.13+ and the stable_sslobj.get_unverified_chain()fallback on 3.10–3.12.- In-tree DER / X.509 parser (#22): a strict-DER, no-
unsafe, panic-free parser underrust_certinfo/src/{der,x509}/. Theder/layer (TLV reader, OID decoder, time parser, string decoders) is reusable for future ASN.1-based capabilities. Thex509/layer (Certificate, Name, SPKI, AlgorithmIdentifier, Extensions) composes those primitives into RFC 5280 structures. - Fuzz harness (#25):
make fuzz(60-second smoke run) andmake fuzz-long(1-hour soak) Makefile targets. Pre-release soak run results: 1.7 billion iterations, 310 code-coverage points, 503 libfuzzer features explored, zero crashes. Requires nightly Rust +cargo-fuzz. Manual pre-release gate, not CI. - 130-cert real-world corpus (
tests/test_certinfo_corpus.py): snapshot tests run every publiccertinfoentry point against captured certs from 101 production hosts on every CI run. - 56 in-module Rust unit tests covering DER primitives, OID round-trips, time parsing, Name/RDN walking, SPKI dispatch, and extension parsing.
pythonCargo feature on thecertinfocrate (default on). Disabling it drops the PyO3 layer entirely and builds only the pure-Rust parser core — used by the fuzz crate.scripts/bench_chain.py: opt-in benchmark with a microbench ofanalyze_chain(~400 µs/call) and a 101-host concurrent pipeline test.
🔄 Changed
- Zero non-pyo3 Rust dependencies.
x509-parserandbase64are gone. The Rust dependency tree shrinks from 48 crates to 20 — every remaining crate ispyo3or a build-time helper.cargo auditsurface drops accordingly. Cargo.tomlcrate-type is now["cdylib", "rlib"]. Thecdylibis the same Python wheel target;rliblets the fuzz crate link the parser as a normal Rust library. No published-wheel surface change.certinfo::Certificate::from_derandcertinfo::ParseErrorare nowpubat the crate root for use by the fuzz crate and future in-tree Rust consumers. The PyO3 boundary and Python-facing API are unchanged.
🛠️ Fixed
- EC
curvefield now correctly contains the curve OID. Previous builds emitted the algorithm OID1.2.840.10045.2.1(id-ecPublicKey) in the field literally namedcurve. The new parser extracts the curve OID fromalgorithm.parameters:1.2.840.10045.3.1.7for P-256,1.3.132.0.34for P-384,1.3.132.0.35for P-521. Visible behavior change for any caller readingpublic_key_info["curve"]. - RSA modulus bit length is no longer over-counted by 8 bits. Previous builds reported RSA-2048 / 3072 / 4096 keys as 2056 / 3080 / 4104 due to including the DER-mandated leading-zero sign byte. The new parser reports the canonical 2048 / 3072 / 4096. Visible in
public_key_info["size"].
📚 Documentation
- New per-validator docs page:
docs/validators/chain.md. - README: new "Why Trust CertMonitor" section with fuzz results, zero-dep guarantee,
forbid(unsafe_code), and coverage numbers. - Comprehensive documentation at certmonitor.readthedocs.io.
🐍 Python Compatibility
Tested with Python 3.8 through 3.13 with 99% code coverage across all supported versions. The chain validator requires Python 3.10+ for chain retrieval; all other features work on 3.8+.
📝 License
This project is licensed under the MIT License. See the LICENSE file for details.
Full Changelog: v0.2.0...v0.3.0
Release v0.2.0
📦 CertMonitor v0.2.0 – Dynamic Validator Args & sensitive_date validator
Release Date: April 13, 2026
Repository: bradh11/certmonitor
🚀 Overview
CertMonitor v0.2.0 overhauls how validators receive arguments. New validators can now declare their user arguments directly on the validate() method signature — the dispatcher discovers them automatically and no core changes are needed. As part of the same effort, the sensitive_date validator — which has been sitting on develop since #15 back in June 2025 — finally makes it into a release, and gets ergonomic input forms, a structured match field, and structured error handling along the way.
This is a minor version bump to reflect the scale of the changes, not because of any hard break in the public API. Existing callers using validator_args={"subject_alt_names": [...]} still work with a DeprecationWarning, and no validator output shape has changed for users.
✨ Added
- Dynamic validator argument dispatch (#18): validators declare their user-configurable arguments directly on the
validate()method signature, andCertMonitor.validate(validator_args=...)discovers them automatically. New validators get argument passing for free — zero core changes needed. CertMonitor.describe_validators(): new introspection helper that returns every registered validator's name, docstring, and argument schema (name, annotation, default). Useful for building CLI--helppages, config validators, or dashboards.sensitive_datevalidator finally ships: flags certificates that expire on weekends, leap days, or user-specified dates (e.g. Black Friday, Cyber Monday, go-live dates).sensitive_dateinput ergonomics: thedatesargument acceptsSensitiveDatenamed tuples, plaindate/datetimevalues, ISO 8601 strings ("2025-12-25"), or(name, date)tuples — all mixable in a single call. No need to importSensitiveDatefrom a deeply nested module path just to pass a list of blackout dates.sensitive_date_matchesstructured field: matching sensitive dates are surfaced as a machine-readable list of{"name", "date"}entries in addition to the existing human-readablewarningsstrings.- Weekend / leap-day warning strings: when the
sensitive_datevalidator flags a weekend or leap-day expiry, a human-readable warning line is now emitted alongside the existing boolean fields, so log output is self-explanatory whenis_validis false. - Shared
parse_not_afterhelper (certmonitor/validators/_utils.py): centralizes thenotAfterformat string shared byexpirationandsensitive_date.
🔄 Changed
- Validator author contract: user arguments on
validate()must be keyword-only, type-annotated, and have a default value. Enforcement runs inBaseCertValidator/BaseCipherValidator__init_subclass__at import time, so a malformed validator raisesTypeErrorthe moment its module is imported. No user-facing impact — every built-in validator conforms, and the dispatcher continues to accept the pre-0.2.0validator_argscall style via a deprecation shim. subject_alt_namesandsensitive_datesignatures migrated to keyword-only user arguments (alternate_names=...,dates=...). Existing users ofmonitor.validate(validator_args={...})are unaffected; callers invoking the validator classes directly with positional arguments need the keyword form.validator_argscanonical form is now a nested dict:validator_args={"subject_alt_names": {"alternate_names": [...]}}. The pre-0.2.0 bare-list form still works and is transparently rewritten by the dispatcher — with aDeprecationWarning— so no user code needs to change immediately.sensitive_dateerror handling: malformeddatesinput (wrong type, invalid ISO string, bad tuple shape) now returns a structured error dict instead of raisingTypeError, matching the rest of the validator suite.expirationvalidator: now uses the sharedparse_not_afterhelper; behavior unchanged.mkdocs.yml: added the previously-missingSensitiveDatenav entry so the validator's auto-generated reference page is reachable.docs/usage/validator_args.md: rewritten to document the canonical nested-dict form,describe_validators(), the bare-list deprecation, and a workedsensitive_dateexample showing all four input forms.- Rust toolchain floor moved to
rustc >= 1.88.0(transitively via thetime 0.3.47security bump, see below). Affects contributors and source builds only — published wheels are unaffected.
⚠️ Deprecated
- Bare-list shorthand for single-argument validators (
validator_args={"subject_alt_names": [...]}) still works but now emits aDeprecationWarning. Migrate to the canonical nested-dict form. Scheduled for removal in a future release.
🔒 Security
- RUSTSEC-2026-0009: bumped the
timecrate from0.3.41to0.3.47(transitively viax509-parser) to address the denial-of-service-via-stack-exhaustion advisory.
🛠️ Fixed
subject_alt_namescore dispatch: the hardcodedif validator.name == "subject_alt_names"special case incore.validate()is gone — replaced with the generic argument-resolution helper used by every validator.CHANGELOG.md: backfilled the missing[0.1.4]section from the published release notes so the historical record is complete.
📚 Documentation
Comprehensive documentation is available at certmonitor.readthedocs.io.
🐍 Python Compatibility
Tested with Python 3.8 and above with 98%+ code coverage across all supported versions.
📝 License
This project is licensed under the MIT License. See the LICENSE file for details.
Full Changelog: v0.1.4...v0.2.0
Release v0.1.4
📦 CertMonitor v0.1.4 – Test Coverage & CI Optimization
Release Date: June 2, 2025
Repository: bradh11/certmonitor
🚀 Overview
CertMonitor v0.1.4 focuses on fixing #16 and improving test coverage excellence. This release achieves 99% test coverage, streamlined security scanning, and enhanced developer convenience methods while maintaining zero runtime dependencies.
Added
- Achieved 99% test coverage (up from 95%) with comprehensive edge case testing
- Instance convenience methods for improved developer experience:
monitor.get_enabled_validators()- Get validators enabled for this specific instancemonitor.list_validators()- Get all available validators
- Enhanced test suite with 323 comprehensive tests covering all edge cases
Changed
- Streamlined security scanning - removed heavy semgrep dependency, kept focused bandit scanning
- Improved test descriptions - removed line number references for maintainable functionality-focused tests
- Enhanced validator configuration - proper distinction between empty lists vs config defaults
Fixed
- Default validator behavior -
enabled_validators=[]now properly means "no validators" vsNonemeaning "use defaults" as referenced in #16 - Configuration environment handling - proper string parsing for ENABLED_VALIDATORS environment variable
- Test coverage gaps - targeted testing for previously uncovered edge cases:
- SSL handler retry exception scenarios
- Certificate parsing fallback mechanisms
- Handler None conditions in raw data operations
- Public key parsing error paths
📚 Documentation
Comprehensive documentation is available at certmonitor.readthedocs.io
🐍 Python Compatibility
Tested with Python 3.8 and above with 99% code coverage across all supported versions.
📝 License
This project is licensed under the MIT License. See the LICENSE file for details.
Full Changelog: v0.1.3...v0.1.4
Release v0.1.3
Added
- Comprehensive GitHub workflows and templates with develop/main branch strategy
- Enhanced type hints throughout entire codebase (zero mypy errors)
- Modularized test suite for better maintainability
- ReadTheDocs integration with proper configuration
- Consolidated CI/CD pipeline with conditional job execution
- Unified Makefile commands for Python + Rust development workflow
make format- Format both Python and Rust codemake format-check- Check formatting for both languagesmake lint- Lint both Python and Rust codemake security- Run security vulnerability check (cargo audit)- Individual language commands:
python-format,python-lint,rust-format,rust-lint - Enhanced
make testwith 9-step CI-equivalent comprehensive testing including security checks - Improved
make helpwith clear categorized command documentation
Changed
- Improved code organization and structure
- Enhanced documentation and contributing guidelines
- Updated all workflows to use develop/main branch strategy
- Removed redundant CI configurations (quality.yml, security.yml, rust.yml, docs.yml)
- Streamlined dependency management (removed Dependabot for stdlib-only project)
- Enhanced local development experience with unified format/lint commands
- Makefile now provides comprehensive Python + Rust development workflow
Fixed
- All mypy type errors across 20 source files using proper type annotations and runtime checking
- Import sorting and code quality issues
- SSL handler connection logic and check_connection() functionality
- Python 3.8 compatibility issues (datetime.UTC → datetime.timezone.utc)
- CI workflow syntax errors and redundant documentation building
- Fixed incorrect reporting on root certificate validation
- Security vulnerability RUSTSEC-2025-0020 by upgrading PyO3 from 0.20.0 to 0.24.1
- Rust code compatibility with PyO3 0.24.x API changes (updated module binding syntax)
Release v0.1.2
📦 CertMonitor v0.1.2 – Initial Release
Release Date: May 11, 2025
Repository: bradh11/certmonitor
🚀 Overview
CertMonitor is a Python library designed for robust SSL/TLS certificate monitoring and validation. Built with a strict philosophy of using only the Python standard library, it ensures maximum portability and reliability across all Python environments. This initial release focuses on delivering high performance and security without any third-party dependencies.
✨ Features
- Zero Dependencies: Utilizes only the Python standard library, ensuring lightweight and portable deployments.
- SSL/TLS Certificate Retrieval: Fetches certificate details from hostnames or IP addresses.
- Built-in Validators:
expiration: Checks if the certificate is expired or nearing expiration.hostname: Validates that the hostname matches the certificate's subject alternative names (SANs).subject_alt_names: Ensures the presence and correctness of SANs.root_certificate: Verifies if the certificate is issued by a trusted root CA.key_info: Validates the public key type and strength.tls_version: Checks the negotiated TLS version.weak_cipher: Ensures the use of strong cipher suites.
- Context Manager Support: Provides a recommended usage pattern for resource management.
- Raw Certificate Data Access: Retrieve DER and PEM formats of certificates.
- Cipher Information Retrieval: Access details about the negotiated cipher suite.
- Protocol Detection: Automatically detects SSL/TLS or SSH protocols for the target host.
- Error Handling: Gracefully handles errors, returning informative messages.
🛠 Installation
From PyPI (if published):
pip install certmonitorFrom Source:
git clone https://github.com/bradh11/certmonitor.git
cd certmonitor
pip install .📚 Documentation
Comprehensive documentation is available at certmonitor.readthedocs.io (coming soon).
🧪 Example Usage
from certmonitor import CertMonitor
with CertMonitor("example.com") as monitor:
cert_data = monitor.get_cert_info()
validation_results = monitor.validate(validator_args={"subject_alt_names": ["www.example.com"]})
print(cert_data)
print(validation_results)🐍 Python Compatibility
Tested with Python 3.8 and above. Given that Python 3.8 is nearing its end-of-life in October 2024, it's recommended to use Python 3.9 or newer for optimal support and security.
📝 License
This project is licensed under the MIT License. See the LICENSE file for details.
Full Changelog: https://github.com/bradh11/certmonitor/commits/v0.1.0