slicc-cdp-client
Shared Chrome DevTools Protocol connection for the playwright-cli and curlwright binaries.
The crates.io name is slicc-cdp-client. The Rust library name stays cdp_client:
cdp-client = { package = "slicc-cdp-client", version = "1" }
One browser WebSocket. Target.attachToTarget uses {targetId, flatten: true}. Later page commands send that sessionId at the top level. There is no /devtools/page/<id> socket.
Fetch interception
cdp_client::fetch pauses requests on one target session. Interception::attach(&mut cdp, target_id, &patterns) checks the target, attaches, and sends Fetch.enable; dropping it, or finish(), sends Fetch.disable and detaches. Interception::on_session does the same on a session you already hold and leaves it attached. cdp::detach also forgets that session's paused requests.
Pattern::request(glob)andPattern::response(glob)are CDPurlPatterns (*,?,\to escape) at the Request or Response stage, with an optionalresource_type.next(timeout)returns aPaused:method,url,url_fragment,full_url(),headers,post_data, and at the response stagestatus,response_headersandlocation(), which resolvesLocationagainst the request URL and keeps its fragment.decide(&paused, &Decision)sendsContinue(Overrides)(continueRequestwith URL, method, body or headers),ContinueResponse(ResponseOverrides),Fulfill(Fulfill)(fulfillRequest: status, headers, body;Fulfill::signed_in()is a short "Signed in, you can close this tab" page) orFail(ErrorReason)(failRequest).response_body(&paused)isFetch.getResponseBody, decoded.navigation(guard, event)reads aPage.frameNavigatedevent asurlplusurlFragment, for a redirect made by script.Cdp::set_decider(session, Some(Box<dyn FnMut(&Paused) -> Decision>))answers pauses inline, even while another call waits for its reply, so a synchronousPage.navigatedoesn't deadlock. A decision CDP rejects is retried as a plain continue and kept intake_fetch_errors().
Seven's origin is off limits
Cdp filters every command and event the way slicc-cli's cdp relay does (slicc-cli README, "Seven's origin is off limits"). What is protected comes from protectedOrigins in the slicc-browsers catalog: every sliccy.ai host, vault.sliccy.ai by name, and the extra origins from Cdp::protect_origins(&[...]). The catalog's protectedSchemes protect chrome, chrome-untrusted, devtools, edge, brave, opera, vivaldi, arc, chrome-extension and file. The schemeExceptions allow chrome://newtab, chrome://new-tab-page, its embedded chrome-untrusted://new-tab-page frame, edge://newtab, brave://newtab and opera://startpage, on any path. Non-inheriting about: URLs are rewritten to the catalog's aboutScheme (chrome) before matching, so about:settings is protected while about:blank and about:srcdoc inherit their existing origin rules. Wrappers are judged by what they wrap: view-source: by its inner URL, and blob: and filesystem: by their creator origin. A type in targetTypes (browser_ui) is always protected, regardless of URL. Extension URLs are protected for every target type, including service_worker, background_page and other. These rules apply to listing, attach, navigation, target creation, curlwright, discovery, WebMCP, frames and child targets. Since 1.27 attaches to a real Chrome on port 9222, this boundary keeps password pages, extensions and local files away from the agent. protected::Protected parses URLs with a WHATWG parser and passes every vector in protected.json. The Rust crate is pinned to the exact npm catalog/vector version; integration tests compare Cargo.lock and the compiled crate's protectedOrigins to npm, and Renovate groups both dependencies. Refusals are slicc: refused: target is on seven's origin, or a reason of their own after slicc: refused: . When the connection itself closes, the close frame (websocket closed <code> <reason>) is reported, never a refusal.
- Targets. A target is protected when its URL is, or when it is on
about:blank(or another inheriting URL) and was protected before or has a protected or unknown opener. A page session also becomes protected when its main frame commits a protected URL orsecurityOrigin(Page.frameNavigated), when its default context's origin is protected (Runtime.executionContextCreated), or when the target moves there (Target.targetInfoChanged). Such a session is detached, its events and results are dropped, and every later command on it is refused. - Extension execution worlds. An extension's isolated world in an ordinary page does not protect the page. Its execution context and tied events are hidden. Explicit context selectors and remote object handles are session-scoped and must have been exposed by the client. Known hidden worlds are refused with
slicc: refused: that execution context belongs to a browser extension; guessed or stale selectors and handles withslicc: refused: that execution context or object is not exposed to browser automation. Unknown context-selecting calls first enable Runtime when needed, so skippingRuntime.enablecannot bypass this boundary. Ordinary page objects returned by evaluation, DOM resolution, properties, console and exception events remain usable without a new preflight. Debugger, Profiler and HeapProfiler access is refused when a session contains a hidden world, and their events are dropped.Runtime.addBindingmust not select or bind into hidden worlds. - Refused:
Target.createTarget,Page.navigateand anything else whoseurlis protected;Page.navigateToHistoryEntryto a protected entry;attachToTarget,activateTarget,closeTarget,Browser.getWindowForTargetand any command whosetargetIdorframeIdis a protected target; every command on a protected session.Target.getTargetInfoandTarget.detachFromTargetstay allowed. - Origins, storage and cookies. A command whose
origin,securityOrigin,topLevelSite,scopeURL(and the rest of slicc-cli's list),storageKeyorcacheIdnames a protected origin is refused. Free-form values (expression,arguments,value,headers,postData) aren't searched. Cookie readers answer without protected cookies, and cookie writers for a protected domain, URL or partition site are refused withslicc: refused: cookies on seven's origin. So areNetwork.clearBrowserCookiesandStorage.clearCookies, except in a browser context thisCdpcreated. - Browser-wide:
Target.exposeDevToolsProtocol,Target.sendMessageToTarget,Target.attachToBrowserTarget,Tracing.start,Extensions.loadUnpacked,Fetch.*without a session, permissions without an origin, andBrowser.resetPermissionsoutside an own context.Target.setAutoAttachwithautoAttach: trueis refused: slicc-cdp does not support auto-attach. - Requests a protected document makes. When a touched target commits a protected origin all the same (back/forward cache, a protected
blob:), seen by its guard session'sPage.frameNavigatedorRuntime.executionContextCreated, every page session on it getsFetch.disableand is detached at once, and its pending pauses are continued unchanged. A paused request whose frame is a protected document, whose target is protected, or whoseRefererorOriginis protected is continued unchanged and never reachesnextor a decider, even when its URL isn't protected. ItsNetworkevents (bydocumentURL,initiatororframeId) are dropped. - Requests. A protected request's
Network.*andFetch.*events are dropped,ExtraInfoevents are held until their request is known, and seven's cookies leaveassociatedCookies. Its body getters (Network.getResponseBody,getRequestPostData,Fetch.getResponseBody,replayXHRand the rest) are refused, and so is one for a request thisCdpnever saw (slicc: refused: slicc hasn't seen this request, so it could be seven's).IO.readworks only on a stream thisCdphanded out. - Navigation by other means. A target this
Cdpattaches to or creates is touched. On it,Cdpattaches a session of its own, enablesFetchfor document requests at the request stage plusNetwork.setBypassServiceWorker, and fails each document request into a protected origin withBlockedByClient. A script'slocation, a link, a meta refresh or a server redirect into the vault then shows Chrome's error page, and the target is protected from then on.Target.createTargetopensabout:blank, touches it, and then navigates. The guard setup (Target.setDiscoverTargetsthe first time, the attach,Fetch.enable,Network.enableandNetwork.setBypassServiceWorker) shares one 30 s budget and fails closed when it runs out, refused as…out of this target: <step> timed out.Page.enableandRuntime.enableon that session are optional. When one of them times out, the setup goes on,Cdp::unguarded_optional(target)lists it, andCdp::take_notices()returnsslicc: guard: <step> timed out; continuing, whichplaywright-cliandcurlwrightprint on stderr. A paused document request to a protected origin on your ownFetchis failed the same way, and other protected requests are continued unchanged; neither reachesnextor a decider. - Listing. A backend's protected-target refusal from
Target.getTargetInfois recorded as a protected verdict, so it returns the standard refusal rather thanUnknown tab.Target.getTargets,getTargetInfo,targetCreatedandtargetInfoChangedstill show protected targets, but theirurlandtitleare only the origin.Cdp::is_protected_target(id)answers from what thisCdphas seen (listings, commits, refused sessions).Cdp::target_protected(id, false)asks the browser now, a tab that landed on the vault included, and records the answer, so both agree afterwards.playwright-cliandcurlwrightleave them out entirely. - Limits. As in slicc-cli: a document that arrives without a request (back/forward cache, a protected
blob:) is caught only after it commits.
WebSocket writes check for an already queued close without waiting for more traffic. That non-blocking drain temporarily clears the read timeout, restores both socket options afterwards, and is capped at 2 ms and 64 KiB. Continuous discovery events cannot hold a command or its refusal before the next send.
fetch::enable refuses a pattern whose host part names a protected host (parsed, so notsliccy.ai and ?next=https://seven.sliccy.ai/ pass). A decision can't redirect to a protected URL by url, Location or Refresh.
use ;
let mut seen = attach?;
let session = seen.session_id.to_string;
seen.notify;
if let Some = seen.next?
Host builds use native-tls for wss:// and for https:// discovery, on macOS, Linux, and Windows. Rust 1.83 or newer. The wasm32-wasip1 build has no TLS. This crate does not launch Chrome. Callers pass a DevTools WebSocket URL or an http:// discovery URL.
cargo install slicc-cdp installs the playwright-cli command. cargo install curlwright installs curlwright. @ai-ecoverse/slicc-cdp on npm ships their WASI builds for SLICC.