Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions SECURITY_UPDATES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# Security-Relevant API Updates

This document highlights the security-relevant additions to the Chrome DevTools Protocol implementation in this update.

## Summary

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.

## New Security-Focused Domains

### 1. Extensions Domain
**Purpose**: Browser extension management for security testing
- Load and uninstall extensions programmatically
- Manage extension storage (local/sync/managed)
- **Use Case**: Test extension security boundaries, data isolation, and permission handling

### 2. FedCm Domain (Federated Credential Management)
**Purpose**: Test federated authentication flows
- Track and interact with FedCm dialogs
- Programmatically select accounts or dismiss dialogs
- **Use Case**: Verify federated login security, test account selection flows

### 3. DeviceAccess Domain
**Purpose**: Handle device permission prompts
- Track camera, microphone, and other device access requests
- Programmatically grant or deny permissions
- **Use Case**: Test device permission security, verify proper permission prompts

### 4. FileSystem Domain
**Purpose**: File system directory access
- Get directory access for testing File System Access API
- **Use Case**: Test file system permission boundaries

### 5. Autofill, BluetoothEmulation, PWA, Preload Domains
Additional domains for comprehensive browser testing

## Major Security Updates to Existing Domains

### Storage Domain - Privacy Sandbox APIs
The Storage domain received the most significant security-relevant updates:

#### Attribution Reporting API (Privacy-Preserving Ad Measurement)
```python
# Enable tracking and local testing
await conn.execute(storage.set_attribution_reporting_tracking(enable=True))
await conn.execute(storage.set_attribution_reporting_local_testing_mode(enabled=True))

# Send test reports
await conn.execute(storage.send_pending_attribution_reports())

# Listen for events
async for event in conn.listen():
if isinstance(event, storage.AttributionReportingSourceRegistered):
print(f"Source registered: {event.registration}")
```

#### Shared Storage API (Cross-Site Storage with Privacy)
```python
# Track shared storage access
await conn.execute(storage.set_shared_storage_tracking(enable=True))

# Get and set entries for testing
metadata = await conn.execute(storage.get_shared_storage_metadata(
owner_origin="https://example.com"
))

await conn.execute(storage.set_shared_storage_entry(
owner_origin="https://example.com",
key="test-key",
value="test-value"
))
```

#### Interest Groups / FLEDGE / Protected Audience API
```python
# Track interest group auctions
await conn.execute(storage.set_interest_group_tracking(enable=True))
await conn.execute(storage.set_interest_group_auction_tracking(enable=True))

# Get details for security verification
details = await conn.execute(storage.get_interest_group_details(
owner_origin="https://example.com",
name="interest-group-name"
))

# Configure k-anonymity for testing
await conn.execute(storage.set_protected_audience_k_anonymity(threshold=50))
```

#### Bounce Tracking Mitigation
```python
# Test bounce tracking mitigation
deleted_sites = await conn.execute(storage.run_bounce_tracking_mitigations())
print(f"Mitigated tracking for {len(deleted_sites)} sites")
```

### Network Domain - Cookie and IP Protection
```python
# Control cookie behavior for third-party cookie testing
await conn.execute(network.set_cookie_controls(mode='block-third-party'))

# Test IP protection features
status = await conn.execute(network.get_ip_protection_proxy_status())
await conn.execute(network.set_ip_protection_proxy_bypass_enabled(enabled=True))

# Get related website sets (First-Party Sets)
sets = await conn.execute(storage.get_related_website_sets())
```

### Audits Domain - Form Security
```python
# Automated form security/privacy issue detection
issues = await conn.execute(audits.check_forms_issues())
for issue in issues:
print(f"Form issue detected: {issue}")
```

### Browser Domain - Privacy Sandbox Configuration
```python
# Override Privacy Sandbox enrollment for testing
await conn.execute(browser.add_privacy_sandbox_enrollment_override(
url="https://example.com"
))

# Configure coordinator keys
await conn.execute(browser.add_privacy_sandbox_coordinator_key_config(
coordinator_origin="https://coordinator.example.com",
coordinator_key="test-key"
))
```

## Security Testing Use Cases

### 1. Privacy Sandbox Testing
Test the complete Privacy Sandbox suite:
- Attribution Reporting (privacy-preserving conversion measurement)
- Shared Storage (cross-site storage with privacy guarantees)
- Interest Groups/FLEDGE (privacy-preserving ad auctions)
- Topics API (via interest groups)
- k-anonymity thresholds

### 2. Third-Party Cookie Migration
Test alternatives to third-party cookies:
- First-Party Sets (Related Website Sets)
- Partitioned cookies (CHIPS)
- Storage Access API
- Cookie controls and policies

### 3. Authentication Security
- Test FedCm federated login flows
- Verify account selection security
- Test dialog dismissal handling

