Skip to content

Commit 6fd8d51

Browse files
authored
Merge pull request #46 from HyperionGray/copilot/implement-additional-api-calls
Update CDP to latest specification with Privacy Sandbox and security APIs
2 parents 91216e9 + af11390 commit 6fd8d51

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+34056
-13400
lines changed

SECURITY_UPDATES.md

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Security-Relevant API Updates
2+
3+
This document highlights the security-relevant additions to the Chrome DevTools Protocol implementation in this update.
4+
5+
## Summary
6+
7+
This update brings the python-chrome-devtools-protocol library to the latest CDP specification, adding **8 new domains** and significantly expanding security-relevant APIs, particularly in the Privacy Sandbox area.
8+
9+
## New Security-Focused Domains
10+
11+
### 1. Extensions Domain
12+
**Purpose**: Browser extension management for security testing
13+
- Load and uninstall extensions programmatically
14+
- Manage extension storage (local/sync/managed)
15+
- **Use Case**: Test extension security boundaries, data isolation, and permission handling
16+
17+
### 2. FedCm Domain (Federated Credential Management)
18+
**Purpose**: Test federated authentication flows
19+
- Track and interact with FedCm dialogs
20+
- Programmatically select accounts or dismiss dialogs
21+
- **Use Case**: Verify federated login security, test account selection flows
22+
23+
### 3. DeviceAccess Domain
24+
**Purpose**: Handle device permission prompts
25+
- Track camera, microphone, and other device access requests
26+
- Programmatically grant or deny permissions
27+
- **Use Case**: Test device permission security, verify proper permission prompts
28+
29+
### 4. FileSystem Domain
30+
**Purpose**: File system directory access
31+
- Get directory access for testing File System Access API
32+
- **Use Case**: Test file system permission boundaries
33+
34+
### 5. Autofill, BluetoothEmulation, PWA, Preload Domains
35+
Additional domains for comprehensive browser testing
36+
37+
## Major Security Updates to Existing Domains
38+
39+
### Storage Domain - Privacy Sandbox APIs
40+
The Storage domain received the most significant security-relevant updates:
41+
42+
#### Attribution Reporting API (Privacy-Preserving Ad Measurement)
43+
```python
44+
# Enable tracking and local testing
45+
await conn.execute(storage.set_attribution_reporting_tracking(enable=True))
46+
await conn.execute(storage.set_attribution_reporting_local_testing_mode(enabled=True))
47+
48+
# Send test reports
49+
await conn.execute(storage.send_pending_attribution_reports())
50+
51+
# Listen for events
52+
async for event in conn.listen():
53+
if isinstance(event, storage.AttributionReportingSourceRegistered):
54+
print(f"Source registered: {event.registration}")
55+
```
56+
57+
#### Shared Storage API (Cross-Site Storage with Privacy)
58+
```python
59+
# Track shared storage access
60+
await conn.execute(storage.set_shared_storage_tracking(enable=True))
61+
62+
# Get and set entries for testing
63+
metadata = await conn.execute(storage.get_shared_storage_metadata(
64+
owner_origin="https://example.com"
65+
))
66+
67+
await conn.execute(storage.set_shared_storage_entry(
68+
owner_origin="https://example.com",
69+
key="test-key",
70+
value="test-value"
71+
))
72+
```
73+
74+
#### Interest Groups / FLEDGE / Protected Audience API
75+
```python
76+
# Track interest group auctions
77+
await conn.execute(storage.set_interest_group_tracking(enable=True))
78+
await conn.execute(storage.set_interest_group_auction_tracking(enable=True))
79+
80+
# Get details for security verification
81+
details = await conn.execute(storage.get_interest_group_details(
82+
owner_origin="https://example.com",
83+
name="interest-group-name"
84+
))
85+
86+
# Configure k-anonymity for testing
87+
await conn.execute(storage.set_protected_audience_k_anonymity(threshold=50))
88+
```
89+
90+
#### Bounce Tracking Mitigation
91+
```python
92+
# Test bounce tracking mitigation
93+
deleted_sites = await conn.execute(storage.run_bounce_tracking_mitigations())
94+
print(f"Mitigated tracking for {len(deleted_sites)} sites")
95+
```
96+
97+
### Network Domain - Cookie and IP Protection
98+
```python
99+
# Control cookie behavior for third-party cookie testing
100+
await conn.execute(network.set_cookie_controls(mode='block-third-party'))
101+
102+
# Test IP protection features
103+
status = await conn.execute(network.get_ip_protection_proxy_status())
104+
await conn.execute(network.set_ip_protection_proxy_bypass_enabled(enabled=True))
105+
106+
# Get related website sets (First-Party Sets)
107+
sets = await conn.execute(storage.get_related_website_sets())
108+
```
109+
110+
### Audits Domain - Form Security
111+
```python
112+
# Automated form security/privacy issue detection
113+
issues = await conn.execute(audits.check_forms_issues())
114+
for issue in issues:
115+
print(f"Form issue detected: {issue}")
116+
```
117+
118+
### Browser Domain - Privacy Sandbox Configuration
119+
```python
120+
# Override Privacy Sandbox enrollment for testing
121+
await conn.execute(browser.add_privacy_sandbox_enrollment_override(
122+
url="https://example.com"
123+
))
124+
125+
# Configure coordinator keys
126+
await conn.execute(browser.add_privacy_sandbox_coordinator_key_config(
127+
coordinator_origin="https://coordinator.example.com",
128+
coordinator_key="test-key"
129+
))
130+
```
131+
132+
## Security Testing Use Cases
133+
134+
### 1. Privacy Sandbox Testing
135+
Test the complete Privacy Sandbox suite:
136+
- Attribution Reporting (privacy-preserving conversion measurement)
137+
- Shared Storage (cross-site storage with privacy guarantees)
138+
- Interest Groups/FLEDGE (privacy-preserving ad auctions)
139+
- Topics API (via interest groups)
140+
- k-anonymity thresholds
141+
142+
### 2. Third-Party Cookie Migration
143+
Test alternatives to third-party cookies:
144+
- First-Party Sets (Related Website Sets)
145+
- Partitioned cookies (CHIPS)
146+
- Storage Access API
147+
- Cookie controls and policies
148+
149+
### 3. Authentication Security
150+
- Test FedCm federated login flows
151+
- Verify account selection security
152+
- Test dialog dismissal handling
153+
154+
### 4. Permission Testing
155+
- Verify device permission prompts (camera, mic, etc.)
156+
- Test permission grant/deny flows
157+
- Validate permission persistence
158+
159+
### 5. Extension Security
160+
- Test extension isolation boundaries
161+
- Verify extension data access controls
162+
- Test extension installation/uninstallation
163+
164+
### 6. Anti-Tracking Features
165+
- Test bounce tracking mitigation
166+
- Verify IP protection
167+
- Test tracking prevention measures
168+
169+
### 7. Form Security Auditing
170+
- Automated detection of insecure forms
171+
- Privacy leak detection
172+
- Input validation issues
173+
174+
## Breaking Changes
175+
176+
**Database Domain Removed**: The deprecated Database domain has been removed from the CDP specification. If your code imports `cdp.database`, you must migrate to:
177+
- IndexedDB APIs (`cdp.indexed_db`)
178+
- Storage APIs (`cdp.storage`)
179+
- Cache Storage APIs (`cdp.cache_storage`)
180+
181+
## Implementation Notes
182+
183+
### Generator Improvements
184+
- Fixed same-domain type reference bug (e.g., `Network.TimeSinceEpoch` now correctly resolves to `TimeSinceEpoch` within the network module)
185+
- Added domain context to all type, command, and event generation
186+
- Protected manually-written files (connection.py, util.py) from deletion
187+
188+
### Testing
189+
- All 19 tests passing
190+
- mypy type checking successful (56 modules)
191+
- Generator tests updated and passing (20 tests)
192+
193+
## Migration Guide
194+
195+
### For Users of cdp.database
196+
```python
197+
# Old (no longer works)
198+
from cdp import database
199+
await conn.execute(database.some_command())
200+
201+
# New - Use IndexedDB instead
202+
from cdp import indexed_db
203+
await conn.execute(indexed_db.request_database_names(security_origin="https://example.com"))
204+
```
205+
206+
### For page.navigate() Users
207+
```python
208+
# Old return signature (3 values)
209+
frame_id, loader_id, error_text = await conn.execute(page.navigate(url="..."))
210+
211+
# New return signature (4 values - added isDownload)
212+
frame_id, loader_id, error_text, is_download = await conn.execute(page.navigate(url="..."))
213+
```
214+
215+
## References
216+
217+
- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
218+
- [Privacy Sandbox APIs](https://privacysandbox.com/)
219+
- [Attribution Reporting API](https://github.com/WICG/attribution-reporting-api)
220+
- [Shared Storage API](https://github.com/WICG/shared-storage)
221+
- [FLEDGE/Protected Audience](https://github.com/WICG/turtledove)
222+
- [FedCM](https://fedidcg.github.io/FedCM/)
223+
224+
## Examples
225+
226+
See `/tmp/security_examples.py` for comprehensive code examples demonstrating all new security APIs.
227+
228+
## Version Information
229+
230+
- Protocol Version: 1.3 (latest)
231+
- Total Domains: 56 (up from 48)
232+
- New Domains: 8
233+
- Removed Domains: 1 (Database)
234+
- Security-Relevant Updates: 5 domains (Storage, Network, Audits, Browser, Target)

cdp/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import cdp.accessibility
99
import cdp.animation
1010
import cdp.audits
11+
import cdp.autofill
1112
import cdp.background_service
13+
import cdp.bluetooth_emulation
1214
import cdp.browser
1315
import cdp.css
1416
import cdp.cache_storage
@@ -18,12 +20,15 @@
1820
import cdp.dom_debugger
1921
import cdp.dom_snapshot
2022
import cdp.dom_storage
21-
import cdp.database
2223
import cdp.debugger
24+
import cdp.device_access
2325
import cdp.device_orientation
2426
import cdp.emulation
2527
import cdp.event_breakpoints
28+
import cdp.extensions
29+
import cdp.fed_cm
2630
import cdp.fetch
31+
import cdp.file_system
2732
import cdp.headless_experimental
2833
import cdp.heap_profiler
2934
import cdp.io
@@ -36,9 +41,11 @@
3641
import cdp.memory
3742
import cdp.network
3843
import cdp.overlay
44+
import cdp.pwa
3945
import cdp.page
4046
import cdp.performance
4147
import cdp.performance_timeline
48+
import cdp.preload
4249
import cdp.profiler
4350
import cdp.runtime
4451
import cdp.schema

cdp/accessibility.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class AXValueSource:
123123
#: Whether this source is superseded by a higher priority source.
124124
superseded: typing.Optional[bool] = None
125125

126-
#: The native markup source for this value, e.g. a <label> element.
126+
#: The native markup source for this value, e.g. a ``<label>`` element.
127127
native_source: typing.Optional[AXValueNativeSourceType] = None
128128

129129
#: The value, such as a node or node list, of the native source.
@@ -267,8 +267,10 @@ class AXPropertyName(enum.Enum):
267267
- from 'live' to 'root': attributes which apply to nodes in live regions
268268
- from 'autocomplete' to 'valuetext': attributes which apply to widgets
269269
- from 'checked' to 'selected': states which apply to widgets
270-
- from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling.
270+
- from 'activedescendant' to 'owns': relationships between elements other than parent/child/sibling
271+
- from 'activeFullscreenElement' to 'uninteresting': reasons why this noode is hidden
271272
'''
273+
ACTIONS = "actions"
272274
BUSY = "busy"
273275
DISABLED = "disabled"
274276
EDITABLE = "editable"
@@ -308,6 +310,24 @@ class AXPropertyName(enum.Enum):
308310
FLOWTO = "flowto"
309311
LABELLEDBY = "labelledby"
310312
OWNS = "owns"
313+
URL = "url"
314+
ACTIVE_FULLSCREEN_ELEMENT = "activeFullscreenElement"
315+
ACTIVE_MODAL_DIALOG = "activeModalDialog"
316+
ACTIVE_ARIA_MODAL_DIALOG = "activeAriaModalDialog"
317+
ARIA_HIDDEN_ELEMENT = "ariaHiddenElement"
318+
ARIA_HIDDEN_SUBTREE = "ariaHiddenSubtree"
319+
EMPTY_ALT = "emptyAlt"
320+
EMPTY_TEXT = "emptyText"
321+
INERT_ELEMENT = "inertElement"
322+
INERT_SUBTREE = "inertSubtree"
323+
LABEL_CONTAINER = "labelContainer"
324+
LABEL_FOR = "labelFor"
325+
NOT_RENDERED = "notRendered"
326+
NOT_VISIBLE = "notVisible"
327+
PRESENTATIONAL_ROLE = "presentationalRole"
328+
PROBABLY_PRESENTATIONAL = "probablyPresentational"
329+
INACTIVE_CAROUSEL_TAB_CONTENT = "inactiveCarouselTabContent"
330+
UNINTERESTING = "uninteresting"
311331

312332
def to_json(self) -> str:
313333
return self.value
@@ -334,6 +354,9 @@ class AXNode:
334354
#: This ``Node``'s role, whether explicit or implicit.
335355
role: typing.Optional[AXValue] = None
336356

357+
#: This ``Node``'s Chrome raw role.
358+
chrome_role: typing.Optional[AXValue] = None
359+
337360
#: The accessible name for this ``Node``.
338361
name: typing.Optional[AXValue] = None
339362

@@ -366,6 +389,8 @@ def to_json(self) -> T_JSON_DICT:
366389
json['ignoredReasons'] = [i.to_json() for i in self.ignored_reasons]
367390
if self.role is not None:
368391
json['role'] = self.role.to_json()
392+
if self.chrome_role is not None:
393+
json['chromeRole'] = self.chrome_role.to_json()
369394
if self.name is not None:
370395
json['name'] = self.name.to_json()
371396
if self.description is not None:
@@ -391,6 +416,7 @@ def from_json(cls, json: T_JSON_DICT) -> AXNode:
391416
ignored=bool(json['ignored']),
392417
ignored_reasons=[AXProperty.from_json(i) for i in json['ignoredReasons']] if 'ignoredReasons' in json else None,
393418
role=AXValue.from_json(json['role']) if 'role' in json else None,
419+
chrome_role=AXValue.from_json(json['chromeRole']) if 'chromeRole' in json else None,
394420
name=AXValue.from_json(json['name']) if 'name' in json else None,
395421
description=AXValue.from_json(json['description']) if 'description' in json else None,
396422
value=AXValue.from_json(json['value']) if 'value' in json else None,
@@ -437,7 +463,7 @@ def get_partial_ax_tree(
437463
:param node_id: *(Optional)* Identifier of the node to get the partial accessibility tree for.
438464
:param backend_node_id: *(Optional)* Identifier of the backend node to get the partial accessibility tree for.
439465
:param object_id: *(Optional)* JavaScript object id of the node wrapper to get the partial accessibility tree for.
440-
:param fetch_relatives: *(Optional)* Whether to fetch this nodes ancestors, siblings and children. Defaults to true.
466+
:param fetch_relatives: *(Optional)* Whether to fetch this node's ancestors, siblings and children. Defaults to true.
441467
:returns: The ``Accessibility.AXNode`` for this DOM node, if it exists, plus its ancestors, siblings and children, if requested.
442468
'''
443469
params: T_JSON_DICT = dict()
@@ -459,7 +485,6 @@ def get_partial_ax_tree(
459485

460486
def get_full_ax_tree(
461487
depth: typing.Optional[int] = None,
462-
max_depth: typing.Optional[int] = None,
463488
frame_id: typing.Optional[page.FrameId] = None
464489
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[AXNode]]:
465490
r'''
@@ -468,15 +493,12 @@ def get_full_ax_tree(
468493
**EXPERIMENTAL**
469494
470495
:param depth: *(Optional)* The maximum depth at which descendants of the root node should be retrieved. If omitted, the full tree is returned.
471-
:param max_depth: **(DEPRECATED)** *(Optional)* Deprecated. This parameter has been renamed to ```depth```. If depth is not provided, max_depth will be used.
472-
:param frame_id: *(Optional)* The frame for whose document the AX tree should be retrieved. If omited, the root frame is used.
496+
:param frame_id: *(Optional)* The frame for whose document the AX tree should be retrieved. If omitted, the root frame is used.
473497
:returns:
474498
'''
475499
params: T_JSON_DICT = dict()
476500
if depth is not None:
477501
params['depth'] = depth
478-
if max_depth is not None:
479-
params['max_depth'] = max_depth
480502
if frame_id is not None:
481503
params['frameId'] = frame_id.to_json()
482504
cmd_dict: T_JSON_DICT = {
@@ -577,7 +599,7 @@ def query_ax_tree(
577599
r'''
578600
Query a DOM node's accessibility subtree for accessible name and role.
579601
This command computes the name and role for all nodes in the subtree, including those that are
580-
ignored for accessibility, and returns those that mactch the specified name and role. If no DOM
602+
ignored for accessibility, and returns those that match the specified name and role. If no DOM
581603
node is specified, or the DOM node does not exist, the command returns an error. If neither
582604
``accessibleName`` or ``role`` is specified, it returns all the accessibility nodes in the subtree.
583605

0 commit comments

Comments
 (0)