Skip to content

Fix high and medium scan safety issues - #2

Merged
Phantom9869 merged 4 commits into
mainfrom
cursor/security-review-fixes-9f2b
Oct 21, 2026
Merged

Phantom9869 merged 4 commits into
mainfrom
cursor/security-review-fixes-9f2b

Conversation

@Phantom9869

Copy link
Copy Markdown
Owner

Fixes the High and Medium findings from the security review. Low findings are unchanged. python -m pytest -q — 155 passed.

Fixes

High

  • Cross-host redirects. gambit.http.request no longer uses allow_redirects=True. A redirect is followed only when the next URL is http/https, the hostname matches, and every resolved address is globally routable (ipaddress.is_global). Chains stop at 5 hops. A followed redirect records the final URL on the finding. The first request to a user-specified private target is still allowed.
    • Tests: test_cross_host_redirect_is_not_followed, test_same_host_redirect_to_a_private_address_is_not_followed, test_same_host_redirect_followed_when_address_is_public, test_redirect_chain_is_capped, test_run_records_final_url_after_a_same_host_redirect.

Medium

  • Report injection. Text reports collapse control characters so evidence cannot forge a new heading. Markdown additionally HTML-escapes &, <, > and backslash-escapes markdown specials. JSON stays raw.

    • Test: test_text_and_markdown_strip_injected_markup_json_keeps_it.
  • TLS re-check dropping the cert. After CERT_NONE, an empty getpeercert() is recovered from getpeercert(binary_form=True) via a temporary PEM and ssl._ssl._test_decode_cert, so expiry is still graded.

    • Tests: test_getpeercert_empty_dict_is_recovered_from_der, test_handshake_reads_binary_cert_when_getpeercert_returns_empty.
  • IPv6 host:port parsing. Bare IPv6 is bracketed in normalize_host. split_host_port returns the unbracketed address plus an in-range port. URLs re-bracket only real IPv6 literals. example.com:notaport is unchanged.

    • Tests: test_split_host_port_ipv6_and_out_of_range_ports, test_ipv6_literal_is_bracketed_in_the_url.
  • Preflight port vs URL port. An open non-default port (8443, 8080, …) is remembered and used on later URLs. 443 and 80 stay scheme defaults. An explicit port on the target always wins.

    • Test: test_preflight_open_alt_port_is_used_in_the_url.
  • Explicit port sent to DNS, and port wrap. DNS, crt.sh, and CNAME queries use the hostname with the port stripped. Ports outside 1–65535 are rejected before getaddrinfo can wrap them (99999 → 34463). A valid explicit port is probed in addition to the standard list.

    • Tests: test_dns_and_crtsh_drop_an_explicit_port, test_out_of_range_port_does_not_connect, test_explicit_port_is_probed_and_out_of_range_port_is_not, test_connect_refuses_a_port_that_would_wrap.
  • Unbounded response bodies. Non-streaming responses are read through iter_content and capped at 1 MiB (max_bytes for callers that want less). Caller stream=True is left unread. crt.sh fetches do not follow redirects.

    • Test: test_response_body_is_capped.
  • Takeover fingerprint false positives. A fingerprint match counts only when status is missing or ≥ 400, so a live HTTP 200 is not HIGH. NXDOMAIN is HIGH only for services marked claimable. Generic IIS, GitLab, and WordPress strings are no longer fingerprints. Existing GitHub NXDOMAIN and “status omitted” cases stay HIGH.

    • Tests: test_fingerprint_on_http_200_is_not_high_and_404_still_is, test_unclaimable_nxdomain_and_generic_iis_text_are_not_high.
  • Dependency pins and pip-audit. pyproject.toml and requirements.txt pin dnspython==2.8.0, requests==2.34.2, and urllib3==2.8.0. CI installs pip-audit and audits requirements.txt.

    • Test: test_runtime_dependencies_are_pinned_and_ci_audits_them.
  • Socket connect DNS timeouts. gambit.net.connect resolves with dnspython lifetime and dials the IP, so socket.create_connection does not call getaddrinfo on a hostname before the timeout starts. Used by the liveness preflight, the port scan, and the TLS handshake.

    • Tests: test_connect_resolves_with_a_deadline_then_dials_the_ip, test_public_address_check_rejects_metadata_and_private_ranges.
Open in Web Open in Cursor 

cursoragent and others added 2 commits October 21, 2026 06:32
Stop cross-host and private redirects, cap response bodies, recover
unverified TLS certificates, and parse IPv6 and explicit ports before
DNS or socket connect. Pin runtime dependencies and audit them in CI.

Co-authored-by: Kamal Choubey <Phantom9869@users.noreply.github.com>
Validate each redirect against the URL requests will connect to, dial
the addresses that check vetted, resolve names with the system resolver
under one deadline, and follow same-host redirects for private literals.

Co-authored-by: Kamal Choubey <Phantom9869@users.noreply.github.com>
cursoragent and others added 2 commits October 21, 2026 07:32
The pinned session kept trust_env off, so REQUESTS_CA_BUNDLE was ignored
and a certificate failure was retried as plain HTTP. Apply the env CA
bundle explicitly, and surface TLS verification errors instead of
downgrading. Follow same-host redirects for names that resolve only to
non-public addresses, still blocking a public-to-private pivot. Check
proxies per URL, limit redirect ports to the same port or 80/443, return
the 3xx when a followed hop fails, and resolve a port scan's host once.

Co-authored-by: Kamal Choubey <Phantom9869@users.noreply.github.com>
Fix redirect SSRF parsing, DNS rebinding, and private-target redirects
@Phantom9869
Phantom9869 marked this pull request as ready for review October 21, 2026 07:37
@Phantom9869
Phantom9869 merged commit a8e4529 into main Oct 21, 2026
8 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