Skip to content

[py] Add APIRequestContext for HTTP requests with browser cookie sync#17226

Closed
mayank-at-sauce wants to merge 1 commit intoSeleniumHQ:trunkfrom
mayank-at-sauce:mayankbhandari/feature/add-apirequestcontext
Closed

[py] Add APIRequestContext for HTTP requests with browser cookie sync#17226
mayank-at-sauce wants to merge 1 commit intoSeleniumHQ:trunkfrom
mayank-at-sauce:mayankbhandari/feature/add-apirequestcontext

Conversation

@mayank-at-sauce
Copy link

@mayank-at-sauce mayank-at-sauce commented Mar 14, 2026

Summary

  • Adds a driver.request API that lets Selenium users make HTTP requests with automatic browser cookie synchronization — bridging the biggest feature gap with Playwright's APIRequestContext
  • Pure Python HTTP client using urllib3 (already a Selenium dep), no BiDi/WebSocket dependency
  • Includes 147 unit tests (no browser) + 27 E2E browser tests — all passing

Motivation

Selenium currently has no way to make HTTP API calls that share authentication/cookie state with the browser session. Common real-world scenarios like API-based login, test data seeding, and hybrid API+UI testing require manual cookie extraction and format conversion. This PR provides a clean, built-in, first-class API.

Key features

  • Bidirectional cookie sync: browser cookies automatically sent with API requests (driver.get_cookies()), API response Set-Cookie headers synced back to browser (driver.add_cookie())
  • All HTTP methods: get(), post(), put(), patch(), delete(), head(), fetch()
  • Common kwargs: data, form, json_data, headers, params, timeout, max_redirects, fail_on_status_code
  • Isolated contexts: driver.request.new_context() creates a separate cookie jar that doesn't affect the browser
  • Auth state persistence: storage_state(path="auth.json") saves cookies to disk, new_context(storage_state="auth.json") restores them
  • Resource cleanup: pool manager disposed on driver.quit()

Usage examples

# 1. API calls with automatic cookie sync
response = driver.request.get("https://api.example.com/users")
assert response.ok
data = response.json()

# 2. API login — cookies auto-available in browser
driver.request.post("https://example.com/api/login",
    form={"username": "testuser", "password": "testpass"})
driver.get("https://example.com/dashboard")  # already authenticated

# 3. Isolated context (separate cookie jar)
api = driver.request.new_context(base_url="https://api.example.com")
api.post("/seed-data", json_data={"item": "test"})
api.dispose()

# 4. Save/load auth state across tests
driver.request.storage_state(path="auth.json")
ctx = driver.request.new_context(storage_state="auth.json")

Files changed (5)

File Change
py/selenium/webdriver/common/api_request_context.py New — Core module: APIResponse, APIRequestContext, _IsolatedAPIRequestContext, cookie matching, Set-Cookie parsing
py/selenium/webdriver/remote/webdriver.py Modified — Import, _request instance var, lazy request property, cleanup in quit()
py/test/selenium/webdriver/common/webserver.py Modified — 5 test endpoints: echo_headers, echo_json, set_cookie, echo_body, do_HEAD
py/test/selenium/webdriver/common/api_request_context_tests.py New — 27 E2E browser tests
py/test/unit/selenium/webdriver/common/api_request_context_tests.py New — 147 unit tests

Test plan

  • 147 unit tests pass (no browser required, 0.6s)
  • 27 E2E tests pass with headless Chrome (1.0s)
  • Run via Bazel: bazel test //py:unit and bazel test //py:test-chrome
  • Verify no regression on existing tests

Add a `driver.request` API that lets Selenium users make HTTP requests
with automatic browser cookie synchronization — bridging the biggest
feature gap with Playwright's APIRequestContext.

Key features:
- Bidirectional cookie sync: browser cookies sent with API requests,
  API response cookies synced back to the browser
- Isolated contexts via `new_context()` with separate cookie jars
- Auth state persistence via `storage_state()` save/load to JSON
- All HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, plus `fetch()`
- Common kwargs: data, form, json_data, headers, params, timeout,
  max_redirects, fail_on_status_code
- Pure Python HTTP client using urllib3 (already a Selenium dep),
  no BiDi/WebSocket dependency
- Resource cleanup on `driver.quit()`

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@selenium-ci selenium-ci added the C-py Python Bindings label Mar 14, 2026
@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Add APIRequestContext for HTTP requests with browser cookie sync

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds driver.request API for HTTP requests with automatic browser cookie synchronization
• Implements bidirectional cookie sync between browser and API requests
• Supports isolated contexts with separate cookie jars via new_context()
• Includes auth state persistence via storage_state() save/load to JSON
• Provides all HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, fetch) with common kwargs

