feat: TEA client v0.2.0 — auth, CLE, CLI, and security hardening#4
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.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 49 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/libtea/client.py:90
- The PR description mentions adding mTLS support (
mtlsparameter with HTTPS enforcement), butTeaClient.__init__/TeaHttpClient.__init__currently expose onlytokenandbasic_auth(no client cert/key/CA bundle wiring). Either implement the advertised mTLS parameters end-to-end (client surface + requests session cert/verify), or update the PR description/docs to remove the mTLS claim.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Servers like Django (APPEND_SLASH) and Caddy commonly return a 301 from /path to /path/. This is benign path normalization, not a security concern. The probe now resolves the Location header against the probed URL and permits the redirect when only a trailing slash differs. Cross-path and cross-origin redirects still fail the probe. Discovered while testing against a real TEA server (trust.local).
Replace the TeaInsecureTransportWarning with a hard TeaDiscoveryError when a discovery redirect downgrades from HTTPS to HTTP. Silent downgrades enable MITM of the discovery document. Users who intentionally need HTTP can pass scheme="http" / --use-http.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 49 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/libtea/client.py:90
- PR description claims TeaClient supports mTLS (and CLI flags like --client-cert/--client-key/--ca-bundle), but there is no
mtls/client cert/CA bundle support in the TeaClient constructor here (only token/basic_auth). Either implement the advertised mTLS parameters (and plumb them through TeaHttpClient/requests), or adjust the PR description and docs to remove the mTLS claims.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Make SSRF error messages URL-context-neutral (remove "Artifact download" prefix) so they read correctly when raised from discovery redirect validation - Guard lazy rich import in _output() with ImportError catch so CLI shows a helpful message when click is installed but rich is not - Updated PR description to reflect click migration and remove stale mTLS/typer references
The entry point guards import of libtea.cli which can fail if any CLI extra (click, rich) is missing — not just click specifically. Rename class/docstrings to reflect what is actually being tested.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 49 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ation - get_json: use stream=True and read at most limit+1 bytes so oversized responses never fully load into memory (prevents memory DoS when Content-Length is missing or wrong) - --debug: pass level=logging.DEBUG to basicConfig so the root logger actually emits debug messages - probe_endpoint: reject 3xx responses that lack a Location header instead of silently treating them as non-redirects
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 49 changed files in this pull request and generated 2 comments.
💡 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 39 out of 49 changed files in this pull request and generated 2 comments.
💡 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 39 out of 49 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…t empty auth username - Set response.raw.decode_content=True in get_json() and _raise_for_status() so gzip/deflate responses are decoded when using stream=True bounded reads - Replace unbounded response.json() in 404 branch with raw.read(1024) + json.loads() matching the bounded-read pattern used for other 4xx codes - probe_endpoint() now treats 404/410 as failures to trigger endpoint failover (wrong path/version); 401/403/405 still pass (server reachable) - _parse_basic_auth() rejects empty username in --auth ":password"
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 49 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.
Summary
TeaClientnow acceptsbasic_authparameter 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 14 commands:discover,search-products,search-releases,get-product,get-product-releases,get-release,get-component,get-component-releases,get-collection,list-collections,get-cle,get-artifact,download,inspect--token(bearer),--auth USER:PASS(basic) on all commands viashared_optionsdecorator--max-componentsflag (default 50), discovery servers included in output, stderr warning on component resolution failure_SemVerwithsemver.Versionlibrarytea-cliprints helpful error when click not installed instead of crashing; SIGPIPE handled cleanly--timeoutusesFloatRange(min=0.1),--max-download-bytesusesIntRange(min=1),--entityusesclick.Choiceclick.version_option(package_name="libtea")avoids import-time evaluationTest plan
--domaindiscovery path tested with HTTP and HTTPS--authsends Basic header; invalid format rejected--max-componentstruncation with warning verified