Handbook
Playwright read-only policy (v1)
Applies to: forge_lcdl.playwright (planned) and any host wiring that calls it. Version: v1 read-only guarantee — stricter than “read-mostly”; writes and secret export are out of scope.
Updated
Trust model
| Actor | Responsibility |
|---|---|
| User | Completes login/MFA in the browser; approves domains and downloads per org policy. |
| Playwright / host | Maintains the authorized session; exposes a narrow attach API to LCDL. |
| forge-lcdl | Reads through the session facade only; normalizes to UnifiedItem; never escalates to private APIs or stolen credentials. |
| Consumer app | Retention, audit, and human approval for any action beyond read. |
LCDL does not ask for passwords, bypass MFA, or export harvested browser tokens. Read-only HTTP GET on allowlisted hosts (graph.microsoft.com, *.sharepoint.com, tenant OWA hosts) using the attached Playwright browser session (same pattern as SharePoint library REST) is permitted for list/count harvest only — no POST/PATCH/DELETE and no separate app registration inside LCDL.
Allowed operations (v1)
| Category | Examples |
|---|---|
| Session | Attach to an existing authorized playwright session; query is_attached |
| Navigation | Navigate to approved domains/URLs (allowlist per source) |
| Observation | Read visible page title, metadata, and text exposed by the facade |
| Snapshots | DOM / accessibility snapshots and derived text for normalization |
| Files | Inspect or download bytes only when session policy permits read-only local extraction (no upload path) |
| Development | Write local tests, docs, logs, and audit artifacts in the repo/CI |
Disallowed operations (v1)
| Category | Examples (non-exhaustive) |
|---|---|
| Messaging / workflow writes | Send, reply, forward, approve, reject, submit, complete, acknowledge |
| Content mutation | Upload, delete, rename, move, share, permission changes |
| UI mutation | Click submit/action controls, type into forms, fill forms, select options that commit state |
| Secret export | Cookies, localStorage/sessionStorage, storage state files, auth headers, access/refresh tokens, hidden credential fields |
| Private APIs | Workday REST or app-private APIs without an explicit policy carve-out |
| Non-session Graph | Graph/SharePoint calls with application credentials exported from LCDL — forbidden |
| Bypass | Credential prompts inside LCDL, MFA bypass, token replay from harvested material |
If a host exposes tools beyond this policy, playwright.policy must deny them at the LCDL boundary even if the runtime could invoke them.
Alignment with MCP readmostly_playwright
The existing MCP preset McpPolicy.readmostly_playwright() is the reference ceiling for browser tool posture when playwright delegates to Playwright-like MCP tools:
| Posture | MCP preset | Playwright v1 |
|---|---|---|
| Navigate / snapshot / screenshot | Allow | Allow (via facade) |
| Click / type / fill / press key | Approval required | Deny (not merely approval) |
Cookie/storage set/clear, set_storage_state, evaluate, file upload |
Deny | Deny |
| Network route / unsafe run_code | Deny | Deny |
Playwright v1 is stricter than “read-mostly”: mutation-capable tools are blocked in code, not left for operator approval inside LCDL.
Policy layers (implementation)
Entry API (e.g. read_outlook_inbox)
→ playwright.policy.assert_readonly_operation(op)
→ playwright.policy.validate_url(url, source_system)
→ playwright.policy.filter_tool_name(name) # when MCP bridge used
→ PlaywrightSession.* (only read methods exposed on Protocol)
→ normalizer (no side effects)
Domain allowlists
Per-source presets (configured in playwright/policy.py, later step) constrain navigation, for example:
| Source | Typical host patterns (illustrative) |
|---|---|
| Outlook | outlook.office.com, outlook.office365.com, tenant-specific OWAs |
| Teams | teams.microsoft.com |
| SharePoint | *.sharepoint.com |
| OneDrive | onedrive.live.com, SharePoint-hosted OneDrive paths |
| Workday | Customer Workday hostnames (explicit config required) |
Exact lists are operator-configurable; defaults are conservative (deny unknown hosts).
Body and PII fields
body_text and rich previews are included in UnifiedItem only when:
- the snapshot visibly contains the content, and
- the caller’s policy flags allow full body (vs preview-only).
Tests use fixtures with synthetic text — never production mail bodies in the repo.
Data that may appear in artifacts
| Field | Allowed content | Forbidden content |
|---|---|---|
title, body_preview, body_text |
Visible UI text | Hidden fields, token-like strings |
url |
Page or item deep link | URLs with embedded secrets |
provenance |
Source id, normalizer version, snapshot id | Cookie names, header dumps |
raw_ref |
Stable non-secret locator (row index, aria ref) | Session ids, bearer tokens |
attachments |
Filename, size, mime, readonly download ref | Raw file paths outside policy |
Redaction rules mirror Data handling: file bugs with REDACTED tokens; do not commit live session captures.
File read policy
| Rule | Detail |
|---|---|
| Local only | Downloaded bytes stay in memory or temp paths controlled by the host; LCDL does not persist mailbox content by default. |
| Formats | PPTX, XLSX, DOCX, PDF — text/metadata extraction only; no macro execution. |
| Optional deps | Parsers may live behind extras; missing deps → clear PlaywrightError, not silent network fetch. |
Enforcement and review
| Mechanism | When |
|---|---|
PlaywrightSession Protocol surface |
Only read methods on the typed API |
policy.py |
URL, operation, and tool-name checks |
| Unit tests | Denied ops return policy_denied; snapshots never contain Set-Cookie patterns in fixtures |
| Code review | PDCA guardrails (.cursor/rules/lcdl-playwright-pdca.mdc) |
| Live tests | Opt-in only; never in default CI |
Narrow exceptions (v1)
| Exception | Scope |
|---|---|
| SharePoint / Graph GET | page.context.request.get with session cookies on allowlisted hosts; list/metadata only |
| Read-state restore | Allowlisted evaluate restore_row_unread only after allowlisted list_row click when preserve_unread=true on outlook_read_conversation_thread — not general mark-read/unread UI automation |
Future work (explicitly out of v1)
- Graph/REST with application credentials (client id/secret) — separate design and policy doc.
- “Read-mostly” with human-approved clicks — not in v1; would require new policy tier and threat-model update.
- Write-back tasks (send mail, approve Workday task) — forbidden until a major version and new contract family.
References
- ARCHITECTURE.md — module layout and call flows
- EXTENSION_POINTS.md — session Protocol and adapters
- Threat model
mcp_client/policy.py