Grey Divider

File Changes

1. py/selenium/webdriver/common/api_request_context.py ✨ Enhancement +612/-0

Core APIRequestContext implementation with cookie sync

• New core module implementing APIResponse, APIRequestContext, and _IsolatedAPIRequestContext
 classes
• Implements RFC 6265 cookie matching logic for domain, path, and secure attributes
• Provides Set-Cookie header parsing and bidirectional cookie synchronization
• Includes APIRequestFailure exception for non-2xx status codes
• Pure Python HTTP client using urllib3 with support for all HTTP methods

py/selenium/webdriver/common/api_request_context.py


2. py/selenium/webdriver/remote/webdriver.py ✨ Enhancement +23/-0

Add request property and lifecycle management

• Imports APIRequestContext from common module
• Adds _request instance variable initialized to None
• Implements lazy-loading request property that creates APIRequestContext on first access
• Disposes request context in quit() method for resource cleanup

py/selenium/webdriver/remote/webdriver.py


3. py/test/selenium/webdriver/common/webserver.py 🧪 Tests +48/-1

Add test endpoints for API request testing

• Adds echo_headers endpoint to echo request headers for testing
• Adds echo_json endpoint returning JSON response
• Adds set_cookie endpoint with query parameters for cookie testing
• Adds echo_body endpoint for POST request body testing
• Implements do_HEAD method handler for HEAD requests

py/test/selenium/webdriver/common/webserver.py


View more (2)
4. py/test/selenium/webdriver/common/api_request_context_tests.py 🧪 Tests +225/-0

E2E browser tests for APIRequestContext

• 27 end-to-end browser tests covering request methods, cookie sync, and response handling
• Tests bidirectional cookie synchronization between browser and API
• Tests isolated contexts, storage state export/import, and fail_on_status_code behavior
• Tests response methods (json, text, body, dispose) and custom headers

py/test/selenium/webdriver/common/api_request_context_tests.py


5. py/test/unit/selenium/webdriver/common/api_request_context_tests.py 🧪 Tests +1173/-0

Comprehensive unit tests for APIRequestContext

• 147 comprehensive unit tests covering all internal functions and classes
• Tests _cookie_matches() with RFC 6265 domain/path/secure matching edge cases
• Tests _parse_set_cookie() header parsing with various attribute combinations
• Tests APIResponse, APIRequestFailure, _BaseRequestContext, and _IsolatedAPIRequestContext
• End-to-end tests with local HTTP server for full request lifecycle validation

py/test/unit/selenium/webdriver/common/api_request_context_tests.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 14, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (3) 📎 Requirement gaps (0)

Grey Divider


Action required

1. driver.request lacks parity note 📘 Rule violation ⛯ Reliability
Description
A new user-visible WebDriver.request API is introduced without any in-code reference to
cross-binding parity verification or a tracked follow-up for other Selenium bindings. This risks
inconsistent behavior across language bindings and makes parity work easy to miss.
Code

py/selenium/webdriver/remote/webdriver.py[R1323-1339]

+    @property
+    def request(self) -> APIRequestContext:
+        """Returns an APIRequestContext for making HTTP requests with browser cookie sync.
+
+        Returns:
+            An APIRequestContext instance bound to this driver.
+
+        Examples:
+            ```
+            response = driver.request.get("https://api.example.com/data")
+            assert response.ok
+            data = response.json()
+            ```
+        """
+        if self._request is None:
+            self._request = APIRequestContext(self)
+        return self._request
Evidence
PR Compliance ID 4 requires that user-visible behavior changes be compared against at least one
other binding and/or parity work be tracked. The PR adds a new request property to WebDriver but
the change itself contains no note/link indicating cross-binding comparison or a parity tracking
item.

AGENTS.md
py/selenium/webdriver/remote/webdriver.py[1323-1339]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new user-visible API (`WebDriver.request`) is added, but there is no evidence in the change itself that cross-binding parity was assessed or that parity follow-up work is tracked.

## Issue Context
Compliance requires user-visible behavior changes to be compared against at least one other Selenium binding and/or have a parity follow-up tracked.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[1323-1339]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. storage_state path unvalidated 📘 Rule violation ⛯ Reliability
Description
User-provided file paths for storage_state import/export are used directly without fail-fast
validation or actionable errors (e.g., missing file, invalid JSON, unwritable path). This can
produce unclear downstream exceptions and makes troubleshooting harder.
Code

