Skip to content

Release 1.9.1 - #159

Merged
skupriienko merged 18 commits into
masterfrom
release/1.9.1
Oct 7, 2026
Merged

skupriienko merged 18 commits into
masterfrom
release/1.9.1

Conversation

@skupriienko

@skupriienko skupriienko commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR finalizes the release of v1.9.1 (October 7, 2026), incorporating the auto-pagination fix from PR #158 alongside key reliability, security, and supply-chain enhancements:

  • Pagination Reliability: Enforces the Mailjet API specification ceiling (Limit=1000) in Endpoint.stream(), normalizes query parameter filter keys (limit vs Limit), and addresses premature pagination termination caused by page-local Total metadata.
  • Security Hardening (SpamGuard DoS Protection): Introduces a 10,000-tag structural ceiling (MAX_TAG_COUNT) and explicit MAX_HTML_SIZE (5MB) enforcement in _SpamGuardParser to defend against HTML tag-bomb denial-of-service vectors (CWE-400).
  • Test Suite Idempotency: Resolves integration test webhook collisions (MJ18 errors) via pre-test cleanup in test_live_eventcallbackurl_crud_lifecycle.
  • Documentation & Samples: Adds interactive pagination stream examples, stream resumption samples, and automated smoke test validation.
  • CI/CD & Supply Chain Maintenance: Optimizes ClusterFuzzLite scheduling, updates pre-commit hook revisions, and merges Dependabot action updates across CodeQL, Miniconda, Codecov, and OSV-Scanner (PRs build(deps): bump github/codeql-action/upload-sarif from 4.37.9 to 4.38.0 #151–build(deps): bump github/codeql-action/analyze from 4.38.0 to 4.38.1 #157).

Actions

  • Core SDK & Endpoint Pagination (mailjet_rest/endpoint.py):

    • Fixed premature auto-pagination termination in Endpoint.stream() caused by page-local Total metadata (Total == len(Data)), continuing across full pages until len(data) < chunk_size.
    • Enforced Mailjet's API pagination ceiling (MAX_LIMIT: int = 1000) in Endpoint.stream() with an explicit ValueError guard to prevent silent page truncation caused by upstream clamping.
    • Updated _normalize_stream_filters() to strip both "limit" and "Limit" keys from input filters, ensuring caller filters do not conflict with chunk_size.
    • Updated Endpoint.stream() docstrings to document the 1,000-record maximum chunk limit.
  • Security & Guardrails (mailjet_rest/utils/guardrails.py, SECURITY.md):

    • HTML Tag Bomb DoS Mitigation (CWE-400): Defined SecurityGuard.MAX_TAG_COUNT = 10000 and implemented _count_tag() across start, end, self-closing, and comment tag handlers in _SpamGuardParser to trigger immediate ValidationError on nested tag flood attacks.
    • Explicitly exposed SecurityGuard.MAX_HTML_SIZE = 5 * 1024 * 1024 (5MB) and safeguarded parser closing lifecycles.
    • Updated SECURITY.md supported versions table to designate versions < 1.9.1 as end-of-life.
  • Tests, Fuzzing & Reliability (tests/):

    • Added test_stream_with_page_local_total verifying multi-page streaming across varied offsets (Offset: 0, Offset: 10) and collection sizes (size: 3, size: 4).
    • Added test_stream_chunk_size_max_limit_rejection to verify ValueError triggers when chunk_size > 1000.
    • Added test_stream_filter_strips_both_limit_casings to ensure filter normalization cleanly strips both casing variants.
    • Added test_stream_empty_collection_exits_immediately to verify a clean 1-request exit on empty collections.
    • Fixed test_live_eventcallbackurl_crud_lifecycle failure by adding idempotent pre-test cleanup for existing "open" webhooks, preventing MJ18 duplicate resource constraint errors (400 Bad Request).
    • Cleaned up misplaced imports (DoesNotExistError, ValidationError) in tests/integration/test_client.py.
    • Optimized tests/fuzz/fuzz_spamguard.py harness performance (bounded baseline buffer to 4,096 chars, deterministic >5MB memory exhaustion checks) and pruned duplicate entries from tests/fuzz/fuzzer.dict.
  • Documentation & Developer Experience (DX):

    • Expanded README.md with explicit side-by-side examples comparing manual single-page queries (.get(filters={"Limit": ..., "Offset": ...})) against lazy auto-pagination (.stream()).
    • Documented stream resumption using explicit starting offsets and query string parsing (urllib.parse.parse_qs).
    • Added stream_all_contacts() to samples/contacts_sample.py.
    • Added automated smoke validation for .stream(), offset resumption, and multidict filters to samples/smoke_readme_runner.py.
  • CI/CD, Workflows & Dependencies:


Release


Verification & Testing

To verify these changes locally, ensure your environment variables (MJ_APIKEY_PUBLIC and MJ_APIKEY_PRIVATE) are set, then execute:

  • 1. Run the Unit & Integration Test Suite:
    Validates core routing, pagination boundary math, SpamGuard DoS protections, and live CRUD lifecycles.
pytest tests/ -v
  • 2. Run the Advanced Fuzzing Suite (Atheris):
    Executes mutation coverage across core handlers, parsers, HTML filters, and differential serializers.
manage.sh fuzz_all 28800 -use_value_profile=1 -max_len=4096 -shrink=1
  • 3. Execute the Interactive Examples:
    Runs the unified documentation scripts demonstrating fluent builders, attachment chunking, and pagination streaming.
bash manage.sh test_examples

Notes

  • RC Pre-Release Verification:
python3 -m venv test_mailjet191rc1
source test_mailjet191rc1/bin/activate

pip3 install --upgrade pip
pip3 install mailjet-rest==1.9.1rc1

python3 -c "from mailjet_rest import Client"
python3 -c "import mailjet_rest; from importlib.metadata import version; print(f'Successfully installed v{version(\"mailjet_rest\")}')"

# Expected output:
# Successfully installed v1.9.1rc1

…n stream

- Enforce MAX_LIMIT = 1000 in stream() to prevent silent page truncation.
- Strip both 'limit' and 'Limit' keys in _normalize_stream_filters.
- Add unit tests for max limit rejection, filter casing normalization, and empty collections.
…l lifecycle

- Purge pre-existing 'open' webhooks prior to creation to prevent MJ18 collision errors.
- Move misplaced DoesNotExistError and ValidationError imports to top of module.
- Add comparison of manual GET pagination vs lazy .stream() to README.
- Add stream_all_contacts example in samples/contacts_sample.py.
- Add stream pagination and offset resumption verification steps in smoke_readme_runner.py.
- Change ClusterFuzzLite batch schedule to monthly.
- Add descriptive comment to security workflow schedule.
- Bump commitizen, semgrep, typos, check-jsonschema, ruff, and mypy hook revisions.
@skupriienko skupriienko self-assigned this Oct 6, 2026
- Enforce MAX_TAG_COUNT = 10000 in SecurityGuard to mitigate CWE-400 resource exhaustion.
- Override handle_startendtag, handle_endtag, and handle_comment in _SpamGuardParser to count all tokens.
- Synchronize is_safe and has_script threat flags upon detecting forbidden tags or event handlers.
- Support parameterless _SpamGuardParser instantiation to maintain mock parser test compatibility.
- Replace 50% sub-5MB string multiplication with deterministic 1% >5MB boundary synthesis.
- Cap base chaotic HTML generation to 4096 characters to maintain high execution throughput.
- Eliminate slow-unit timeouts and excessive RSS memory peaks during Atheris runs.
@skupriienko
skupriienko marked this pull request as ready for review October 7, 2026 20:21
@skupriienko
skupriienko merged commit 14fe924 into master Oct 7, 2026
27 checks passed
@skupriienko
skupriienko deleted the release/1.9.1 branch October 7, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant