Skip to content

feat: TEA client v0.2.0 — auth, CLE, CLI, and security hardening#4

Merged
vpetersson merged 60 commits intosbomify:masterfrom
aurangzaib048:feat/tea-client-v0.2.0
Mar 2, 2026
Merged

feat: TEA client v0.2.0 — auth, CLE, CLI, and security hardening#4
vpetersson merged 60 commits intosbomify:masterfrom
aurangzaib048:feat/tea-client-v0.2.0

Conversation

@aurangzaib048
Copy link
Copy Markdown
Collaborator

@aurangzaib048 aurangzaib048 commented Feb 26, 2026

Summary

  • Basic auth: TeaClient now accepts basic_auth parameter with HTTPS enforcement
  • Retry with backoff: Configurable max_retries and backoff_factor on all HTTP requests; server-controlled Retry-After disabled to prevent stalling
  • CLE endpoints: 4 new methods (get_product_cle, get_product_release_cle, get_component_cle, get_component_release_cle) with full Pydantic v2 models
  • tea-cli: Click-based CLI (pip 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
  • CLI auth options: --token (bearer), --auth USER:PASS (basic) on all commands via shared_options decorator
  • SSRF protection: Download URLs and discovery redirects block private IPs, loopback, link-local, CGNAT, unspecified, multicast, and localhost with per-hop validation
  • HTTPS downgrade protection: Discovery redirects from HTTPS to HTTP are blocked (not just warned)
  • Inspect improvements: --max-components flag (default 50), discovery servers included in output, stderr warning on component resolution failure
  • SemVer migration: Replaced custom _SemVer with semver.Version library
  • Graceful CLI entry point: tea-cli prints helpful error when click not installed instead of crashing; SIGPIPE handled cleanly
  • Immutable models: All list fields changed to tuples for true frozen-model immutability
  • Response lifecycle: All HTTP responses closed on every code path via try/finally
  • Endpoint probe: Trailing-slash redirects (Django APPEND_SLASH, Caddy) allowed; cross-path/cross-origin rejected
  • Input validation: --timeout uses FloatRange(min=0.1), --max-download-bytes uses IntRange(min=1), --entity uses click.Choice
  • Lazy version: click.version_option(package_name="libtea") avoids import-time evaluation

Test plan

  • 513 tests passing with 98-99% branch coverage
  • All SSRF protection URLs rejected (9 parametrized cases)
  • Discovery redirect SSRF validated at each hop (scheme, internal IP, localhost)
  • HTTPS-to-HTTP downgrade blocked in discovery redirects
  • Trailing-slash redirects allowed in endpoint probe; cross-path/cross-origin rejected
  • Retry-After header disabled verified via adapter config
  • CLI --domain discovery path tested with HTTP and HTTPS
  • CLI --auth sends Basic header; invalid format rejected
  • Inspect --max-components truncation with warning verified
  • Entry point wrapper importable and pyproject.toml verified
  • mypy strict mode clean, ruff lint and format clean
  • Rich markup escaping verified for all server-controlled strings

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-known discovery.
  • 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.

Comment thread src/libtea/_cli_entry.py
Comment thread src/libtea/models.py
Comment thread libtea/models.py Outdated
Comment thread libtea/_http.py Outdated
Comment thread libtea/cli.py Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (mtls parameter with HTTPS enforcement), but TeaClient.__init__/TeaHttpClient.__init__ currently expose only token and basic_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.

Comment thread src/libtea/cli.py
Comment thread src/libtea/cli.py
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).
@aurangzaib048 aurangzaib048 requested a review from Copilot March 2, 2026 13:25
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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread tests/unit/test_cli_entry.py Outdated
Comment thread src/libtea/cli.py
Comment thread src/libtea/cli.py Outdated
Comment thread src/libtea/_cli_entry.py Outdated
- 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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/libtea/_http.py Outdated
Comment thread src/libtea/cli.py Outdated
Comment thread src/libtea/_http.py
…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
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread README.md
Comment thread src/libtea/_http.py Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/libtea/_http.py
Comment thread src/libtea/_http.py
@aurangzaib048 aurangzaib048 changed the title feat: TEA client v0.2.0 — auth, mTLS, CLE, CLI, and security hardening feat: TEA client v0.2.0 — auth, CLE, CLI, and security hardening Mar 2, 2026
@aurangzaib048 aurangzaib048 requested a review from Copilot March 2, 2026 15:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/libtea/_http.py
…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"
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@vpetersson vpetersson merged commit 39c74a2 into sbomify:master Mar 2, 2026
9 checks passed
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.

4 participants