py/selenium/webdriver/common/api_request_context.py[R504-535]

+        if storage_state is not None:
+            if isinstance(storage_state, (str, pathlib.Path)):
+                with open(storage_state) as f:
+                    state = json.load(f)
+            else:
+                state = storage_state
+            cookies = list(state.get("cookies", []))
+
+        return _IsolatedAPIRequestContext(
+            base_url=base_url,
+            extra_headers=extra_headers,
+            cookies=cookies,
+            timeout=self._timeout,
+            max_redirects=self._max_redirects,
+            fail_on_status_code=fail_on_status_code,
+        )
+
+    def storage_state(self, path: str | pathlib.Path | None = None) -> dict[str, Any]:
+        """Export the current browser cookies as a storage state dict.
+
+        Args:
+            path: Optional file path to save the storage state as JSON.
+
+        Returns:
+            A dict with a "cookies" key containing the browser cookies.
+        """
+        cookies = self._driver.get_cookies()
+        state: dict[str, Any] = {"cookies": cookies}
+        if path is not None:
+            with open(path, "w") as f:
+                json.dump(state, f, indent=2)
+        return state
Evidence
PR Compliance ID 10 requires validating required inputs early and failing fast with actionable
messages. The new code calls open(storage_state) / json.load() and writes with open(path, "w")
without pre-checks or wrapping exceptions to provide clear, actionable errors.

py/selenium/webdriver/common/api_request_context.py[504-511]
py/selenium/webdriver/common/api_request_context.py[530-535]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`storage_state` load/save uses user-provided paths without validation and can raise low-level exceptions (e.g., `FileNotFoundError`, `PermissionError`, `JSONDecodeError`) that are not actionable.

## Issue Context
Compliance requires fail-fast validation with clear error messages for required/user-provided inputs.

## Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[504-511]
- py/selenium/webdriver/common/api_request_context.py[530-535]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Host-only cookie overmatch 🐞 Bug ⛨ Security
Description
_cookie_matches treats cookies with missing/empty 'domain' as matching any hostname, so such cookies
can be attached to requests for unrelated domains. This violates the intended “host-only cookie”
behavior and can leak session cookies outside their origin host via driver.request.
Code

py/selenium/webdriver/common/api_request_context.py[R117-125]

+    # Domain matching
+    cookie_domain = cookie.get("domain", "")
+    if not cookie_domain:
+        # No domain set — treat as host-only, match any host
+        pass
+    elif cookie_domain.startswith("."):
+        # .example.com matches example.com and sub.example.com
+        if not (hostname == cookie_domain[1:] or hostname.endswith(cookie_domain)):
+            return False
Evidence
The new cookie matcher explicitly allows empty/missing cookie domains to pass domain checks, and
_fetch then unconditionally attaches all “matching” cookies to outgoing requests.
WebDriver.add_cookie documents that 'domain' is optional, so cookies can exist without it, and the
PR’s own unit tests assert that missing/empty domain matches any host—demonstrating the behavior
that causes the overmatch.