### 4. Permission Testing
- Verify device permission prompts (camera, mic, etc.)
- Test permission grant/deny flows
- Validate permission persistence

### 5. Extension Security
- Test extension isolation boundaries
- Verify extension data access controls
- Test extension installation/uninstallation

### 6. Anti-Tracking Features
- Test bounce tracking mitigation
- Verify IP protection
- Test tracking prevention measures

### 7. Form Security Auditing
- Automated detection of insecure forms
- Privacy leak detection
- Input validation issues

## Breaking Changes

**Database Domain Removed**: The deprecated Database domain has been removed from the CDP specification. If your code imports `cdp.database`, you must migrate to:
- IndexedDB APIs (`cdp.indexed_db`)
- Storage APIs (`cdp.storage`)
- Cache Storage APIs (`cdp.cache_storage`)

## Implementation Notes

### Generator Improvements
- Fixed same-domain type reference bug (e.g., `Network.TimeSinceEpoch` now correctly resolves to `TimeSinceEpoch` within the network module)
- Added domain context to all type, command, and event generation
- Protected manually-written files (connection.py, util.py) from deletion

### Testing
- All 19 tests passing
- mypy type checking successful (56 modules)
- Generator tests updated and passing (20 tests)

## Migration Guide

### For Users of cdp.database
```python
# Old (no longer works)
from cdp import database
await conn.execute(database.some_command())

# New - Use IndexedDB instead
from cdp import indexed_db
await conn.execute(indexed_db.request_database_names(security_origin="https://example.com"))
```

### For page.navigate() Users
```python
# Old return signature (3 values)
frame_id, loader_id, error_text = await conn.execute(page.navigate(url="..."))

# New return signature (4 values - added isDownload)
frame_id, loader_id, error_text, is_download = await conn.execute(page.navigate(url="..."))
```

## References

- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
- [Privacy Sandbox APIs](https://privacysandbox.com/)
- [Attribution Reporting API](https://github.com/WICG/attribution-reporting-api)
- [Shared Storage API](https://github.com/WICG/shared-storage)
- [FLEDGE/Protected Audience](https://github.com/WICG/turtledove)
- [FedCM](https://fedidcg.github.io/FedCM/)

## Examples

See `/tmp/security_examples.py` for comprehensive code examples demonstrating all new security APIs.

## Version Information

- Protocol Version: 1.3 (latest)
- Total Domains: 56 (up from 48)
- New Domains: 8
- Removed Domains: 1 (Database)
- Security-Relevant Updates: 5 domains (Storage, Network, Audits, Browser, Target)
9 changes: 8 additions & 1 deletion cdp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import cdp.accessibility
import cdp.animation
import cdp.audits
import cdp.autofill
import cdp.background_service
import cdp.bluetooth_emulation
import cdp.browser
import cdp.css
import cdp.cache_storage
Expand All @@ -18,12 +20,15 @@
import cdp.dom_debugger
import cdp.dom_snapshot
import cdp.dom_storage
import cdp.database
import cdp.debugger
import cdp.device_access
import cdp.device_orientation
import cdp.emulation
import cdp.event_breakpoints
import cdp.extensions
import cdp.fed_cm
import cdp.fetch
import cdp.file_system
import cdp.headless_experimental
import cdp.heap_profiler
import cdp.io
Expand All @@ -36,9 +41,11 @@
import cdp.memory
import cdp.network
import cdp.overlay
import cdp.pwa
import cdp.page
import cdp.performance
import cdp.performance_timeline
import cdp.preload
import cdp.profiler
import cdp.runtime
import cdp.schema
Expand Down
40 changes: 31 additions & 9 deletions cdp/accessibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class AXValueSource:
#: Whether this source is superseded by a higher priority source.
superseded: typing.Optional[bool] = None

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

#: The value, such as a node or node list, of the native source.
Expand Down Expand Up @@ -267,8 +267,10 @@ class AXPropertyName(enum.Enum):
- from 'live' to 'root': attributes which apply to nodes in live regions
- from 'autocomplete' to 'valuetext': attributes which apply to widgets
- from 'checked' to 'selected': states which apply to widgets
- from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling.
- from 'activedescendant' to 'owns': relationships between elements other than parent/child/sibling
- from 'activeFullscreenElement' to 'uninteresting': reasons why this noode is hidden
'''
ACTIONS = "actions"
BUSY = "busy"
DISABLED = "disabled"
EDITABLE = "editable"
Expand Down Expand Up @@ -308,6 +310,24 @@ class AXPropertyName(enum.Enum):
FLOWTO = "flowto"
LABELLEDBY = "labelledby"
OWNS = "owns"
URL = "url"
ACTIVE_FULLSCREEN_ELEMENT = "activeFullscreenElement"
ACTIVE_MODAL_DIALOG = "activeModalDialog"
ACTIVE_ARIA_MODAL_DIALOG = "activeAriaModalDialog"
ARIA_HIDDEN_ELEMENT = "ariaHiddenElement"
ARIA_HIDDEN_SUBTREE = "ariaHiddenSubtree"
EMPTY_ALT = "emptyAlt"
EMPTY_TEXT = "emptyText"
INERT_ELEMENT = "inertElement"
INERT_SUBTREE = "inertSubtree"
LABEL_CONTAINER = "labelContainer"
LABEL_FOR = "labelFor"
NOT_RENDERED = "notRendered"
NOT_VISIBLE = "notVisible"
PRESENTATIONAL_ROLE = "presentationalRole"
PROBABLY_PRESENTATIONAL = "probablyPresentational"
INACTIVE_CAROUSEL_TAB_CONTENT = "inactiveCarouselTabContent"
UNINTERESTING = "uninteresting"

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

#: This ``Node``'s Chrome raw role.
chrome_role: typing.Optional[AXValue] = None

#: The accessible name for this ``Node``.
name: typing.Optional[AXValue] = None

Expand Down Expand Up @@ -366,6 +389,8 @@ def to_json(self) -> T_JSON_DICT:
json['ignoredReasons'] = [i.to_json() for i in self.ignored_reasons]
if self.role is not None:
json['role'] = self.role.to_json()
if self.chrome_role is not None:
json['chromeRole'] = self.chrome_role.to_json()
if self.name is not None:
json['name'] = self.name.to_json()
if self.description is not None:
Expand All @@ -391,6 +416,7 @@ def from_json(cls, json: T_JSON_DICT) -> AXNode:
ignored=bool(json['ignored']),
ignored_reasons=[AXProperty.from_json(i) for i in json['ignoredReasons']] if 'ignoredReasons' in json else None,
role=AXValue.from_json(json['role']) if 'role' in json else None,
chrome_role=AXValue.from_json(json['chromeRole']) if 'chromeRole' in json else None,
name=AXValue.from_json(json['name']) if 'name' in json else None,
description=AXValue.from_json(json['description']) if 'description' in json else None,
value=AXValue.from_json(json['value']) if 'value' in json else None,
Expand Down Expand Up @@ -437,7 +463,7 @@ def get_partial_ax_tree(
:param node_id: *(Optional)* Identifier of the node to get the partial accessibility tree for.
:param backend_node_id: *(Optional)* Identifier of the backend node to get the partial accessibility tree for.
:param object_id: *(Optional)* JavaScript object id of the node wrapper to get the partial accessibility tree for.
:param fetch_relatives: *(Optional)* Whether to fetch this nodes ancestors, siblings and children. Defaults to true.
:param fetch_relatives: *(Optional)* Whether to fetch this node's ancestors, siblings and children. Defaults to true.
:returns: The ``Accessibility.AXNode`` for this DOM node, if it exists, plus its ancestors, siblings and children, if requested.
'''
params: T_JSON_DICT = dict()
Expand All @@ -459,7 +485,6 @@ def get_partial_ax_tree(

def get_full_ax_tree(
depth: typing.Optional[int] = None,
max_depth: typing.Optional[int] = None,
frame_id: typing.Optional[page.FrameId] = None
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[AXNode]]:
r'''
Expand All @@ -468,15 +493,12 @@ def get_full_ax_tree(
**EXPERIMENTAL**

:param depth: *(Optional)* The maximum depth at which descendants of the root node should be retrieved. If omitted, the full tree is returned.
:param max_depth: **(DEPRECATED)** *(Optional)* Deprecated. This parameter has been renamed to ```depth```. If depth is not provided, max_depth will be used.
:param frame_id: *(Optional)* The frame for whose document the AX tree should be retrieved. If omited, the root frame is used.
:param frame_id: *(Optional)* The frame for whose document the AX tree should be retrieved. If omitted, the root frame is used.
:returns:
'''
params: T_JSON_DICT = dict()
if depth is not None:
params['depth'] = depth
if max_depth is not None:
params['max_depth'] = max_depth
if frame_id is not None:
params['frameId'] = frame_id.to_json()
cmd_dict: T_JSON_DICT = {
Expand Down Expand Up @@ -577,7 +599,7 @@ def query_ax_tree(
r'''
Query a DOM node's accessibility subtree for accessible name and role.
This command computes the name and role for all nodes in the subtree, including those that are
ignored for accessibility, and returns those that mactch the specified name and role. If no DOM
ignored for accessibility, and returns those that match the specified name and role. If no DOM
node is specified, or the DOM node does not exist, the command returns an error. If neither
``accessibleName`` or ``role`` is specified, it returns all the accessibility nodes in the subtree.

Expand Down
Loading