Skip to content

Tighten TypeSafe key host checks and disable SDK redirects - #17

Merged
jkbennitt merged 1 commit into
mainfrom
cursor/typesafe-key-port-and-sdk-cd5d
Nov 1, 2026
Merged

jkbennitt merged 1 commit into
mainfrom
cursor/typesafe-key-port-and-sdk-cd5d

Conversation

@jkbennitt

Copy link
Copy Markdown
Member

Summary

Follow-up to the merged key allowlist. The TypeSafe API key still only goes to api.typesafe.ai over HTTPS, and now only on port 443 with an exact host match. The SDK path is given an httpx2 client with redirects disabled.

This is not a major production PR. It changes tools/typesafe-pareto/ only. No PackageId, version, CHANGELOG, or NuGet release.

1. Port must be omitted or 443

_typesafe_key_host requires parsed.port in (None, 443). Non-numeric ports such as :foo make urlsplit(...).port raise ValueError (the same property urlparse uses). That error is caught and returned as TypeSafeConfigError, before a request is built.

Checked: test_explicit_port_443_ok_other_ports_and_trailing_dot_rejected accepts https://api.typesafe.ai:443 and rejects :8443 and :foo from resolve_base_url and from _http_system_one without constructing Request or an opener. test_redirect_to_non_443_port_is_refused scripts a 302 to https://api.typesafe.ai:8443/... and records Authorization only on the original https://api.typesafe.ai/v1/systemone request.

2. Trailing-dot hosts are rejected

The host comparison no longer calls rstrip("."). parsed.hostname keeps the trailing dot, so api.typesafe.ai. is not an exact allowlist match.

Checked: the same unit test rejects https://api.typesafe.ai. and https://API.typesafe.ai. with TypeSafeConfigError and does not open a request.

3. SDK redirect behavior

Read typesafe-sdk 0.7.5 (typesafe_sdk/_core/client/sync/client.py, typesafe_sdk/_core/transport.py) and httpx2 2.16.0 (httpx2/_client.py), then installed those versions and constructed a client locally. No request was sent.

  • TypeSafeClient builds httpx2.Client(timeout=..., limits=DEFAULT_LIMITS) when the caller does not pass http_client. It does not set follow_redirects. send() calls http_client.request(...) without that argument, so the client default applies.
  • httpx2.Client defaults follow_redirects to False. In that mode _send_handling_redirects stores next_request and returns the 3xx. The next request is not sent, so Authorization does not reach a second host.
  • If redirects were enabled, _redirect_headers drops Authorization when the origin changes, except _is_https_redirect (only http port 80 to https port 443 on the same host). A same-host scheme downgrade or a different port would keep the header.

The SDK exposes http_client, so _sdk_system_one now passes httpx2.Client(follow_redirects=False, timeout=timeout) and does not rely on the default. TypeSafeClient closes that client; close() is idempotent, and the finally block closes it again if construction fails first.

Checked: local constructor confirmed client._http_client.follow_redirects is False and that it is the same object passed in. test_sdk_path_disables_redirects mocks the seam (CI does not install the SDK) and asserts follow_redirects=False is what gets passed. The pinned client uses httpx2's default connection limits rather than the SDK's DEFAULT_LIMITS (100). system_one, retries, and the SDK's own Authorization header are unchanged.

4. Tests

  • HTTP downgrade (https to http on api.typesafe.ai) was already covered by test_https_downgrade_redirect_is_refused and still passes. The scripted 302 raises, and the bearer token is recorded only on the original HTTPS URL.
  • Explicit ports and the trailing-dot host are covered as in items 1 and 2.
  • No sockets: redirect tests install a scripted HTTPS handler and patch socket.create_connection / socket.getaddrinfo.

Full suite: python3 -m unittest tools/typesafe-pareto/test_score_pareto.py -v (25 tests) and python3 tools/typesafe-pareto/score_pareto.py --smoke --force-mock --no-metrics.

What stayed unchanged

Mock scoring, question shapes, metrics JSONL, and --force-mock for an allowlisted base URL. The urllib allowlist redirect handler still follows same-host HTTPS redirects on port 443. The SDK still calls TypeSafeClient.system_one after the shared host check.

Checklist

  • dotnet test Unsga3.slnx -c Release — not run; library code is unchanged
  • CHANGELOG.md — intentionally unchanged (tools script only, not a package release)
  • Public API docs — no NuGet / XML doc change
  • Survival / normalization / metrics — unchanged; no oracle re-run

Test plan

  • python3 -m unittest tools/typesafe-pareto/test_score_pareto.py -v
  • python3 tools/typesafe-pareto/score_pareto.py --smoke --force-mock --no-metrics
  • Local typesafe-sdk 0.7.5 / httpx2 2.16.0 constructor check, no request
Open in Web Open in Cursor 

Require port 443, reject trailing-dot hosts and non-numeric ports, and
pass the SDK an httpx2 client with follow_redirects=False.

Tools script only; not a package release.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
@jkbennitt
jkbennitt merged commit 478909d into main Nov 1, 2026
3 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.

2 participants