py/selenium/webdriver/common/api_request_context.py[112-125]
py/selenium/webdriver/common/api_request_context.py[425-433]
py/selenium/webdriver/remote/webdriver.py[701-705]
py/test/unit/selenium/webdriver/common/api_request_context_tests.py[196-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`_cookie_matches()` currently treats cookies with missing/empty `domain` as matching **any** request hostname, which can cause host-only cookies to be sent to unrelated domains via `driver.request`.

### Issue Context
Host-only cookies (no Domain attribute) must only match the origin host, not arbitrary hosts. In the WebDriver cookie dict, `domain` is optional, so this case must be handled safely.

### Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[100-141]
- py/selenium/webdriver/common/api_request_context.py[537-545]

### Implementation notes
- Prefer changing `_cookie_matches(cookie, url, default_host=None)` so that if `cookie` has no domain, it only matches when `hostname == default_host`.
- In `APIRequestContext._get_cookies_for_request`, compute `default_host` from `urlparse(self._driver.current_url).hostname` (guard exceptions/empty current_url). If unavailable, skip cookies missing `domain`.
- Consider normalizing any cookie dicts returned by the driver by filling in missing `domain` with the current host before matching.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Expired cookies still sent 🐞 Bug ✓ Correctness
Description
The isolated cookie jar never removes or filters cookies by 'expiry', so cookies set with
Max-Age=0/negative (deletion) or past Expires remain stored and will still be sent on subsequent
requests. This breaks cookie lifecycle semantics and can keep stale authentication state alive in
isolated contexts.
Code

py/selenium/webdriver/common/api_request_context.py[R593-612]

+    def _get_cookies_for_request(self, url: str) -> list[dict]:
+        """Get matching cookies from the internal jar."""
+        return [c for c in self._cookies if _cookie_matches(c, url)]
+
+    def _handle_response_cookies(self, set_cookie_headers: list[str], url: str) -> None:
+        """Store Set-Cookie headers in the internal jar."""
+        parsed_url = urllib.parse.urlparse(url)
+        for sc_header in set_cookie_headers:
+            cookie = _parse_set_cookie(sc_header)
+            if not cookie.get("name"):
+                continue
+            cookie.setdefault("domain", parsed_url.hostname or "")
+            cookie.setdefault("path", "/")
+            # Cookies are unique by (name, domain, path)
+            key = (cookie["name"], cookie.get("domain", ""), cookie.get("path", "/"))
+            self._cookies = [
+                c for c in self._cookies
+                if (c.get("name"), c.get("domain", ""), c.get("path", "/")) != key
+            ]
+            self._cookies.append(cookie)
Evidence
Set-Cookie parsing stores an 'expiry' timestamp (including timestamps in the past for Max-Age=0),
but the cookie matching logic ignores 'expiry' completely. The isolated context appends parsed
cookies to its jar and later sends them solely based on _cookie_matches, so expired/deleted
cookies continue to match and be included in request Cookie headers.

py/selenium/webdriver/common/api_request_context.py[188-192]
py/test/unit/selenium/webdriver/common/api_request_context_tests.py[355-357]
py/selenium/webdriver/common/api_request_context.py[100-141]
py/selenium/webdriver/common/api_request_context.py[593-595]
py/selenium/webdriver/common/api_request_context.py[597-612]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Expired/deleted cookies are never filtered or removed, so isolated contexts can keep sending cookies whose `expiry` is in the past (e.g., `Max-Age=0`).

### Issue Context
`_parse_set_cookie` already computes `expiry`, but `_cookie_matches` ignores it and `_IsolatedAPIRequestContext` always stores the cookie.

### Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[100-141]
- py/selenium/webdriver/common/api_request_context.py[593-612]
- py/selenium/webdriver/common/api_request_context.py[144-201]

### Implementation notes
- Add an expiry check to `_cookie_matches`: if `expiry` exists and `expiry &lt;= int(time.time())`, return `False`.
- In `_IsolatedAPIRequestContext._handle_response_cookies`, if parsed cookie has `expiry &lt;= now`, remove any existing cookie with the same (name, domain, path) key and **do not append**.
- Consider doing the same for `APIRequestContext` browser-sync path where feasible (e.g., attempt `driver.delete_cookie(cookie[&#x27;name&#x27;])` when `expiry` is in the past), but at minimum ensure isolated contexts behave correctly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Redundant cookie workflow comments 📘 Rule violation ✓ Correctness
Description
Several new comments restate obvious behavior (e.g., applying/processing cookies) rather than
explaining rationale. This adds noise and reduces maintainability compared to self-explanatory
structure/naming.
Code

py/selenium/webdriver/common/api_request_context.py[R425-443]

+        # Apply cookies
+        matching_cookies = self._get_cookies_for_request(url)
+        if matching_cookies:
+            cookie_header = "; ".join(f"{c['name']}={c['value']}" for c in matching_cookies)
+            if "Cookie" in headers:
+                headers["Cookie"] = headers["Cookie"] + "; " + cookie_header
+            else:
+                headers["Cookie"] = cookie_header
+
+        body = self._prepare_body(headers, kwargs)
+        url = self._append_params(url, kwargs)
+        resp = self._execute_request(method, url, headers, body, kwargs)
+
+        # Process response cookies
+        set_cookie_headers = _get_set_cookie_headers(resp)
+        if set_cookie_headers:
+            self._handle_response_cookies(set_cookie_headers, url)
+
+        response = self._build_response(resp, url)
Evidence
PR Compliance ID 8 asks for comments to explain 'why' rather than narrate 'what'. The added `# Apply
cookies and # Process response cookies` comments are descriptive of the immediately-following code
and do not capture rationale/constraints.

AGENTS.md
py/selenium/webdriver/common/api_request_context.py[425-443]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Some new comments narrate what the code is doing instead of explaining why it is implemented that way.

## Issue Context
Prefer self-explanatory code and reserve comments for rationale, constraints, and non-obvious intent.

## Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[425-443]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. SameSite value not normalized 🐞 Bug ⛯ Reliability
Description
_parse_set_cookie stores SameSite values verbatim, but WebDriver.add_cookie asserts sameSite is
exactly one of {Strict, Lax, None}, so non-normalized values can trigger an AssertionError and
prevent that response cookie from syncing to the browser. The exception is caught and logged as a
domain mismatch, masking the true cause and silently dropping the cookie sync.
Code

py/selenium/webdriver/common/api_request_context.py[R186-187]

+        elif attr_name == "samesite":
+            cookie["sameSite"] = attr_value
Evidence
The Set-Cookie parser assigns sameSite directly from the header value without
normalization/validation. APIRequestContext then passes that cookie dict into WebDriver.add_cookie,
where Selenium enforces allowed SameSite values via an assert; failures are caught and only logged,
so the cookie will not be added to the browser.

py/selenium/webdriver/common/api_request_context.py[186-187]
py/selenium/webdriver/common/api_request_context.py[546-557]
py/selenium/webdriver/remote/webdriver.py[712-714]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`_parse_set_cookie()` stores `sameSite` verbatim, but `WebDriver.add_cookie()` asserts the value is exactly `Strict`, `Lax`, or `None`. This can break browser cookie sync for some Set-Cookie headers.

### Issue Context
`APIRequestContext._handle_response_cookies()` passes parsed cookies directly to `driver.add_cookie()`.

### Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[144-201]
- py/selenium/webdriver/remote/webdriver.py[712-716]
- py/selenium/webdriver/common/api_request_context.py[546-563]

### Implementation notes
- In `_parse_set_cookie`, normalize SameSite values (e.g., map case-insensitively to `Strict`/`Lax`/`None`).
- If the value is not recognized after normalization, do not set the `sameSite` key at all.
- Optionally improve the warning message in `_handle_response_cookies` to not hardcode “domain mismatch” for all exceptions (so SameSite/assert failures are diagnosable).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +1323 to +1339
@property
def request(self) -> APIRequestContext:
"""Returns an APIRequestContext for making HTTP requests with browser cookie sync.
Returns:
An APIRequestContext instance bound to this driver.
Examples:
```
response = driver.request.get("https://api.example.com/data")
assert response.ok
data = response.json()
```
"""
if self._request is None:
self._request = APIRequestContext(self)
return self._request
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. driver.request lacks parity note 📘 Rule violation ⛯ Reliability

A new user-visible WebDriver.request API is introduced without any in-code reference to
cross-binding parity verification or a tracked follow-up for other Selenium bindings. This risks
inconsistent behavior across language bindings and makes parity work easy to miss.
Agent Prompt
## Issue description
A new user-visible API (`WebDriver.request`) is added, but there is no evidence in the change itself that cross-binding parity was assessed or that parity follow-up work is tracked.

## Issue Context
Compliance requires user-visible behavior changes to be compared against at least one other Selenium binding and/or have a parity follow-up tracked.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[1323-1339]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +504 to +535
if storage_state is not None:
if isinstance(storage_state, (str, pathlib.Path)):
with open(storage_state) as f:
state = json.load(f)
else:
state = storage_state
cookies = list(state.get("cookies", []))

return _IsolatedAPIRequestContext(
base_url=base_url,
extra_headers=extra_headers,
cookies=cookies,
timeout=self._timeout,
max_redirects=self._max_redirects,
fail_on_status_code=fail_on_status_code,
)

def storage_state(self, path: str | pathlib.Path | None = None) -> dict[str, Any]:
"""Export the current browser cookies as a storage state dict.

Args:
path: Optional file path to save the storage state as JSON.

Returns:
A dict with a "cookies" key containing the browser cookies.
"""
cookies = self._driver.get_cookies()
state: dict[str, Any] = {"cookies": cookies}
if path is not None:
with open(path, "w") as f:
json.dump(state, f, indent=2)
return state
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

2. storage_state path unvalidated 📘 Rule violation ⛯ Reliability

User-provided file paths for storage_state import/export are used directly without fail-fast
validation or actionable errors (e.g., missing file, invalid JSON, unwritable path). This can
produce unclear downstream exceptions and makes troubleshooting harder.
Agent Prompt
## Issue description
`storage_state` load/save uses user-provided paths without validation and can raise low-level exceptions (e.g., `FileNotFoundError`, `PermissionError`, `JSONDecodeError`) that are not actionable.

## Issue Context
Compliance requires fail-fast validation with clear error messages for required/user-provided inputs.

## Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[504-511]
- py/selenium/webdriver/common/api_request_context.py[530-535]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +117 to +125
# Domain matching
cookie_domain = cookie.get("domain", "")
if not cookie_domain:
# No domain set — treat as host-only, match any host
pass
elif cookie_domain.startswith("."):
# .example.com matches example.com and sub.example.com
if not (hostname == cookie_domain[1:] or hostname.endswith(cookie_domain)):
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

3. Host-only cookie overmatch 🐞 Bug ⛨ Security

_cookie_matches treats cookies with missing/empty 'domain' as matching any hostname, so such cookies
can be attached to requests for unrelated domains. This violates the intended “host-only cookie”
behavior and can leak session cookies outside their origin host via driver.request.
Agent Prompt
### Issue description
`_cookie_matches()` currently treats cookies with missing/empty `domain` as matching **any** request hostname, which can cause host-only cookies to be sent to unrelated domains via `driver.request`.

### Issue Context
Host-only cookies (no Domain attribute) must only match the origin host, not arbitrary hosts. In the WebDriver cookie dict, `domain` is optional, so this case must be handled safely.

### Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[100-141]
- py/selenium/webdriver/common/api_request_context.py[537-545]

### Implementation notes
- Prefer changing `_cookie_matches(cookie, url, default_host=None)` so that if `cookie` has no domain, it only matches when `hostname == default_host`.
- In `APIRequestContext._get_cookies_for_request`, compute `default_host` from `urlparse(self._driver.current_url).hostname` (guard exceptions/empty current_url). If unavailable, skip cookies missing `domain`.
- Consider normalizing any cookie dicts returned by the driver by filling in missing `domain` with the current host before matching.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +593 to +612
def _get_cookies_for_request(self, url: str) -> list[dict]:
"""Get matching cookies from the internal jar."""
return [c for c in self._cookies if _cookie_matches(c, url)]

def _handle_response_cookies(self, set_cookie_headers: list[str], url: str) -> None:
"""Store Set-Cookie headers in the internal jar."""
parsed_url = urllib.parse.urlparse(url)
for sc_header in set_cookie_headers:
cookie = _parse_set_cookie(sc_header)
if not cookie.get("name"):
continue
cookie.setdefault("domain", parsed_url.hostname or "")
cookie.setdefault("path", "/")
# Cookies are unique by (name, domain, path)
key = (cookie["name"], cookie.get("domain", ""), cookie.get("path", "/"))
self._cookies = [
c for c in self._cookies
if (c.get("name"), c.get("domain", ""), c.get("path", "/")) != key
]
self._cookies.append(cookie)
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

4. Expired cookies still sent 🐞 Bug ✓ Correctness

The isolated cookie jar never removes or filters cookies by 'expiry', so cookies set with
Max-Age=0/negative (deletion) or past Expires remain stored and will still be sent on subsequent
requests. This breaks cookie lifecycle semantics and can keep stale authentication state alive in
isolated contexts.
Agent Prompt
### Issue description
Expired/deleted cookies are never filtered or removed, so isolated contexts can keep sending cookies whose `expiry` is in the past (e.g., `Max-Age=0`).

### Issue Context
`_parse_set_cookie` already computes `expiry`, but `_cookie_matches` ignores it and `_IsolatedAPIRequestContext` always stores the cookie.

### Fix Focus Areas
- py/selenium/webdriver/common/api_request_context.py[100-141]
- py/selenium/webdriver/common/api_request_context.py[593-612]
- py/selenium/webdriver/common/api_request_context.py[144-201]

### Implementation notes
- Add an expiry check to `_cookie_matches`: if `expiry` exists and `expiry <= int(time.time())`, return `False`.
- In `_IsolatedAPIRequestContext._handle_response_cookies`, if parsed cookie has `expiry <= now`, remove any existing cookie with the same (name, domain, path) key and **do not append**.
- Consider doing the same for `APIRequestContext` browser-sync path where feasible (e.g., attempt `driver.delete_cookie(cookie['name'])` when `expiry` is in the past), but at minimum ensure isolated contexts behave correctly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@mayank-at-sauce mayank-at-sauce deleted the mayankbhandari/feature/add-apirequestcontext branch March 14, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-py Python Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants