@ai-ecoverse/slicc-browsers 1.8.3 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -9
- package/catalog.json +7 -0
- package/guard-driver.mjs +35 -20
- package/guard-vectors.json +389 -1
- package/package.json +3 -3
- package/protected.json +220 -0
- package/schema/guard-vectors.schema.json +5 -0
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Every feature builds for `wasm32-wasip1`.
|
|
|
94
94
|
- `discardExceptions`: where the site pattern and the extra origins' hosts go, as a `list` of strings or as a `chrome-time-map`, a map from site to the time it was added (microseconds since 1601-01-01 UTC, as a decimal string);
|
|
95
95
|
- `cleanExit`: values that mark the last exit clean;
|
|
96
96
|
- `form` says where a key comes from: `six` (SLICC six's keys) or `chrome-154` (what Chrome 154 reads).
|
|
97
|
-
- `protectedOrigins`: what browser control must never reach, see [Protected origins](#protected-origins). `domains` (`sliccy.ai`), `hosts` (`vault.sliccy.ai`), `schemes` (`https`, `http`), `innerSchemes` (`blob`, `filesystem`, `view-source`), `anySchemes` (`devtools`), `protectedSchemes` (`chrome`, `chrome-untrusted`, `chrome-extension`, `devtools`, `edge`, `brave`, `opera`, `vivaldi`, `arc`, `file`) with `schemeExceptions` (`chrome://newtab`, `chrome://new-tab-page`, `chrome-untrusted://new-tab-page`, `edge://newtab`, `brave://newtab`, `opera://startpage`), `aboutScheme` (`chrome`), `targetTypes` (`browser_ui`), `inheriting` (`about:blank`, `about:srcdoc`), and `refusal`, the text of a refusal.
|
|
97
|
+
- `protectedOrigins`: what browser control must never reach, see [Protected origins](#protected-origins). `domains` (`sliccy.ai`), `hosts` (`vault.sliccy.ai`), `schemes` (`https`, `http`), `innerSchemes` (`blob`, `filesystem`, `view-source`), `anySchemes` (`devtools`), `protectedSchemes` (`chrome`, `chrome-untrusted`, `chrome-search`, `chrome-native`, `chrome-distiller`, `chrome-experimental-site-token-provider`, `isolated-app`, `externalfile`, `content`, `chrome-extension`, `devtools`, `edge`, `brave`, `opera`, `vivaldi`, `arc`, `file`) with `schemeExceptions` (`chrome://newtab`, `chrome://new-tab-page`, `chrome-untrusted://new-tab-page`, `edge://newtab`, `brave://newtab`, `opera://startpage`), `aboutScheme` (`chrome`), `targetTypes` (`browser_ui`), `inheriting` (`about:blank`, `about:srcdoc`), and `refusal`, the text of a refusal.
|
|
98
98
|
- `profiles`: SLICC profiles live in `<config dir>/<root>/<name>`, and a snap's in `~/snap/<snap>/common/<snapRoot>/<name>`. `legacyRoot` is the old root, never scanned; on macOS's case-insensitive volume it is SLICC six's root too. `sixProfilePattern` matches six's profile names, case-insensitively. `namePattern` is a valid profile name. `six` describes SLICC six's Chrome profiles (see [SLICC six's profiles](#slicc-sixs-profiles)):
|
|
99
99
|
- `roots`: six's profile root per OS (`macos`, `linux`, `windows`), a `base` location followed by the `path` segments. The base is its `env` when that is set and not empty (any value, as six takes it), else `home` under the home dir.
|
|
100
100
|
- `defaultName` (`browser-coding-agent-chrome`), `defaultPort` (5710) and `portSuffix` (`-{port}`): six's profile for its serve port `<port>` is `<root>/<defaultName>`, with `portSuffix` added for any port other than `defaultPort`.
|
|
@@ -120,8 +120,35 @@ Browser control must never navigate to, open or attach to a target on seven's ow
|
|
|
120
120
|
|
|
121
121
|
This is wider than the rule for finding seven (`seven.originHostPattern`, which leaves out the `excludedLabels` and `excludedLabelPrefixes`—`www`, `vault`, `vault-preview`, `get`, `mcpcat`, `echo`, and any `vault-*` prefix). Every `sliccy.ai` host shares the `sliccy.ai` cookies, and cookies ignore the port and, without `Secure`, the scheme. SLICC six lives on `www.sliccy.ai` and the apex, and its cookies matter. `*.kernel.localhost` is not protected: each port is its own origin, and the agent's own servers live there. An extra origin is matched exactly, so `http://127.0.0.1:5710` doesn't protect `http://127.0.0.1:5711`.
|
|
122
122
|
|
|
123
|
+
**Chromium scheme audit.** Checked [content scheme registration](https://github.com/chromium/chromium/blob/d0bfaae35fa745d9dee525dc4ff6617b1551dbe7/content/common/url_schemes.cc) and [Chrome additional schemes](https://github.com/chromium/chromium/blob/d0bfaae35fa745d9dee525dc4ff6617b1551dbe7/chrome/common/chrome_content_client.cc), including platform-specific registrations:
|
|
124
|
+
|
|
125
|
+
| Scheme | Decision and reason |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `chrome` | Protected: browser WebUI, with only the catalog's exact new-tab host exceptions. |
|
|
128
|
+
| `chrome-untrusted` | Protected: browser-owned isolated UI resources, except the exact `new-tab-page` host. |
|
|
129
|
+
| `chrome-search` | Protected: Instant/search-provider UI and profile resources, including `local-ntp`, `most-visited` and `remote-ntp`; hiding targets is not refusal. |
|
|
130
|
+
| `chrome-native` | Protected: browser UI rendered by platform-native widgets instead of HTML. |
|
|
131
|
+
| `chrome-distiller` | Protected: browser-generated reader-mode documents, which may contain content distilled from a protected document. |
|
|
132
|
+
| `chrome-error` | Not scheme-wide protected: generic network-error documents hold no protected document content; ordinary errors must remain readable and recoverable. |
|
|
133
|
+
| `isolated-app` | Protected: installed isolated web apps have app-specific storage and privileged capabilities outside ordinary web origins. |
|
|
134
|
+
| `devtools` | Protected already: a DevTools front end can inspect a protected target. |
|
|
135
|
+
| `chrome-extension` | Protected already: extension documents and workers can contain credentials and browser privileges. |
|
|
136
|
+
| `chrome-experimental-site-token-provider` | Protected: an internal data source issues site-specific tokens. |
|
|
137
|
+
| `externalfile` | Protected: ChromeOS local/external file access must not bypass the `file` fence. |
|
|
138
|
+
| `file` | Protected already: local files must not bypass the agent's file-system fence. |
|
|
139
|
+
| `content` | Protected: Android local content-provider access must not bypass the `file` fence. |
|
|
140
|
+
| `android-app` | Not scheme-wide protected: Android app/referrer identity, not a browser UI document; extra-origin rules still apply. |
|
|
141
|
+
| `googlechrome` | Not scheme-wide protected: an external launch alias, not registered as a privileged document scheme by these entry points. |
|
|
142
|
+
| `view-source`, `blob`, `filesystem` | Not blindly scheme-wide protected: existing recursive inner-origin rules protect what they wrap. |
|
|
143
|
+
| `about` | Not blindly scheme-wide protected: existing Chromium alias rewriting protects UI while retaining inheriting blank/srcdoc URLs. |
|
|
144
|
+
| `http`, `https`, `data`, `javascript`, `ws`, `wss`, `mailto`, `webcal` | Not scheme-wide protected: ordinary web, inline content, transport or external handlers, not privileged browser UI. |
|
|
145
|
+
|
|
146
|
+
A guard must refuse navigation, creation and attach on protected schemes, including protected frames of an otherwise ordinary tab, rather than merely hiding them from discovery. `chrome-error` is deliberately not a protected scheme: ordinary DNS, connection and certificate failures produce that same generic document, so scheme-wide refusal would prevent reattaching to an ordinary error tab. Error pages hold no secrets from the failed document. This does not permit opening or attaching to a protected origin: guards still fail protected navigations before commit and enforce protected-target provenance; C1–C5 observer-only `unreachableUrl`, history and network block evidence is unchanged.
|
|
147
|
+
|
|
123
148
|
**New-tab checks (1.8.2).** In fresh default profiles on macOS, Chrome for Testing 156.0.8078.4 (Playwright build 1248) and Chrome 155.0.8059.82 commit `chrome://newtab/` to `chrome://new-tab-page/` and embed `chrome-untrusted://new-tab-page/one-google-bar?paramsencoded=` as an out-of-process frame. Direct `chrome://new-tab-page/` also commits as itself. Edge 155.0.4283.60 commits `edge://newtab/` to `https://ntp.msn.com/edge/ntp` with an `about:blank` child and no internal-scheme child observed; directly opening `edge://ntp/` returns `ERR_INVALID_URL`. These observations are for fresh default profiles, not every browser configuration. Brave, Opera and Vivaldi were not installed locally, and this repository's CI installs only Chromium, so their redirects and internal frames were not verified and no additional exceptions are inferred. Incognito Chrome and Edge new tabs omit the ordinary new tab's frame tree; the reachability vector therefore uses the default context.
|
|
124
149
|
|
|
150
|
+
**Privileged-scheme follow-up checks.** With the expanded catalog, fresh default-profile Chrome for Testing 156.0.8078.4 on macOS again committed both NTP entry points to `chrome://new-tab-page/`, with an `about:blank` background child and only `chrome-untrusted://new-tab-page/one-google-bar?paramsencoded=` as its internal-scheme OOPIF. `C8.newtab` passed both attach and ordinary-navigation legs; no `chrome-search` exceptions are added or inferred for other profile/search-provider configurations. Direct `Page.navigate` to `chrome-search://most-visited/` returned `net::ERR_INVALID_URL` and committed `chrome-error://chromewebdata/` with that exact `unreachableUrl`, confirming why `C8.chrome-search` distinguishes a browser response from a guard refusal rather than requiring a successful search-document commit. The catalog-adopting emulator passed every C8 protection row; plain clients failed them, and removing only `chrome-search` from the adopted catalog failed the new row. C1.href, C2.302, C3.popup-self-nav, all tested C4 iframe process layouts and C5.document passed their guarded and control legs with the expanded schemes; plain clients passed controls and failed protection expectations. Those HTTP fixtures deliberately protect only the seven host, leaving their same-site control host ordinary, while adopting every internal-scheme rule unchanged.
|
|
151
|
+
|
|
125
152
|
**The vault.** `vault.sliccy.ai` is in `hosts` by name, and vault branch previews (`vault-preview.sliccy.ai`, `vault-*.sliccy.ai`) are covered by the `sliccy.ai` domain rule. `sliccy.ai` already covers them all, but vault must stay protected even if `domains` ever changes: the vault must never be a CDP target for agent tools. The one exception is slicc-cli's own pairing attach for the vault, which is the CLI's own connection and not browser control handed to the agent.
|
|
126
153
|
|
|
127
154
|
**Targets.** A target whose `TargetInfo.type` is one of `targetTypes` (`browser_ui`, Chrome's own UI such as `chrome://omnibox-popup.top-chrome/`) is always protected, whatever its URL. Otherwise a target's effective origin is its URL's (`TargetInfo.url`), except when the URL inherits the origin of whoever made the document: the empty URL, and `inheriting` with any query or fragment. Such a target is protected when it was protected before this URL (a seven tab that went to `about:blank` keeps seven's origin), or when its opener (`openerId`, else `openerFrameId`) is protected or unknown, for an opener closed before the program saw it. The URL, `openerFrameId` and `browserContextId` alone can't tell: a blank popup that seven opens has seven's origin and the URL `about:blank`. A program that holds a session to the target also has the document's real origin: the main frame's `securityOrigin` in `Page.frameNavigated`, and the default context's `origin` in `Runtime.executionContextCreated`. When one of those is protected, the target is protected too, until a URL with an origin of its own replaces it.
|
|
@@ -143,14 +170,20 @@ Every CDP guard runs the vectors in `guard-vectors.json` (schema `schema/guard-v
|
|
|
143
170
|
- slicc-cdp's `test/integration/guard-vectors.json` at `4907a45`, where its `E1`, `E2`, `E3`, `E5` and `B2` are renamed `E1.onload`, `E2.binding`, `E3.bypass-csp`, `E5.console` and `B2.headers`;
|
|
144
171
|
- the cert lane's E and B rows;
|
|
145
172
|
- `C5.continue-request` and `C5.continue-response`, added next to `C5.document`;
|
|
146
|
-
- the `C8` rows for privileged pages: `C8.navigate` (a page navigates to `chrome://settings`, `chrome://password-manager`, an extension page or `file:///`; refused), `C8.view-source` (the same through `view-source:`; refused), `C8.create` (`Target.createTarget` straight on those pages; refused), `C8.browser-ui-listed` (`Target.getTargets` lists no `browser_ui` target) and `C8.browser-ui-attach` (`Target.attachToTarget` by a `browser_ui` target's id; refused). All
|
|
173
|
+
- the `C8` rows for privileged pages: `C8.navigate` (a page navigates to `chrome://settings`, `chrome://password-manager`, an extension page or `file:///`; refused), `C8.view-source` (the same through `view-source:`; refused), `C8.create` (`Target.createTarget` straight on those pages; refused), `C8.browser-ui-listed` (`Target.getTargets` lists no `browser_ui` target) and `C8.browser-ui-attach` (`Target.attachToTarget` by a `browser_ui` target's id; refused). All five have an unguarded control, since the guard refuses them only on the privileged target. Two cases aren't rows, because Chrome itself blocks them, so an unguarded client can't tell a guard from none: a page's `window.open('chrome://settings')`, and an `about:` alias such as `about:settings`, which `Page.navigate` and `Target.createTarget` both turn into `about:blank#blocked`. The `about:` aliases are in `protected.json` instead.
|
|
174
|
+
|
|
175
|
+
`C8.chrome-search` requests `chrome-search://most-visited/` through `Page.navigate` and requires an explicit guard refusal. Its unguarded control requires a CDP response, not a successful document commit: Chrome can answer with `errorText` when that internal data source is unavailable outside an Instant process. Browser-side load failure is not a guard refusal, and merely hiding the target must fail this row.
|
|
176
|
+
|
|
177
|
+
The `C8.newtab` reachability control attaches through the guard to a settled ordinary new tab, including its internal frames, then navigates that same tab to an ordinary document. Both legs must succeed, including without a guard. It requires `chromeUrlsAttach`, so an extension transport reports it as `skip` with `not-supported:chromeUrlsAttach`. The C8 protection rows expect a refusal on the guarded leg and run their controls unguarded, so they don't require it and still run on extension transports. It uses the default browser context, since an incognito new tab omits the ordinary new tab's internal frames. It covers `chrome://newtab/` and directly opened `chrome://new-tab-page/`.
|
|
178
|
+
|
|
179
|
+
`C8.network-error` and `C8.iframe-error` are ordinary-error reachability controls: request the configured `network-error` URL (default `http://nonexistent.invalid/`) directly or in an ordinary iframe, observe a failed Document request, reattach to the settled tab, evaluate and snapshot its document, then navigate the same tab to an ordinary document. Both control and guarded legs must pass. The failing URL is unchanged on the control, rather than replaced with a healthy origin, and the rows require `browserSession` but not `chromeUrlsAttach`. With Chrome for Testing 156.0.8078.4, scheme-wide `chrome-error` protection made the direct-error row fail both legs because reattachment was refused; removing it passed both rows, with readable DOM and successful recovery. The old emulator still allowed the cross-process iframe parent because the iframe TargetInfo kept the failing HTTP URL, but that layout-specific behavior is not grounds for protecting the error scheme. Plain-client controls and reachability legs also passed both rows.
|
|
147
180
|
|
|
148
|
-
|
|
181
|
+
A client may register `Fetch.enable` patterns naming a protected origin in either of two conformant ways: the guard allows registration but never pauses or forwards protected requests to the client and refuses continue, fulfil or fail calls for request ids it did not hand out; or the guard refuses registration with its declared refusal text. Setup calls explicitly naming a protected placeholder are refusable with `refusal.onlyProtected: true`, and interception-dependent observations depend on that refusal. Wildcard and ordinary-only patterns do not receive this exemption. The ordinary-origin control still runs through the guard and must succeed.
|
|
149
182
|
|
|
150
183
|
**Top level.**
|
|
151
184
|
- `schemaVersion` (1): guards pin it.
|
|
152
185
|
- `setVersion`: the package version. The release flow stamps it into the npm package and the crate (`scripts/set-crate-version.mjs`). In git, and so in the Go module, it reads `0.0.0`; Go code gets the version from the module.
|
|
153
|
-
- `targets`: the names a runner maps to a scheme, host and port: `seven`, `vault`, `vault-x` (a vault branch preview), `apex`, `own` (the client page's origin, `{client}`), `ordinary`, `ordinary-same-site`, `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-extension-page` and `file-root`. Each has a `description` and, for the protected ones, a default `host`. `chrome-newtab` (`chrome://newtab/`), `chrome-new-tab-page` (`chrome://new-tab-page/`), `chrome-settings` (`chrome://settings/`), `chrome-passwords` (`chrome://password-manager/`), `chrome-extension-page` (a page of Chrome's built-in PDF viewer extension) and `file-root` (`file:///`) carry a `url`, which the runner uses unless `targets` maps the name to something else. No other URL in the file is one a runner must use.
|
|
186
|
+
- `targets`: the names a runner maps to a scheme, host and port: `seven`, `vault`, `vault-x` (a vault branch preview), `apex`, `own` (the client page's origin, `{client}`), `ordinary`, `ordinary-same-site`, `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-search`, `network-error`, `chrome-extension-page` and `file-root`. Each has a `description` and, for the protected ones, a default `host`. `chrome-newtab` (`chrome://newtab/`), `chrome-new-tab-page` (`chrome://new-tab-page/`), `chrome-settings` (`chrome://settings/`), `chrome-passwords` (`chrome://password-manager/`), `chrome-search` (`chrome-search://most-visited/`), `network-error` (`http://nonexistent.invalid/`), `chrome-extension-page` (a page of Chrome's built-in PDF viewer extension) and `file-root` (`file:///`) carry a `url`, which the runner uses unless `targets` maps the name to something else. No other URL in the file is one a runner must use.
|
|
154
187
|
- `variants`: the detach variants in ms. `prompt` and `lagging` are documented; a runner may add others, such as `lagging-8s`, and reports the name it ran.
|
|
155
188
|
- `capabilities`: what a transport may lack. `autoAttach` covers `Target.setAutoAttach`, `children` steps and child sessions; `browserSession` covers calls on the browser session; `browserContexts` covers `Target.createBrowserContext` and `Target.disposeBrowserContext`; `chromeUrlsAttach` means the transport can attach to and drive `chrome://` pages. CDP over a DevTools port can; `chrome.debugger` can't, since Chrome refuses it every `chrome://` page, even with `--extensions-on-chrome-urls`.
|
|
156
189
|
- `placeholders`: every `{name}` used in the vectors, with what it stands for. `{client}` is the client page's origin, which the runner maps.
|
|
@@ -160,7 +193,7 @@ The `C8.newtab` reachability control attaches through the guard to a settled ord
|
|
|
160
193
|
- `kind`: `cdp` (runnable steps) or `text` (rows in words, which runners report as `not-run`, never `pass`);
|
|
161
194
|
- `scope`: `any`, or the one guard the vector is for;
|
|
162
195
|
- `setup` and `trigger`, both lists of steps;
|
|
163
|
-
- `targets`: the protected targets it runs against;
|
|
196
|
+
- `targets`: the protected or reachability targets it runs against;
|
|
164
197
|
- `requires?`: the capabilities it needs, the union of its steps' and observations' `requires`. A runner whose transport lacks one reports the vector as `skip` with the reason `not-supported:<capability>`. A row with a `page-or-browser` cookie step that would carry a `browserContextId` reports `not-supported:browserSession` without a browser session, because that parameter is only allowed on the browser target;
|
|
165
198
|
- `observe?`: each observed name, as `{ "means", "dependsOn"?, "requires"?, "eitherWhen"? }`. `dependsOn` lists the refusals the observation depends on (see Refusals). `eitherWhen` is a check: when it holds on the guarded leg, that observation's expectation becomes `"either"` there (see Design-dependent outcomes);
|
|
166
199
|
- `live`: the liveness check;
|
|
@@ -169,7 +202,7 @@ The `C8.newtab` reachability control attaches through the guard to a settled ord
|
|
|
169
202
|
|
|
170
203
|
**Steps** are declarative and transport-neutral. Any step can take `on` (`control` or `guarded`), which runs it on that leg only.
|
|
171
204
|
Any `cdp`, `page` or `children` step can take `requires`, the capabilities it needs: `autoAttach` for `Target.setAutoAttach`, `children` and `session: "child"`, `browserSession` for `session: "browser"`, `browserContexts` for `Target.createBrowserContext` and `Target.disposeBrowserContext`, and `chromeUrlsAttach` for a step that must attach to a `chrome://` page to pass.
|
|
172
|
-
- `{ "cdp": method, "params"?, "session"?, "as"?, "refusable"?, "refusal"? }`: a CDP call. It runs on the page session by default, on the browser with `browser`, on either with `page-or-browser` (the driver uses the page session), or on the current auto-attached target with `child`. `as` names its outcome. `refusable: true` with `refusal: { "match" }` (a regular expression for the error message or code) marks a call that a guard may refuse at registration.
|
|
205
|
+
- `{ "cdp": method, "params"?, "session"?, "as"?, "refusable"?, "refusal"? }`: a CDP call. It runs on the page session by default, on the browser with `browser`, on either with `page-or-browser` (the driver uses the page session), or on the current auto-attached target with `child`. `as` names its outcome. `refusable: true` with `refusal: { "match", "onlyProtected"? }` (a regular expression for the error message or code) marks a call that a guard may refuse at registration.
|
|
173
206
|
- `{ "page": action, … }`:
|
|
174
207
|
- `tab`: a new tab at `url`, in `context`, which becomes the page. `background` defaults to `true`;
|
|
175
208
|
- `navigate`: go to `url` and wait for the load;
|
|
@@ -202,7 +235,7 @@ Popups are observed by `Target.targetCreated` with the opener's `openerId`, or b
|
|
|
202
235
|
|
|
203
236
|
**The control rule.** Every vector has a control on an ordinary origin: the same setup and trigger against an unprotected origin must succeed. A missing or failing control fails the id. With `via: "guard"`, the control runs through the guard. With `via: "unguarded"`, the runner runs setup and trigger through its own CDP against the ordinary origin and against the protected stand-in, and both must show the effect. The runner reports the control's transport as `via`.
|
|
204
237
|
|
|
205
|
-
**Refusals.** Each refusable step has a refusal name: its `as`, or `refused.<i>`, where `i` is its index in the vector's steps, setup then trigger, with each `children` step's steps right after it, counted from 0 before `on` is applied. When a guard refuses the step and the error matches `refusal.match`, the runner sets that name to `true` in the leg's observations. On that leg, an observation whose `dependsOn` lists the name is skipped; every other observation is checked as usual. A guard that refuses the step on every origin passes in one of two ways: through the guard, with the step refused on both legs, or with an unguarded control and the guard's matched refusal on the guarded leg. With `via: "guard"`, a refusal on one leg only fails that leg. The comparison only counts steps that ran on both legs. A step that ran on one leg only (for example a `children` step on the control's popup, when the guarded leg never gets that popup attached) doesn't fail the row by itself, and the row reports it in `ran`, as `{ "refused.5": "control-only" }` or `"guarded-only"`. Observations that depend on such a step follow their `dependsOn` on each leg. A refusable step that fails without a matching refusal, for example with an internal error or a timeout, is not a refusal: it fails the row with `<name> failed without a refusal: <message>`. A step skipped because an earlier step failed therefore never passes through `ran`, and only a matched refusal skips the observations that depend on it. A guard that allows the step must pass its control through the guard.
|
|
238
|
+
**Refusals.** Each refusable step has a refusal name: its `as`, or `refused.<i>`, where `i` is its index in the vector's steps, setup then trigger, with each `children` step's steps right after it, counted from 0 before `on` is applied. When a guard refuses the step and the error matches `refusal.match`, the runner sets that name to `true` in the leg's observations. On that leg, an observation whose `dependsOn` lists the name is skipped; every other observation is checked as usual. A guard that refuses the step on every origin passes in one of two ways: through the guard, with the step refused on both legs, or with an unguarded control and the guard's matched refusal on the guarded leg. With `via: "guard"`, a refusal on one leg only fails that leg, unless the step declares `refusal.onlyProtected: true`: its matched refusal on the guarded leg alone is conformant, and its dependent observations are skipped there. Refusing such a step on the control always fails the control, even if both legs are refused. Without that flag, refusal symmetry is unchanged. The comparison only counts steps that ran on both legs. A step that ran on one leg only (for example a `children` step on the control's popup, when the guarded leg never gets that popup attached) doesn't fail the row by itself, and the row reports it in `ran`, as `{ "refused.5": "control-only" }` or `"guarded-only"`. Observations that depend on such a step follow their `dependsOn` on each leg. A refusable step that fails without a matching refusal, for example with an internal error or a timeout, is not a refusal: it fails the row with `<name> failed without a refusal: <message>`. A step skipped because an earlier step failed therefore never passes through `ran`, and only a matched refusal skips the observations that depend on it. A guard that allows the step must pass its control through the guard.
|
|
206
239
|
|
|
207
240
|
**Liveness.** A runner checks `live` through its own CDP, never through the guard path: either the protected document loaded (title, marker or URL), or the guard returned its own refusal for a refusable step. A failing `live` fails the id.
|
|
208
241
|
|
|
@@ -286,7 +319,7 @@ extension on `chrome.debugger` leaves it out, so `C8.newtab` is skipped with
|
|
|
286
319
|
`not-supported:chromeUrlsAttach`. `capabilities.scope` selects owner-specific rows.
|
|
287
320
|
|
|
288
321
|
Map `ordinary`, `ordinary-same-site`, `seven`, `vault`, `vault-x`, `apex`, `own`
|
|
289
|
-
and `client` to origins. `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-extension-page` and `file-root` default to their `url`.
|
|
322
|
+
and `client` to origins. `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-search`, `network-error`, `chrome-extension-page` and `file-root` default to their `url`.
|
|
290
323
|
`{browserUi}` is the id of one of Chrome's own `browser_ui` targets, which the runner looks up through
|
|
291
324
|
its unguarded observer (or the client) before the row; a row that uses it fails when there is none.
|
|
292
325
|
A vector with `"defaultContext": true` runs in the browser's default context even with the
|
|
@@ -538,7 +571,7 @@ for case in corpus().cases {
|
|
|
538
571
|
Go 1.26, Rust stable with `wasm32-wasip1`, Node ≥ 24.
|
|
539
572
|
- `npm run corpus` regenerates `corpus.json` from `corpus/`; the pre-commit hook and CI fail when it's stale.
|
|
540
573
|
- `npm run validate` checks `catalog.json`, `corpus.json`, `protected.json` and `guard-vectors.json` against the schemas, runs the protected-origin vectors through `protected.mjs`, and checks what a schema can't say: unique ids, bundle ids and names, known Windows roots, rising tiers, compiling patterns, known tokens, tier order, `sevenOpen`, `tabs` (which holds no entry of `sevenTabs`), `six` and `auto` in each `expected.json`, and no port from 9222 to 9229.
|
|
541
|
-
- Every gate prints one verdict line when it passes: `validate: PASS …`, `corpus: PASS <n> cases` (`node scripts/corpus.mjs --check`), `major-guard: PASS v<version> fits <module>`, and `guard-sweep: PASS
|
|
574
|
+
- Every gate prints one verdict line when it passes: `validate: PASS …`, `corpus: PASS <n> cases` (`node scripts/corpus.mjs --check`), `major-guard: PASS v<version> fits <module>`, and `guard-sweep: PASS 13 targets` (`npm run test:guard-sweep`). CI runs each through `scripts/gate.mjs --expect <regex> -- <command>`, which fails when the command exits non-zero or prints no matching line, so a silent or early exit never reads as a pass. `gate.mjs --expect <regex> --in <file>` checks a log instead; the release job uses it on semantic-release's log for the major-guard line. `npm run test:gates` tests the gates, including runs through a symlinked checkout and a symlinked script.
|
|
542
575
|
- `npm run lint`: the slicc lint tools (Biome, no comments, no unit tests in git, `AGENTS.md`), `gofmt`, `go mod tidy -diff`, `go vet`, golangci-lint, `cargo fmt --check` and clippy.
|
|
543
576
|
- `npm test`: `validate`, the Go tests in `test/integration/`, and `cargo test --all-features`.
|
|
544
577
|
- `npm run build`: the Go module, the crate (also for `wasm32-wasip1`), and `npm pack --dry-run`.
|
package/catalog.json
CHANGED
|
@@ -711,6 +711,13 @@
|
|
|
711
711
|
"protectedSchemes": [
|
|
712
712
|
"chrome",
|
|
713
713
|
"chrome-untrusted",
|
|
714
|
+
"chrome-search",
|
|
715
|
+
"chrome-native",
|
|
716
|
+
"chrome-distiller",
|
|
717
|
+
"chrome-experimental-site-token-provider",
|
|
718
|
+
"isolated-app",
|
|
719
|
+
"externalfile",
|
|
720
|
+
"content",
|
|
714
721
|
"chrome-extension",
|
|
715
722
|
"devtools",
|
|
716
723
|
"edge",
|
package/guard-driver.mjs
CHANGED
|
@@ -152,6 +152,32 @@ export function judge(vector, side, observed) {
|
|
|
152
152
|
return { pass: mismatches.length === 0, refused, skipped, mismatches };
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
const ranOn = (observed, name) => typeof observed?.[name] === 'boolean';
|
|
156
|
+
|
|
157
|
+
const refusalRuns = (vector, control, guarded) =>
|
|
158
|
+
Object.fromEntries(
|
|
159
|
+
refusals(vector)
|
|
160
|
+
.filter((name) => control && guarded && ranOn(control, name) !== ranOn(guarded, name))
|
|
161
|
+
.map((name) => [name, ranOn(control, name) ? 'control-only' : 'guarded-only'])
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const protectedRefusals = (vector) =>
|
|
165
|
+
new Set(
|
|
166
|
+
flatten(named([...(vector.setup ?? []), ...(vector.trigger ?? [])]))
|
|
167
|
+
.filter((step) => step.refusable && step.refusal.onlyProtected)
|
|
168
|
+
.map((step) => step.refusal.name)
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
function controlRefusal(c, g, via, only, onlyProtected) {
|
|
172
|
+
if (!c) return 'no control';
|
|
173
|
+
if (c.refused.length > 0 && via !== 'guard') return 'an unguarded control was refused';
|
|
174
|
+
if (c.refused.some((name) => onlyProtected.has(name)))
|
|
175
|
+
return 'a protected-only step was refused on the control';
|
|
176
|
+
if (via === 'guard' && g && only(c, g).length > 0)
|
|
177
|
+
return `refused only on the control: ${only(c, g).join(', ')}`;
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
export function result(vector, run) {
|
|
156
182
|
const row = {
|
|
157
183
|
id: vector.id,
|
|
@@ -173,33 +199,22 @@ export function result(vector, run) {
|
|
|
173
199
|
}
|
|
174
200
|
const c = run.control ? judge(vector, 'control', run.control) : null;
|
|
175
201
|
const g = run.guarded ? judge(vector, 'guarded', run.guarded) : null;
|
|
176
|
-
const ranOn = (observed, name) => typeof observed?.[name] === 'boolean';
|
|
177
202
|
const both = (name) => ranOn(run.control, name) && ranOn(run.guarded, name);
|
|
178
203
|
const only = (a, b) =>
|
|
179
204
|
(a?.refused ?? []).filter((name) => both(name) && !(b?.refused ?? []).includes(name));
|
|
180
|
-
const ran =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
(name) =>
|
|
184
|
-
run.control && run.guarded && ranOn(run.control, name) !== ranOn(run.guarded, name)
|
|
185
|
-
)
|
|
186
|
-
.map((name) => [name, ranOn(run.control, name) ? 'control-only' : 'guarded-only'])
|
|
187
|
-
);
|
|
205
|
+
const ran = refusalRuns(vector, run.control, run.guarded);
|
|
206
|
+
const onlyProtected = protectedRefusals(vector);
|
|
207
|
+
const protectedOnly = only(g, c).filter((name) => !onlyProtected.has(name));
|
|
188
208
|
const why = [];
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
why.push('an unguarded control was refused');
|
|
194
|
-
} else if (row.via === 'guard' && g && only(c, g).length > 0) {
|
|
195
|
-
control = 'fail';
|
|
196
|
-
why.push(`refused only on the control: ${only(c, g).join(', ')}`);
|
|
197
|
-
} else why.push(...c.mismatches.map((m) => `control ${m}`));
|
|
209
|
+
const refusal = controlRefusal(c, g, row.via, only, onlyProtected);
|
|
210
|
+
const control = c?.pass && !refusal ? 'pass' : 'fail';
|
|
211
|
+
if (refusal) why.push(refusal);
|
|
212
|
+
else why.push(...c.mismatches.map((m) => `control ${m}`));
|
|
198
213
|
let guarded = g?.pass ? 'pass' : 'fail';
|
|
199
214
|
if (!g) why.push('no guarded run');
|
|
200
|
-
else if (row.via === 'guard' &&
|
|
215
|
+
else if (row.via === 'guard' && protectedOnly.length > 0) {
|
|
201
216
|
guarded = 'fail';
|
|
202
|
-
why.push(`refused only on the protected target: ${
|
|
217
|
+
why.push(`refused only on the protected target: ${protectedOnly.join(', ')}`);
|
|
203
218
|
} else if (row.via === 'unguarded' && g.refused.length > 0 && !c?.pass) {
|
|
204
219
|
guarded = 'fail';
|
|
205
220
|
why.push('refused on the protected target without a passing control');
|
package/guard-vectors.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.8.
|
|
3
|
+
"setVersion": "1.8.5",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
@@ -61,6 +61,14 @@
|
|
|
61
61
|
"url": "chrome://settings/",
|
|
62
62
|
"description": "Chrome's settings page, a privileged chrome: page"
|
|
63
63
|
},
|
|
64
|
+
"network-error": {
|
|
65
|
+
"url": "http://nonexistent.invalid/",
|
|
66
|
+
"description": "an ordinary URL with a failing DNS lookup, used on both reachability legs"
|
|
67
|
+
},
|
|
68
|
+
"chrome-search": {
|
|
69
|
+
"url": "chrome-search://most-visited/",
|
|
70
|
+
"description": "Chrome's internal search UI, including NTP search-provider resources"
|
|
71
|
+
},
|
|
64
72
|
"chrome-passwords": {
|
|
65
73
|
"url": "chrome://password-manager/",
|
|
66
74
|
"description": "Chrome's password manager, a privileged chrome: page"
|
|
@@ -94,6 +102,7 @@
|
|
|
94
102
|
"own": "the own target's origin, which is {client}",
|
|
95
103
|
"ordinary": "the ordinary origin the run starts from",
|
|
96
104
|
"ordinary-same-site": "the ordinary-same-site origin",
|
|
105
|
+
"network-error": "the ordinary failing-network URL, unchanged on the control",
|
|
97
106
|
"client": "the client page's origin, which the runner maps",
|
|
98
107
|
"cross": "another ordinary origin, cross-site with {ordinary}",
|
|
99
108
|
"plain": "an ordinary destination origin, the control target",
|
|
@@ -7806,6 +7815,11 @@
|
|
|
7806
7815
|
"requestStage": "Response"
|
|
7807
7816
|
}
|
|
7808
7817
|
]
|
|
7818
|
+
},
|
|
7819
|
+
"refusable": true,
|
|
7820
|
+
"refusal": {
|
|
7821
|
+
"match": "^slicc: refused",
|
|
7822
|
+
"onlyProtected": true
|
|
7809
7823
|
}
|
|
7810
7824
|
},
|
|
7811
7825
|
{
|
|
@@ -7875,6 +7889,7 @@
|
|
|
7875
7889
|
"targets": ["seven", "vault", "vault-x", "apex"],
|
|
7876
7890
|
"observe": {
|
|
7877
7891
|
"candidates": {
|
|
7892
|
+
"dependsOn": ["refused.2"],
|
|
7878
7893
|
"means": "attempts is not empty",
|
|
7879
7894
|
"check": {
|
|
7880
7895
|
"result": "attempts",
|
|
@@ -7886,6 +7901,7 @@
|
|
|
7886
7901
|
}
|
|
7887
7902
|
},
|
|
7888
7903
|
"refused": {
|
|
7904
|
+
"dependsOn": ["refused.2"],
|
|
7889
7905
|
"means": "every attempt was refused",
|
|
7890
7906
|
"check": {
|
|
7891
7907
|
"refused": ["attempts"],
|
|
@@ -7893,6 +7909,7 @@
|
|
|
7893
7909
|
}
|
|
7894
7910
|
},
|
|
7895
7911
|
"answered": {
|
|
7912
|
+
"dependsOn": ["refused.2"],
|
|
7896
7913
|
"means": "body is 'real-{tag}'",
|
|
7897
7914
|
"check": {
|
|
7898
7915
|
"result": "body",
|
|
@@ -7902,6 +7919,7 @@
|
|
|
7902
7919
|
}
|
|
7903
7920
|
},
|
|
7904
7921
|
"overridden": {
|
|
7922
|
+
"dependsOn": ["refused.2"],
|
|
7905
7923
|
"means": "the fetch reported the overridden response status",
|
|
7906
7924
|
"check": {
|
|
7907
7925
|
"result": "body",
|
|
@@ -12442,6 +12460,376 @@
|
|
|
12442
12460
|
"observer": "unguarded"
|
|
12443
12461
|
}
|
|
12444
12462
|
},
|
|
12463
|
+
{
|
|
12464
|
+
"id": "C8.network-error",
|
|
12465
|
+
"family": "C",
|
|
12466
|
+
"kind": "cdp",
|
|
12467
|
+
"scope": "any",
|
|
12468
|
+
"setup": [
|
|
12469
|
+
{
|
|
12470
|
+
"text": "an ordinary tab hits a network error; the error document must remain readable, attachable and navigable"
|
|
12471
|
+
},
|
|
12472
|
+
{
|
|
12473
|
+
"page": "tab",
|
|
12474
|
+
"url": "{ordinary}/doc?tag={tag}"
|
|
12475
|
+
},
|
|
12476
|
+
{
|
|
12477
|
+
"cdp": "Page.enable"
|
|
12478
|
+
},
|
|
12479
|
+
{
|
|
12480
|
+
"cdp": "Network.enable"
|
|
12481
|
+
}
|
|
12482
|
+
],
|
|
12483
|
+
"trigger": [
|
|
12484
|
+
{
|
|
12485
|
+
"page": "navigate",
|
|
12486
|
+
"url": "{network-error}"
|
|
12487
|
+
},
|
|
12488
|
+
{
|
|
12489
|
+
"settle": {
|
|
12490
|
+
"ms": 2500
|
|
12491
|
+
}
|
|
12492
|
+
},
|
|
12493
|
+
{
|
|
12494
|
+
"cdp": "Target.attachToTarget",
|
|
12495
|
+
"params": {
|
|
12496
|
+
"targetId": "{pageTarget}",
|
|
12497
|
+
"flatten": true
|
|
12498
|
+
},
|
|
12499
|
+
"session": "browser",
|
|
12500
|
+
"as": "attach",
|
|
12501
|
+
"requires": ["browserSession"]
|
|
12502
|
+
},
|
|
12503
|
+
{
|
|
12504
|
+
"page": "eval",
|
|
12505
|
+
"expression": "Boolean(document.body)",
|
|
12506
|
+
"as": "read"
|
|
12507
|
+
},
|
|
12508
|
+
{
|
|
12509
|
+
"cdp": "DOM.getDocument",
|
|
12510
|
+
"params": {
|
|
12511
|
+
"depth": 1
|
|
12512
|
+
},
|
|
12513
|
+
"as": "snapshot"
|
|
12514
|
+
},
|
|
12515
|
+
{
|
|
12516
|
+
"page": "navigate",
|
|
12517
|
+
"url": "{ordinary}/doc?tag={tag}",
|
|
12518
|
+
"as": "navigate"
|
|
12519
|
+
},
|
|
12520
|
+
{
|
|
12521
|
+
"settle": {
|
|
12522
|
+
"title": "doc",
|
|
12523
|
+
"ms": 5000,
|
|
12524
|
+
"required": true
|
|
12525
|
+
}
|
|
12526
|
+
}
|
|
12527
|
+
],
|
|
12528
|
+
"targets": ["network-error"],
|
|
12529
|
+
"requires": ["browserSession"],
|
|
12530
|
+
"observe": {
|
|
12531
|
+
"errorSeen": {
|
|
12532
|
+
"means": "Chrome reported a failed ordinary document request before the tab navigated away",
|
|
12533
|
+
"check": {
|
|
12534
|
+
"event": "Network.loadingFailed",
|
|
12535
|
+
"where": {
|
|
12536
|
+
"params": {
|
|
12537
|
+
"type": "Document",
|
|
12538
|
+
"errorText": {
|
|
12539
|
+
"$regex": "^net::ERR_"
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
},
|
|
12543
|
+
"observer": "client"
|
|
12544
|
+
}
|
|
12545
|
+
},
|
|
12546
|
+
"attached": {
|
|
12547
|
+
"means": "the client can reattach to the settled ordinary error tab, including an error iframe",
|
|
12548
|
+
"check": {
|
|
12549
|
+
"result": "attach",
|
|
12550
|
+
"path": "ok",
|
|
12551
|
+
"equals": true,
|
|
12552
|
+
"observer": "client"
|
|
12553
|
+
}
|
|
12554
|
+
},
|
|
12555
|
+
"readable": {
|
|
12556
|
+
"means": "the client can evaluate the error document or its ordinary iframe parent",
|
|
12557
|
+
"check": {
|
|
12558
|
+
"result": "read",
|
|
12559
|
+
"path": "result.result.value",
|
|
12560
|
+
"equals": true,
|
|
12561
|
+
"observer": "client"
|
|
12562
|
+
}
|
|
12563
|
+
},
|
|
12564
|
+
"snapshotted": {
|
|
12565
|
+
"means": "the client can snapshot the error document or its ordinary iframe parent",
|
|
12566
|
+
"check": {
|
|
12567
|
+
"result": "snapshot",
|
|
12568
|
+
"path": "result.root.nodeName",
|
|
12569
|
+
"equals": "#document",
|
|
12570
|
+
"observer": "client"
|
|
12571
|
+
}
|
|
12572
|
+
},
|
|
12573
|
+
"navigated": {
|
|
12574
|
+
"means": "the same tab reached an ordinary document through the client",
|
|
12575
|
+
"check": {
|
|
12576
|
+
"title": "doc",
|
|
12577
|
+
"via": "unguarded",
|
|
12578
|
+
"observer": "unguarded"
|
|
12579
|
+
}
|
|
12580
|
+
}
|
|
12581
|
+
},
|
|
12582
|
+
"live": "the client sees an ordinary network error and the observer sees recovery to an ordinary document",
|
|
12583
|
+
"control": {
|
|
12584
|
+
"origin": "ordinary",
|
|
12585
|
+
"via": "guard",
|
|
12586
|
+
"expected": {
|
|
12587
|
+
"errorSeen": true,
|
|
12588
|
+
"attached": true,
|
|
12589
|
+
"readable": true,
|
|
12590
|
+
"snapshotted": true,
|
|
12591
|
+
"navigated": true
|
|
12592
|
+
}
|
|
12593
|
+
},
|
|
12594
|
+
"expected": {
|
|
12595
|
+
"errorSeen": true,
|
|
12596
|
+
"attached": true,
|
|
12597
|
+
"readable": true,
|
|
12598
|
+
"snapshotted": true,
|
|
12599
|
+
"navigated": true
|
|
12600
|
+
},
|
|
12601
|
+
"liveCheck": {
|
|
12602
|
+
"title": "doc",
|
|
12603
|
+
"via": "unguarded",
|
|
12604
|
+
"observer": "unguarded"
|
|
12605
|
+
}
|
|
12606
|
+
},
|
|
12607
|
+
{
|
|
12608
|
+
"id": "C8.iframe-error",
|
|
12609
|
+
"family": "C",
|
|
12610
|
+
"kind": "cdp",
|
|
12611
|
+
"scope": "any",
|
|
12612
|
+
"setup": [
|
|
12613
|
+
{
|
|
12614
|
+
"text": "an ordinary tab embeds an ordinary iframe whose network request fails; its parent must remain readable, attachable and navigable"
|
|
12615
|
+
},
|
|
12616
|
+
{
|
|
12617
|
+
"page": "tab",
|
|
12618
|
+
"url": "{ordinary}/doc?tag={tag}"
|
|
12619
|
+
},
|
|
12620
|
+
{
|
|
12621
|
+
"cdp": "Page.enable"
|
|
12622
|
+
},
|
|
12623
|
+
{
|
|
12624
|
+
"cdp": "Network.enable"
|
|
12625
|
+
}
|
|
12626
|
+
],
|
|
12627
|
+
"trigger": [
|
|
12628
|
+
{
|
|
12629
|
+
"page": "framed",
|
|
12630
|
+
"from": "{ordinary}",
|
|
12631
|
+
"url": "{network-error}"
|
|
12632
|
+
},
|
|
12633
|
+
{
|
|
12634
|
+
"settle": {
|
|
12635
|
+
"ms": 2500
|
|
12636
|
+
}
|
|
12637
|
+
},
|
|
12638
|
+
{
|
|
12639
|
+
"cdp": "Target.attachToTarget",
|
|
12640
|
+
"params": {
|
|
12641
|
+
"targetId": "{pageTarget}",
|
|
12642
|
+
"flatten": true
|
|
12643
|
+
},
|
|
12644
|
+
"session": "browser",
|
|
12645
|
+
"as": "attach",
|
|
12646
|
+
"requires": ["browserSession"]
|
|
12647
|
+
},
|
|
12648
|
+
{
|
|
12649
|
+
"page": "eval",
|
|
12650
|
+
"expression": "Boolean(document.body)",
|
|
12651
|
+
"as": "read"
|
|
12652
|
+
},
|
|
12653
|
+
{
|
|
12654
|
+
"cdp": "DOM.getDocument",
|
|
12655
|
+
"params": {
|
|
12656
|
+
"depth": 1
|
|
12657
|
+
},
|
|
12658
|
+
"as": "snapshot"
|
|
12659
|
+
},
|
|
12660
|
+
{
|
|
12661
|
+
"page": "navigate",
|
|
12662
|
+
"url": "{ordinary}/doc?tag={tag}",
|
|
12663
|
+
"as": "navigate"
|
|
12664
|
+
},
|
|
12665
|
+
{
|
|
12666
|
+
"settle": {
|
|
12667
|
+
"title": "doc",
|
|
12668
|
+
"ms": 5000,
|
|
12669
|
+
"required": true
|
|
12670
|
+
}
|
|
12671
|
+
}
|
|
12672
|
+
],
|
|
12673
|
+
"targets": ["network-error"],
|
|
12674
|
+
"requires": ["browserSession"],
|
|
12675
|
+
"observe": {
|
|
12676
|
+
"errorSeen": {
|
|
12677
|
+
"means": "Chrome reported a failed ordinary document request before the tab navigated away",
|
|
12678
|
+
"check": {
|
|
12679
|
+
"event": "Network.loadingFailed",
|
|
12680
|
+
"where": {
|
|
12681
|
+
"params": {
|
|
12682
|
+
"type": "Document",
|
|
12683
|
+
"errorText": {
|
|
12684
|
+
"$regex": "^net::ERR_"
|
|
12685
|
+
}
|
|
12686
|
+
}
|
|
12687
|
+
},
|
|
12688
|
+
"observer": "client"
|
|
12689
|
+
}
|
|
12690
|
+
},
|
|
12691
|
+
"attached": {
|
|
12692
|
+
"means": "the client can reattach to the settled ordinary error tab, including an error iframe",
|
|
12693
|
+
"check": {
|
|
12694
|
+
"result": "attach",
|
|
12695
|
+
"path": "ok",
|
|
12696
|
+
"equals": true,
|
|
12697
|
+
"observer": "client"
|
|
12698
|
+
}
|
|
12699
|
+
},
|
|
12700
|
+
"readable": {
|
|
12701
|
+
"means": "the client can evaluate the error document or its ordinary iframe parent",
|
|
12702
|
+
"check": {
|
|
12703
|
+
"result": "read",
|
|
12704
|
+
"path": "result.result.value",
|
|
12705
|
+
"equals": true,
|
|
12706
|
+
"observer": "client"
|
|
12707
|
+
}
|
|
12708
|
+
},
|
|
12709
|
+
"snapshotted": {
|
|
12710
|
+
"means": "the client can snapshot the error document or its ordinary iframe parent",
|
|
12711
|
+
"check": {
|
|
12712
|
+
"result": "snapshot",
|
|
12713
|
+
"path": "result.root.nodeName",
|
|
12714
|
+
"equals": "#document",
|
|
12715
|
+
"observer": "client"
|
|
12716
|
+
}
|
|
12717
|
+
},
|
|
12718
|
+
"navigated": {
|
|
12719
|
+
"means": "the same tab reached an ordinary document through the client",
|
|
12720
|
+
"check": {
|
|
12721
|
+
"title": "doc",
|
|
12722
|
+
"via": "unguarded",
|
|
12723
|
+
"observer": "unguarded"
|
|
12724
|
+
}
|
|
12725
|
+
}
|
|
12726
|
+
},
|
|
12727
|
+
"live": "the client sees an ordinary network error and the observer sees recovery to an ordinary document",
|
|
12728
|
+
"control": {
|
|
12729
|
+
"origin": "ordinary",
|
|
12730
|
+
"via": "guard",
|
|
12731
|
+
"expected": {
|
|
12732
|
+
"errorSeen": true,
|
|
12733
|
+
"attached": true,
|
|
12734
|
+
"readable": true,
|
|
12735
|
+
"snapshotted": true,
|
|
12736
|
+
"navigated": true
|
|
12737
|
+
}
|
|
12738
|
+
},
|
|
12739
|
+
"expected": {
|
|
12740
|
+
"errorSeen": true,
|
|
12741
|
+
"attached": true,
|
|
12742
|
+
"readable": true,
|
|
12743
|
+
"snapshotted": true,
|
|
12744
|
+
"navigated": true
|
|
12745
|
+
},
|
|
12746
|
+
"liveCheck": {
|
|
12747
|
+
"title": "doc",
|
|
12748
|
+
"via": "unguarded",
|
|
12749
|
+
"observer": "unguarded"
|
|
12750
|
+
}
|
|
12751
|
+
},
|
|
12752
|
+
{
|
|
12753
|
+
"id": "C8.chrome-search",
|
|
12754
|
+
"family": "C",
|
|
12755
|
+
"kind": "cdp",
|
|
12756
|
+
"scope": "any",
|
|
12757
|
+
"defaultContext": true,
|
|
12758
|
+
"setup": [
|
|
12759
|
+
{
|
|
12760
|
+
"text": "a plain tab requests an internal search UI URL; Chrome may return an unavailable data-source error, but the guard must refuse before forwarding"
|
|
12761
|
+
},
|
|
12762
|
+
{
|
|
12763
|
+
"page": "tab",
|
|
12764
|
+
"url": "{ordinary}/doc?n={tag}"
|
|
12765
|
+
},
|
|
12766
|
+
{
|
|
12767
|
+
"cdp": "Page.enable"
|
|
12768
|
+
}
|
|
12769
|
+
],
|
|
12770
|
+
"trigger": [
|
|
12771
|
+
{
|
|
12772
|
+
"cdp": "Page.navigate",
|
|
12773
|
+
"params": {
|
|
12774
|
+
"url": "{target}"
|
|
12775
|
+
},
|
|
12776
|
+
"as": "nav",
|
|
12777
|
+
"refusable": true,
|
|
12778
|
+
"refusal": {
|
|
12779
|
+
"match": "refused"
|
|
12780
|
+
}
|
|
12781
|
+
}
|
|
12782
|
+
],
|
|
12783
|
+
"targets": ["chrome-search"],
|
|
12784
|
+
"observe": {
|
|
12785
|
+
"accepted": {
|
|
12786
|
+
"means": "Chrome answered Page.navigate rather than a guard refusal, even if its internal data source is unavailable",
|
|
12787
|
+
"dependsOn": ["nav"],
|
|
12788
|
+
"check": {
|
|
12789
|
+
"result": "nav",
|
|
12790
|
+
"path": "ok",
|
|
12791
|
+
"equals": true,
|
|
12792
|
+
"observer": "client"
|
|
12793
|
+
}
|
|
12794
|
+
},
|
|
12795
|
+
"refused": {
|
|
12796
|
+
"means": "the navigation was refused",
|
|
12797
|
+
"dependsOn": [],
|
|
12798
|
+
"check": {
|
|
12799
|
+
"refused": ["nav"],
|
|
12800
|
+
"observer": "client"
|
|
12801
|
+
}
|
|
12802
|
+
}
|
|
12803
|
+
},
|
|
12804
|
+
"live": "the guard returned its explicit refusal, or Chrome answered the forwarded navigation request",
|
|
12805
|
+
"control": {
|
|
12806
|
+
"origin": "ordinary",
|
|
12807
|
+
"via": "unguarded",
|
|
12808
|
+
"expected": {
|
|
12809
|
+
"accepted": true,
|
|
12810
|
+
"refused": false
|
|
12811
|
+
}
|
|
12812
|
+
},
|
|
12813
|
+
"expected": {
|
|
12814
|
+
"accepted": false,
|
|
12815
|
+
"refused": true
|
|
12816
|
+
},
|
|
12817
|
+
"liveCheck": {
|
|
12818
|
+
"any": [
|
|
12819
|
+
{
|
|
12820
|
+
"refused": ["nav"],
|
|
12821
|
+
"observer": "client"
|
|
12822
|
+
},
|
|
12823
|
+
{
|
|
12824
|
+
"result": "nav",
|
|
12825
|
+
"path": "ok",
|
|
12826
|
+
"equals": true,
|
|
12827
|
+
"observer": "client"
|
|
12828
|
+
}
|
|
12829
|
+
],
|
|
12830
|
+
"observer": "client"
|
|
12831
|
+
}
|
|
12832
|
+
},
|
|
12445
12833
|
{
|
|
12446
12834
|
"id": "C8.navigate",
|
|
12447
12835
|
"family": "C",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.5",
|
|
4
4
|
"description": "SLICC's shared browser catalog, JSON schemas and discovery conformance corpus",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -65,14 +65,14 @@
|
|
|
65
65
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
66
66
|
"release": "semantic-release",
|
|
67
67
|
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
68
|
-
"test:guard-integration": "node --test test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-scales.test.mjs",
|
|
68
|
+
"test:guard-integration": "node --test test/integration/guard-fit.test.mjs test/integration/guard-chain.test.mjs test/integration/guard-validation.test.mjs test/integration/guard-liveness.test.mjs test/integration/guard-observations.test.mjs test/integration/guard-child-latency.test.mjs test/integration/guard-adoption.test.mjs test/integration/guard-scoping.test.mjs test/integration/guard-late-pause.test.mjs test/integration/guard-legs.test.mjs test/integration/guard-fetch-enable.test.mjs test/integration/guard-diagnostics.test.mjs test/integration/guard-chrome.test.mjs test/integration/guard-scales.test.mjs",
|
|
69
69
|
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
70
70
|
"test:gates": "node --test test/integration/gates.test.mjs",
|
|
71
71
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
72
72
|
"test:unit:guard": "node --experimental-test-coverage --test --test-coverage-include=guard-runner.mjs --test-coverage-include=fixture-server.mjs --test-coverage-lines=100 test/unit/guard-*.test.mjs"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
75
|
+
"@ai-ecoverse/slicc-shared-web": "1.24.2",
|
|
76
76
|
"@biomejs/biome": "2.5.15",
|
|
77
77
|
"ajv": "8.20.0",
|
|
78
78
|
"ajv-formats": "3.0.1",
|
package/protected.json
CHANGED
|
@@ -326,6 +326,146 @@
|
|
|
326
326
|
"url": "chrome://settings/",
|
|
327
327
|
"protected": true
|
|
328
328
|
},
|
|
329
|
+
{
|
|
330
|
+
"url": "chrome-search://local-ntp/",
|
|
331
|
+
"protected": true
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"url": "view-source:chrome-search://local-ntp/",
|
|
335
|
+
"protected": true
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"url": "https://ordinary.example.test/?next=chrome-search%3A%2F%2Flocal-ntp%2F",
|
|
339
|
+
"protected": false
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"url": "chrome-search://most-visited/",
|
|
343
|
+
"protected": true
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"url": "view-source:chrome-search://most-visited/",
|
|
347
|
+
"protected": true
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"url": "https://ordinary.example.test/?next=chrome-search%3A%2F%2Fmost-visited%2F",
|
|
351
|
+
"protected": false
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"url": "chrome-search://remote-ntp/",
|
|
355
|
+
"protected": true
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"url": "view-source:chrome-search://remote-ntp/",
|
|
359
|
+
"protected": true
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"url": "https://ordinary.example.test/?next=chrome-search%3A%2F%2Fremote-ntp%2F",
|
|
363
|
+
"protected": false
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"url": "chrome-search://new-tab-page/",
|
|
367
|
+
"protected": true
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"url": "view-source:chrome-search://new-tab-page/",
|
|
371
|
+
"protected": true
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"url": "https://ordinary.example.test/?next=chrome-search%3A%2F%2Fnew-tab-page%2F",
|
|
375
|
+
"protected": false
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"url": "chrome-native://newtab/",
|
|
379
|
+
"protected": true
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"url": "view-source:chrome-native://newtab/",
|
|
383
|
+
"protected": true
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"url": "https://ordinary.example.test/?next=chrome-native%3A%2F%2Fnewtab%2F",
|
|
387
|
+
"protected": false
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"url": "chrome-distiller://reader/?url=https://example.test/",
|
|
391
|
+
"protected": true
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"url": "view-source:chrome-distiller://reader/?url=https://example.test/",
|
|
395
|
+
"protected": true
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"url": "https://ordinary.example.test/?next=chrome-distiller%3A%2F%2Freader%2F%3Furl%3Dhttps%3A%2F%2Fexample.test%2F",
|
|
399
|
+
"protected": false
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"url": "chrome-error://chromewebdata/",
|
|
403
|
+
"protected": false
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"url": "view-source:chrome-error://chromewebdata/",
|
|
407
|
+
"protected": false
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"url": "https://ordinary.example.test/?next=chrome-error%3A%2F%2Fchromewebdata%2F",
|
|
411
|
+
"protected": false
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"url": "chrome-experimental-site-token-provider://token/",
|
|
415
|
+
"protected": true
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"url": "view-source:chrome-experimental-site-token-provider://token/",
|
|
419
|
+
"protected": true
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"url": "https://ordinary.example.test/?next=chrome-experimental-site-token-provider%3A%2F%2Ftoken%2F",
|
|
423
|
+
"protected": false
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"url": "isolated-app://app-id/",
|
|
427
|
+
"protected": true
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
"url": "view-source:isolated-app://app-id/",
|
|
431
|
+
"protected": true
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"url": "https://ordinary.example.test/?next=isolated-app%3A%2F%2Fapp-id%2F",
|
|
435
|
+
"protected": false
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"url": "externalfile://downloads/private.txt",
|
|
439
|
+
"protected": true
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"url": "view-source:externalfile://downloads/private.txt",
|
|
443
|
+
"protected": true
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"url": "https://ordinary.example.test/?next=externalfile%3A%2F%2Fdownloads%2Fprivate.txt",
|
|
447
|
+
"protected": false
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
"url": "content://provider/private.txt",
|
|
451
|
+
"protected": true
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"url": "view-source:content://provider/private.txt",
|
|
455
|
+
"protected": true
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"url": "https://ordinary.example.test/?next=content%3A%2F%2Fprovider%2Fprivate.txt",
|
|
459
|
+
"protected": false
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"url": "android-app://com.example/https/example.test/",
|
|
463
|
+
"protected": false
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"url": "googlechrome://example.test/",
|
|
467
|
+
"protected": false
|
|
468
|
+
},
|
|
329
469
|
{
|
|
330
470
|
"url": "chrome://settings/passwords",
|
|
331
471
|
"protected": true
|
|
@@ -516,6 +656,86 @@
|
|
|
516
656
|
}
|
|
517
657
|
],
|
|
518
658
|
"targets": [
|
|
659
|
+
{
|
|
660
|
+
"url": "chrome-search://private/",
|
|
661
|
+
"type": "iframe",
|
|
662
|
+
"protected": true
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
"url": "https://ordinary.example.test/chrome-search",
|
|
666
|
+
"type": "page",
|
|
667
|
+
"protected": false
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
"url": "chrome-native://private/",
|
|
671
|
+
"type": "iframe",
|
|
672
|
+
"protected": true
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
"url": "https://ordinary.example.test/chrome-native",
|
|
676
|
+
"type": "page",
|
|
677
|
+
"protected": false
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"url": "chrome-distiller://private/",
|
|
681
|
+
"type": "iframe",
|
|
682
|
+
"protected": true
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"url": "https://ordinary.example.test/chrome-distiller",
|
|
686
|
+
"type": "page",
|
|
687
|
+
"protected": false
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
"url": "chrome-error://private/",
|
|
691
|
+
"type": "iframe",
|
|
692
|
+
"protected": false
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
"url": "https://ordinary.example.test/chrome-error",
|
|
696
|
+
"type": "page",
|
|
697
|
+
"protected": false
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
"url": "chrome-experimental-site-token-provider://private/",
|
|
701
|
+
"type": "iframe",
|
|
702
|
+
"protected": true
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
"url": "https://ordinary.example.test/chrome-experimental-site-token-provider",
|
|
706
|
+
"type": "page",
|
|
707
|
+
"protected": false
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"url": "isolated-app://private/",
|
|
711
|
+
"type": "iframe",
|
|
712
|
+
"protected": true
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
"url": "https://ordinary.example.test/isolated-app",
|
|
716
|
+
"type": "page",
|
|
717
|
+
"protected": false
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
"url": "externalfile://private/",
|
|
721
|
+
"type": "iframe",
|
|
722
|
+
"protected": true
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
"url": "https://ordinary.example.test/externalfile",
|
|
726
|
+
"type": "page",
|
|
727
|
+
"protected": false
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
"url": "content://private/",
|
|
731
|
+
"type": "iframe",
|
|
732
|
+
"protected": true
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
"url": "https://ordinary.example.test/content",
|
|
736
|
+
"type": "page",
|
|
737
|
+
"protected": false
|
|
738
|
+
},
|
|
519
739
|
{
|
|
520
740
|
"url": "https://seven.sliccy.ai/",
|
|
521
741
|
"protected": true
|
|
@@ -170,6 +170,8 @@
|
|
|
170
170
|
"chrome-new-tab-page",
|
|
171
171
|
"chrome-settings",
|
|
172
172
|
"chrome-passwords",
|
|
173
|
+
"chrome-search",
|
|
174
|
+
"network-error",
|
|
173
175
|
"chrome-extension-page",
|
|
174
176
|
"file-root"
|
|
175
177
|
]
|
|
@@ -456,6 +458,9 @@
|
|
|
456
458
|
"additionalProperties": false,
|
|
457
459
|
"required": ["match"],
|
|
458
460
|
"properties": {
|
|
461
|
+
"onlyProtected": {
|
|
462
|
+
"type": "boolean"
|
|
463
|
+
},
|
|
459
464
|
"match": {
|
|
460
465
|
"type": "string",
|
|
461
466
|
"minLength": 1
|