feat: TEA client v0.2.0 — auth, mTLS, CLE, CLI, and security hardening#4
feat: TEA client v0.2.0 — auth, mTLS, CLE, CLI, and security hardening#4aurangzaib048 wants to merge 49 commits intosbomify:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR bumps libtea to v0.2.0 and expands the TEA client with new auth modes (Basic + mTLS), retry/backoff behavior, CLE endpoints/models, a Typer-based tea-cli, and multiple security hardening measures (notably SSRF protections for artifact downloads).
Changes:
- Add Basic auth + mTLS configuration, retry/backoff plumbing, and endpoint failover during
.well-knowndiscovery. - Introduce CLE (Common Lifecycle Enumeration) models + 4 CLE client methods, plus related test coverage.
- Add
tea-cli(optional extra) with commands for discovery/search/get/download/inspect, plus entry-point wrapper.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock |
Locks new runtime/optional dependencies (semver, typer stack) and bumps libtea to 0.2.0. |
pyproject.toml |
Updates version, adds semver, adds [cli] extra + tea-cli script entry. |
libtea/_http.py |
Adds mTLS/basic-auth handling, retry configuration, download redirect handling, and SSRF protections. |
libtea/client.py |
Plumbs auth/mTLS/retry, adds failover probing, CLE methods, paging validation, and download size limit + weak-hash warning. |
libtea/discovery.py |
Adds scheme/port options, mTLS forwarding, domain validation helper, and SemVer-based endpoint selection (plural + singular). |
libtea/models.py |
Makes identifiers forward-compatible, relaxes Collection fields, adds CLE models, enforces DiscoveryInfo.servers min length. |
libtea/cli.py |
New Typer CLI implementing TEA consumer workflows (optional dependency). |
libtea/_cli_entry.py |
New wrapper entrypoint intended to handle missing CLI deps gracefully. |
libtea/__init__.py |
Re-exports new public surface (mTLS config, CLE models, discovery helpers). |
tests/test_http.py |
Adds tests for auth/mTLS, retry config, SSRF protection, redirects, size limits, and truncation messaging. |
tests/test_client.py |
Adds tests for discovery failover/probing, paging validation, CLE, and weak-hash warning behavior. |
tests/test_discovery.py |
Updates SemVer tests, adds scheme/port tests, mTLS forwarding, domain validation, and endpoint selection list behavior. |
tests/test_models.py |
Adds CLE model tests and updates identifier/collection validation expectations. |
tests/test_download.py |
Adds cleanup behavior test for multi-checksum partial failure. |
tests/test_cli.py |
New CLI test suite (skips when typer isn’t installed). |
docs/plans/* + docs/FUTURE.md + CLAUDE.md |
Adds/updates design docs and repo guidance for current + future roadmap. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 20 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Remove phantom semver dep (re-add when Task 8 uses it) - Remove tea-cli entry point (re-add when Task 9 creates libtea/cli.py) - Remove cli optional-dependencies (re-add with entry point) - Add SemVer pre-release character validation per spec item 9 - Reject trailing-hyphen SemVer like "1.0.0-" (empty pre-release) - Add RFC 1035 253-char total domain length limit - Improve empty path segment error message - Add direct unit tests for _is_valid_domain() edge cases - Add SemVer edge case tests (four-part, single-number, invalid chars) - Move orphan test into TestDiscoveryInfo class with proper imports
- Add port range validation (1-65535) in fetch_well_known - Emit TeaInsecureTransportWarning for HTTP scheme in discovery - Expand CLE method docstrings with Args/Returns sections - Reorganize __all__ exports with section comments - Add 8 tests: port validation, HTTP warning, CLE unsafe UUID, CLE malformed response, from_well_known scheme/port forwarding
- Block basic auth over plaintext HTTP (security parity with token) - Validate max_retries >= 0 (prevent infinite retry loop) - Clear session.auth on close() (credential cleanup) - Forward max_retries/backoff_factor through TeaClient - Catch ValueError from invalid ChecksumAlgorithm in CLI download - Add type annotations to CLI helpers (_output, _error) - Fix hardcoded /tmp path in test_http.py - Add tests for download, inspect, error paths, --component --version
- P0-1: graceful entry point when typer not installed (_cli_entry.py wrapper) - P1-4: add --auth, --client-cert, --client-key, --ca-bundle to all CLI commands - P2-2: disable server-controlled Retry-After to prevent stalling - P2-3: add tests for --domain discovery path in CLI - P3-1/P3-7: add --max-components to inspect with truncation warning - P3-3: fix TestSemVer to import from semver directly, not private alias - P3-4: include discovery servers info in inspect output - P3-5: block private IPs, loopback, and localhost in download URLs (SSRF) - P3-6: recommend env vars (TEA_TOKEN, TEA_AUTH) in --token/--auth help text
CI runs without the [cli] extra, so typer is unavailable. Use pytest.importorskip to gracefully skip test_cli.py.
- Add DNS rebinding protection via hostname resolution check - Follow download redirects manually with SSRF validation at each hop - Add download size limit (max_download_bytes parameter) - Expand SSRF hostname blocklist with GCP metadata endpoint - Pass mTLS config through to endpoint probe requests - Add page_size bounds validation on paginated endpoints - Make Collection.uuid and Collection.version optional per TEA spec - Change Identifier.id_type to str for forward-compatibility - Remove dead redirect check in discovery fetch - Use BaseException for download cleanup to catch KeyboardInterrupt - Add truncation indicator to error response body snippets
- Add CGNAT (RFC 6598) range to SSRF IP blocklist - Add post-redirect SSRF validation in discovery - Forward mTLS config to fetch_well_known and clear on close - Remove UDI from IdentifierType (not in TEA spec) - Add page_offset and collection_version validation - Warn on weak hash algorithms (MD5, SHA-1) - Add --max-download-bytes CLI option - Export discovery functions from package root - Relax pydantic floor to >=2.1.0 - Add 25 new tests covering all changes (407 total, 97% coverage)
- v0.3.0: httpx migration, AsyncTeaClient, pagination iterators, SemVer range matching, DNS TEI resolution, Protocol/ABC, code quality refactors, interactive CLI - FUTURE.md: Publisher API (blocked on TEA spec stability) - Update v0.1.0 and v0.2.0 docs to reference FUTURE.md
- Add model parse tests for supersededBy, endOfLife, endOfDistribution, endOfMarketing event types (previously only had enum value tests) - Rewrite CLAUDE.md with architecture overview, critical implementation rules, single-test commands, and design doc references
- Fix cli.py/\_cli_entry.py interaction: let ImportError propagate
naturally from cli.py instead of catching and raising SystemExit at
import time; _cli_entry.py already handles the graceful error message
- Add model_validator to CLEVersionSpecifier requiring at least one of
version or range (reject empty {})
- Soften CLE docstring: event ordering is producer-determined, not
enforced by the model
- Block unspecified (0.0.0.0, ::) and multicast IPs in SSRF protection
- Add tests for all new behaviors (414 total, 97% coverage)
- Update inspect function in cli.py to handle component releases more robustly by checking for the presence of a release before fetching component details. - Modify client.py to allow additional URL-safe characters (underscores, periods, tildes) in path segments for improved flexibility. - Update tests in test_cli.py to reflect changes in component structure, ensuring proper handling of component releases in test cases. - Add new tests for validating path segments to include support for nanoid-style IDs and additional characters.
- Introduce a new function to extract the domain from a TEI URN. - Update the _build_client function to allow domain auto-discovery from TEI when neither --base-url nor --domain is provided. - Modify the discover and inspect functions to pass the TEI argument to the client for improved discovery capabilities.
…dd tests - Remove unused ErrorResponse model and TestSemVer third-party tests - Replace Optional[X] with X | None across CLI (Python 3.11+ syntax) - Use Self return type instead of quoted string in CLEVersionSpecifier - Simplify _probe_endpoint exception handling (merge redundant clauses) - Remove import aliasing (requests as _requests), type bare dict - Bump requests>=2.32.4 (CVE-2024-47081), align pre-commit ruff rev - Add --cov-fail-under=90 and uv build to CI, test gate to PyPI publish - Replace fragile sed TOML parsing with Python tomllib in pypi.yaml - Add SSRF scheme guard test using unittest.mock (ftp:// injection) - Add 12 tests: CLI error paths, inspect fallback, TEI auto-discovery - Fix warning suppression test to assert on recorded warnings
Enrich module-level, class, and method docstrings with Args/Returns/Raises/Attributes sections, usage examples, and cross-references for Sphinx compatibility.
Migrate from flat libtea/ to src/libtea/ layout. Extract _validation.py (input validators), _security.py (SSRF/DNS protection), and _hashing.py (checksum builders) from oversized client.py and _http.py. Add __all__ to public modules. Reorganize tests into unit/, client/, cli/, and integration/ subdirectories with dedicated test files for each extracted module. Update CLAUDE.md and pyproject.toml for new structure.
test_entry_point_registered_in_pyproject used parent.parent to reach the project root, but moving test_cli.py into tests/cli/ added a directory level. Update to parent.parent.parent.
The subprocess-based test doesn't contribute to pytest-cov coverage. This test patches sys.modules to trigger the ImportError path in-process, covering lines 10-12 (_cli_entry.py).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Configure mypy in strict mode with the pydantic.mypy plugin, add types-requests stubs, and fix all type errors in CLI modules (return annotations, type narrowing, generic dict params). Add mypy pre-commit hook using local uv run entry point.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/libtea/client.py:161
from_well_knowndocuments that it raisesTeaConnectionErrorwhen all candidates are unreachable andTeaServerErrorwhen all candidates return 5xx, but the implementation raises the last encountered exception (which could be either, even with mixed failures). Please either update the docstring to reflect this behavior, or aggregate the failures and raise a singleTeaDiscoveryErrorwith a summary (and optionally attach the last exception as the cause).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…, CI config - Treat 3xx as probe failure in probe_endpoint since get_json rejects redirects - Add try/finally to get_json to ensure response.close() on all paths - Aggregate failover errors into TeaDiscoveryError instead of re-raising the last exception (fixes inconsistent exception types on mixed failures) - Remove duplicate --cov flags from CI/PyPI workflows (pyproject.toml is the single source for coverage config) - Install CLI extra in PyPI release test job
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ability - Add try/finally to download_with_hashes to ensure response.close() after streaming completes (including early abort on max_download_bytes) - Switch all list fields in Pydantic models to tuple for true immutability (frozen=True only prevents reassignment, not in-place list mutation) - Update _cli_fmt.py helper signatures to accept Sequence instead of list - Update test assertions for empty tuple defaults
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Track redirect count separately instead of using the loop counter, so _MAX_DOWNLOAD_REDIRECTS redirects are actually allowed (previously the loop spent one iteration on the final non-redirect response, effectively allowing only N-1 redirects).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…-hop validation Changed fetch_well_known() from allow_redirects=True (which would let intermediate hops reach internal IPs before validation) to manual redirect following with _validate_download_url() check at each hop. Updated SSRF tests to use responses library redirect mocking instead of unittest.mock.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Wrap response handling in try/finally so the connection is released on success, HTTP errors, and JSON parse failures. Error messages now report current_url (the final URL after any redirects) instead of the original url, making redirect-chain failures easier to debug.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 50 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def inspect( | ||
| tei: str, | ||
| max_components: Annotated[ | ||
| int, typer.Option("--max-components", help="Maximum number of components to fetch per release") |
There was a problem hiding this comment.
--max-components accepts any int, so negative values lead to surprising slicing behavior (e.g., components[: -1]) and confusing truncation logic. Add validation to reject values < 1 (e.g., enforce a minimum in the Typer option or call _error when max_components < 1).
| int, typer.Option("--max-components", help="Maximum number of components to fetch per release") | |
| int, | |
| typer.Option( | |
| "--max-components", | |
| min=1, | |
| help="Maximum number of components to fetch per release", | |
| ), |
| def fmt_inspect(data: list[dict[str, Any]], *, console: Console) -> None: | ||
| """Render the full inspect output (discovery + release + components).""" | ||
| for entry in data: | ||
| pr = entry["productRelease"] | ||
| fields = [ |
There was a problem hiding this comment.
fmt_inspect claims to render “discovery + release + components”, but it never reads/prints the entry['discovery'] data (server URLs / versions). If the intent is that inspect output includes discovery servers (per PR description / docs), add a section (or fields in the panel) that renders the discovery servers in rich mode as well.
Summary
TeaClientnow acceptsbasic_authandmtlsparameters with HTTPS enforcementmax_retriesandbackoff_factoron all HTTP requests; server-controlledRetry-Afterdisabled to prevent stallingget_product_cle,get_product_release_cle,get_component_cle,get_component_release_cle) with full Pydantic v2 modelspip install libtea[cli]) with 9 commands:discover,search-products,search-releases,get-product,get-release,get-collection,get-artifact,download,inspect--auth USER:PASS,--client-cert,--client-key,--ca-bundleon all commands--max-componentsflag (default 50), discovery servers included in output_SemVerwithsemver.Versionlibrarytea-cliprints helpful error when typer not installed instead of crashingTest plan
--domaindiscovery path tested with HTTP and HTTPS--authsends Basic header; invalid format rejected--client-cert/--client-keymutual requirement enforced--max-componentstruncation with warning verified