@ai-ecoverse/slicc-browsers 1.7.11 → 1.8.0
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 +24 -9
- package/catalog.json +15 -0
- package/guard-runner.mjs +51 -8
- package/guard-vectors.json +412 -2
- package/package.json +2 -2
- package/protected.json +213 -4
- package/protected.mjs +11 -2
- package/schema/catalog.schema.json +16 -0
- package/schema/guard-vectors.schema.json +18 -1
- package/schema/protected.schema.json +1 -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`), `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-extension`, `devtools`, `edge`, `brave`, `opera`, `vivaldi`, `arc`, `file`) with `schemeExceptions` (`chrome://newtab`, `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`.
|
|
@@ -109,14 +109,20 @@ Browser control must never navigate to, open or attach to a target on seven's ow
|
|
|
109
109
|
**URLs.** A URL is parsed the way the WHATWG URL Standard (and so the browser) parses it, so `https:\\seven.sliccy.ai`, a tab or newline inside the URL, `%2E`, `。` and full-width letters all reach seven. In Go and Rust that takes a WHATWG parser, not `net/url`. A URL that doesn't parse isn't protected; the browser doesn't load it either. Then:
|
|
110
110
|
1. While its scheme is one of `innerSchemes`, the rest of the URL after `<scheme>:` takes its place, at most 8 times: `blob:https://seven.sliccy.ai/<uuid>` is `https://seven.sliccy.ai/<uuid>`.
|
|
111
111
|
2. A scheme in `anySchemes` is protected whatever follows: a DevTools front end (`devtools://`) can be inspecting seven.
|
|
112
|
-
3. A scheme in `
|
|
113
|
-
|
|
112
|
+
3. A scheme in `protectedSchemes` is protected like seven, whatever follows, except the `scheme://host` pairs in `schemeExceptions` (the new tab pages), on any path:
|
|
113
|
+
- Chrome's privileged UI (`chrome://settings`, `chrome://password-manager`, `chrome-untrusted://…`, `devtools://`) and the same UI in the catalog's other Chromium browsers (`edge://settings/passwords`, `edge://wallet`, `brave://`, `opera://`, `vivaldi://`, `arc://`), since a client attaches to whatever browser answers;
|
|
114
|
+
- `chrome-extension://`, every page, frame and extension target of every type: password-manager and wallet extensions keep secrets there. There are no default exceptions. Seven's own extension origin is already covered as a seven origin. If a guard ever needs an exception, it is a catalog entry by extension id with a reason, never a default;
|
|
115
|
+
- `file://`: otherwise a tool could open `~/.ssh` or `~/.aws/credentials` in the real browser, around slicc's file-system fence. Adopters whose tests open local HTML through the guarded client move those fixtures to a local http server.
|
|
116
|
+
|
|
117
|
+
An `about:` URL other than `inheriting` is first rewritten the way Chromium rewrites it, to `aboutScheme://<path>`: `about:settings` is `chrome://settings` and protected, `about:newtab` is `chrome://newtab`, and `about:blank` stays unprotected. A wrapper is judged by what it wraps (rule 1): `view-source:chrome://settings/` and `view-source:file:///…` are protected, and `blob:` and `filesystem:` URLs by their creator's origin.
|
|
118
|
+
4. A scheme in `schemes` is protected when its host is one of `hosts`, or one of `domains` or under one, at any depth and on any port. The host is lower-cased, and one trailing dot is dropped.
|
|
119
|
+
5. Any other URL is protected when its origin is one of the program's **extra origins**: the seven origins it accepts besides `sliccy.ai` (slicc-cli's `--origin` and `--page` origins), compared as `scheme://host[:port]` without the scheme's default port, with the host as in 3.
|
|
114
120
|
|
|
115
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`.
|
|
116
122
|
|
|
117
123
|
**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.
|
|
118
124
|
|
|
119
|
-
**Targets.** 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.
|
|
125
|
+
**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.
|
|
120
126
|
|
|
121
127
|
A same-site frame shares its parent's process and has no target of its own. Every `sliccy.ai` host is protected, and site isolation gives a seven frame inside any other site its own target, so this only matters for an extra origin whose site has other origins (another port of `127.0.0.1`, say).
|
|
122
128
|
|
|
@@ -126,7 +132,7 @@ A same-site frame shares its parent's process and has no target of its own. Ever
|
|
|
126
132
|
|
|
127
133
|
**Refusals** carry the text in `refusal`, `slicc: refused: target is on seven's origin`.
|
|
128
134
|
|
|
129
|
-
**`protected.json`** (schema `schema/protected.schema.json`): `origins`, the extra origins every vector runs with; `urls` (`url`, `protected`); `targets` (`url`, `previous?`, whether the target was protected before; `opener?`, `protected`, `unprotected` or `unknown`, and no `opener` for a target without one; `protected`); `cookieDomains` (`domain`, `protected`); `storageKeys` (`key`, `protected`). `npm run validate` runs every vector through `protected.mjs`, and no vector uses a port from 9222 to 9229.
|
|
135
|
+
**`protected.json`** (schema `schema/protected.schema.json`): `origins`, the extra origins every vector runs with; `urls` (`url`, `protected`); `targets` (`url`, `type?`, the target's `TargetInfo.type`; `previous?`, whether the target was protected before; `opener?`, `protected`, `unprotected` or `unknown`, and no `opener` for a target without one; `protected`); `cookieDomains` (`domain`, `protected`); `storageKeys` (`key`, `protected`). `npm run validate` runs every vector through `protected.mjs`, and no vector uses a port from 9222 to 9229.
|
|
130
136
|
|
|
131
137
|
## Guard vectors
|
|
132
138
|
|
|
@@ -134,12 +140,13 @@ Every CDP guard runs the vectors in `guard-vectors.json` (schema `schema/guard-v
|
|
|
134
140
|
- slicc-cli's `protectVectors` at `2d5d981` (`test/integration/testdata/link/protect.js`), with the ids kept, and at `fe33052` for `C3.redirect-popup-stays-out` and `C3.direct-popup-stays-out`: a popup opened straight onto, or through a redirect chain to, a protected origin never reaches the client, not at open and not after the page later navigates itself and auto-attach runs again;
|
|
135
141
|
- 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`;
|
|
136
142
|
- the cert lane's E and B rows;
|
|
137
|
-
- `C5.continue-request` and `C5.continue-response`, added next to `C5.document
|
|
143
|
+
- `C5.continue-request` and `C5.continue-response`, added next to `C5.document`;
|
|
144
|
+
- 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 four 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.
|
|
138
145
|
|
|
139
146
|
**Top level.**
|
|
140
147
|
- `schemaVersion` (1): guards pin it.
|
|
141
148
|
- `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.
|
|
142
|
-
- `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
|
|
149
|
+
- `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-settings`, `chrome-passwords`, `chrome-extension-page` and `file-root`. Each has a `description` and, for the protected ones, a default `host`. `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.
|
|
143
150
|
- `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.
|
|
144
151
|
- `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`.
|
|
145
152
|
- `placeholders`: every `{name}` used in the vectors, with what it stands for. `{client}` is the client page's origin, which the runner maps.
|
|
@@ -272,7 +279,13 @@ otherwise they run serially. `capabilities` is an object of booleans or an array
|
|
|
272
279
|
of supported names. `capabilities.scope` selects owner-specific rows.
|
|
273
280
|
|
|
274
281
|
Map `ordinary`, `ordinary-same-site`, `seven`, `vault`, `vault-x`, `apex`, `own`
|
|
275
|
-
and `client` to origins.
|
|
282
|
+
and `client` to origins. `chrome-settings`, `chrome-passwords`, `chrome-extension-page` and `file-root` default to their `url`.
|
|
283
|
+
`{browserUi}` is the id of one of Chrome's own `browser_ui` targets, which the runner looks up through
|
|
284
|
+
its unguarded observer (or the client) before the row; a row that uses it fails when there is none.
|
|
285
|
+
A vector with `"defaultContext": true` runs in the browser's default context even with the
|
|
286
|
+
`browserContexts` capability: navigating a page in a created browser context to `chrome://settings`
|
|
287
|
+
closes Chrome's DevTools connection (Chrome for Testing 156), so `C8.navigate` stays in the default
|
|
288
|
+
context. Optionally map `plain`, `cross` and `log`; their defaults
|
|
276
289
|
are `ordinary`, `client` and `ordinary`. Origins must preserve the site
|
|
277
290
|
relationships of the data. Map an unavailable target to `null`, or leave it out; a
|
|
278
291
|
missing or `undefined` origin is treated exactly like `null`: every
|
|
@@ -287,6 +300,8 @@ every alternative of an `any`, or a required (`all`, `not` or plain) check, need
|
|
|
287
300
|
selects one variant or an array; omission selects all. `parallel` defaults to 1.
|
|
288
301
|
With the `browserContexts` capability each leg runs in its own browser context. A `Target.createBrowserContext` that fails with a timeout is retried once, and the row's `notes` records it; any other failure fails the row.
|
|
289
302
|
|
|
303
|
+
`settleScale` (default 1) multiplies every bound the runner waits on: settles (beacon, served, title and load), `paused`, page and child loads, the wait for a background step's `as` result (`<as> did not complete within <ms>ms`), and the wait for an observation. Adopters on slow hosts can set 3 or 4. Every wait polls and returns as soon as its event arrives, so a larger bound only costs time when something is really missing, and each timeout error names the bound it used. An observation read from the fixture (`observer: "fixture"`, such as a beacon or a served request) that the leg expects to be `true` is polled for up to 5000 ms (scaled) instead of read once. One that the leg expects to be `false` is still read once.
|
|
304
|
+
|
|
290
305
|
`onStart(row)` receives each row's `id`, `target`, `variant`, `transport` and `via` as the row starts. `run()` returns a promise that also has `inFlight()`, and its result has `inFlight` too. `inFlight()` lists the rows that are running now, each as `{ id, target, variant, leg, phase, step, clients }`, where `step` is the index of the current step in its `phase` (`setup`, `trigger` or `child`), and `clients` lists each client's pending CDP calls as `{ method, session, ageMs }` (`client`, plus `unguarded` when it is a separate client). With `rowTimeoutMs`, a row still running after that many milliseconds fails on its own. Its `errors` hold `row timed out after <ms>ms: <the in-flight view>`, `inFlight` holds the view, and the clients that `clientFactory` made for it are closed. The run then goes on to the next row. It is off by default, and a client passed as `client` is never closed.
|
|
291
306
|
|
|
292
307
|
`log(row)` receives completed rows. Besides `control`, `guarded`, `live`, `why` and `errors`, a row may carry `ran` (steps that ran on one leg only) and `notes` (settles skipped because their registration was refused). Both are informational: they explain a row's verdict, and adopters (bios, slicc-cli, slicc-cdp) must not count them as failures, or as passes. The verdict is `control`, `guarded` and `live`. A variant supplies the wait before an
|
|
@@ -513,7 +528,7 @@ for case in corpus().cases {
|
|
|
513
528
|
Go 1.26, Rust stable with `wasm32-wasip1`, Node ≥ 24.
|
|
514
529
|
- `npm run corpus` regenerates `corpus.json` from `corpus/`; the pre-commit hook and CI fail when it's stale.
|
|
515
530
|
- `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.
|
|
516
|
-
- 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
|
|
531
|
+
- 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 9 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.
|
|
517
532
|
- `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.
|
|
518
533
|
- `npm test`: `validate`, the Go tests in `test/integration/`, and `cargo test --all-features`.
|
|
519
534
|
- `npm run build`: the Go module, the crate (also for `wasm32-wasip1`), and `npm pack --dry-run`.
|
package/catalog.json
CHANGED
|
@@ -708,6 +708,21 @@
|
|
|
708
708
|
"schemes": ["https", "http"],
|
|
709
709
|
"innerSchemes": ["blob", "filesystem", "view-source"],
|
|
710
710
|
"anySchemes": ["devtools"],
|
|
711
|
+
"protectedSchemes": [
|
|
712
|
+
"chrome",
|
|
713
|
+
"chrome-untrusted",
|
|
714
|
+
"chrome-extension",
|
|
715
|
+
"devtools",
|
|
716
|
+
"edge",
|
|
717
|
+
"brave",
|
|
718
|
+
"opera",
|
|
719
|
+
"vivaldi",
|
|
720
|
+
"arc",
|
|
721
|
+
"file"
|
|
722
|
+
],
|
|
723
|
+
"schemeExceptions": ["chrome://newtab", "edge://newtab", "brave://newtab", "opera://startpage"],
|
|
724
|
+
"aboutScheme": "chrome",
|
|
725
|
+
"targetTypes": ["browser_ui"],
|
|
711
726
|
"inheriting": ["about:blank", "about:srcdoc"],
|
|
712
727
|
"refusal": "slicc: refused: target is on seven's origin"
|
|
713
728
|
},
|
package/guard-runner.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
15
|
const namedResultMs = 10000;
|
|
16
|
+
const observeMs = 5000;
|
|
16
17
|
const q = JSON.stringify;
|
|
17
18
|
const at = (value, path = '') =>
|
|
18
19
|
path
|
|
@@ -115,6 +116,7 @@ class Execution {
|
|
|
115
116
|
this.expectWhere = [...(vector.setup ?? []), ...(vector.trigger ?? [])].some(
|
|
116
117
|
(step) => step.children && step.where
|
|
117
118
|
);
|
|
119
|
+
this.scale = options.settleScale ?? 1;
|
|
118
120
|
this.childRuns = new Map();
|
|
119
121
|
this.named = new Map();
|
|
120
122
|
this.processed = new Set();
|
|
@@ -167,8 +169,12 @@ class Execution {
|
|
|
167
169
|
return out;
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
bound(ms) {
|
|
173
|
+
return Math.round(ms * this.scale);
|
|
174
|
+
}
|
|
175
|
+
|
|
170
176
|
async poll(predicate, ms = 1500) {
|
|
171
|
-
const deadline = Date.now() + ms;
|
|
177
|
+
const deadline = Date.now() + this.bound(ms);
|
|
172
178
|
do {
|
|
173
179
|
if (await predicate()) return true;
|
|
174
180
|
await sleep(25);
|
|
@@ -402,7 +408,13 @@ class Execution {
|
|
|
402
408
|
}
|
|
403
409
|
|
|
404
410
|
async seed() {
|
|
405
|
-
if (
|
|
411
|
+
if (JSON.stringify([this.vector.setup, this.vector.trigger]).includes('{browserUi}')) {
|
|
412
|
+
const listed = await (this.reader ?? this.client).send('Target.getTargets', {});
|
|
413
|
+
const ui = listed.targetInfos?.find((info) => info.type === 'browser_ui');
|
|
414
|
+
if (!ui) throw new Error('no browser_ui target to use as {browserUi}');
|
|
415
|
+
this.vars.browserUi = ui.targetId;
|
|
416
|
+
}
|
|
417
|
+
if (supported(['browserContexts'], this.options.capabilities) && !this.vector.defaultContext) {
|
|
406
418
|
const browserContextId = await this.createContext();
|
|
407
419
|
this.vars.context = browserContextId;
|
|
408
420
|
this.contexts.push(browserContextId);
|
|
@@ -513,7 +525,7 @@ class Execution {
|
|
|
513
525
|
this.rule(event)?.url === url
|
|
514
526
|
);
|
|
515
527
|
if (!(await this.poll(seen, ms)) && this.side === 'control')
|
|
516
|
-
this.errors.push(`paused: no pause for ${url} within ${ms}ms`);
|
|
528
|
+
this.errors.push(`paused: no pause for ${url} within ${this.bound(ms)}ms`);
|
|
517
529
|
}
|
|
518
530
|
|
|
519
531
|
async answer(event) {
|
|
@@ -651,7 +663,7 @@ class Execution {
|
|
|
651
663
|
if (refused() && !alt) return skipped();
|
|
652
664
|
if (settle.variant) await sleep(this.delay);
|
|
653
665
|
if (settle.title && !this.reader) {
|
|
654
|
-
await sleep(settle.ms ?? 1500);
|
|
666
|
+
await sleep(this.bound(settle.ms ?? 1500));
|
|
655
667
|
return;
|
|
656
668
|
}
|
|
657
669
|
const met = await this.poll(async () => {
|
|
@@ -677,7 +689,7 @@ class Execution {
|
|
|
677
689
|
if (refused() && !alt) return skipped();
|
|
678
690
|
if (settle.required && !met)
|
|
679
691
|
this.errors.push(
|
|
680
|
-
`settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${settle.ms ?? 1500}ms`
|
|
692
|
+
`settle: no ${settle.beacon ? `beacon from ${settle.beacon}` : settle.served ? `request for ${settle.served}` : settle.title ? `title ${settle.title}` : 'load'} within ${this.bound(settle.ms ?? 1500)}ms`
|
|
681
693
|
);
|
|
682
694
|
}
|
|
683
695
|
|
|
@@ -725,6 +737,8 @@ class Execution {
|
|
|
725
737
|
if (params.autoAttach) this.attachSettings.set(session, true);
|
|
726
738
|
else this.attachSettings.delete(session);
|
|
727
739
|
}
|
|
740
|
+
if (step.cdp === 'Target.createTarget' && out.ok && out.result?.targetId)
|
|
741
|
+
this.targets.push(out.result.targetId);
|
|
728
742
|
if (step.cdp === 'Target.createBrowserContext' && out.ok) {
|
|
729
743
|
this.vars.context = out.result.browserContextId;
|
|
730
744
|
this.contexts.push(this.vars.context);
|
|
@@ -795,7 +809,10 @@ class Execution {
|
|
|
795
809
|
if (!waits.length) return true;
|
|
796
810
|
let timer;
|
|
797
811
|
const expired = new Promise((resolve) => {
|
|
798
|
-
timer = setTimeout(
|
|
812
|
+
timer = setTimeout(
|
|
813
|
+
() => resolve(false),
|
|
814
|
+
this.bound(this.options.namedResultMs ?? namedResultMs)
|
|
815
|
+
);
|
|
799
816
|
});
|
|
800
817
|
const done = await Promise.race([Promise.all(waits).then(() => true), expired]);
|
|
801
818
|
clearTimeout(timer);
|
|
@@ -814,11 +831,24 @@ class Execution {
|
|
|
814
831
|
.filter((name) => typeof name === 'string' && this.named.has(name));
|
|
815
832
|
if (!names.length || (await this.namedSettled(names))) return true;
|
|
816
833
|
this.errors.push(
|
|
817
|
-
`${names.join(', ')} did not complete within ${this.options.namedResultMs ?? namedResultMs}ms`
|
|
834
|
+
`${names.join(', ')} did not complete within ${this.bound(this.options.namedResultMs ?? namedResultMs)}ms`
|
|
818
835
|
);
|
|
819
836
|
return false;
|
|
820
837
|
}
|
|
821
838
|
|
|
839
|
+
async observe(name, check) {
|
|
840
|
+
const want = (this.side === 'control' ? this.vector.control.expected : this.vector.expected)?.[
|
|
841
|
+
name
|
|
842
|
+
];
|
|
843
|
+
if (want !== true || check?.observer !== 'fixture') return this.check(check);
|
|
844
|
+
let seen = false;
|
|
845
|
+
await this.poll(async () => {
|
|
846
|
+
seen = await this.check(check);
|
|
847
|
+
return seen === true;
|
|
848
|
+
}, observeMs);
|
|
849
|
+
return seen;
|
|
850
|
+
}
|
|
851
|
+
|
|
822
852
|
async check(check) {
|
|
823
853
|
const c = expand(check, this.vars);
|
|
824
854
|
if (!(await this.ready(c))) return false;
|
|
@@ -1022,7 +1052,7 @@ class Execution {
|
|
|
1022
1052
|
for (const [name, observation] of Object.entries(this.vector.observe ?? {})) {
|
|
1023
1053
|
if (!supported(observation.requires, this.options.capabilities)) continue;
|
|
1024
1054
|
if ((observation.dependsOn ?? []).some((dep) => this.refused[dep])) continue;
|
|
1025
|
-
observed[name] = await this.
|
|
1055
|
+
observed[name] = await this.observe(name, observation.check);
|
|
1026
1056
|
if (observation.eitherWhen && this.side === 'guarded')
|
|
1027
1057
|
observed[`${name}.either`] = await this.check(observation.eitherWhen);
|
|
1028
1058
|
}
|
|
@@ -1391,6 +1421,11 @@ function validateBlocked(vectors) {
|
|
|
1391
1421
|
|
|
1392
1422
|
function validate(set, opts) {
|
|
1393
1423
|
if (!opts.fixture?.log) throw new TypeError('fixture.log is required');
|
|
1424
|
+
if (
|
|
1425
|
+
opts.settleScale !== undefined &&
|
|
1426
|
+
!(Number.isFinite(opts.settleScale) && opts.settleScale > 0)
|
|
1427
|
+
)
|
|
1428
|
+
throw new TypeError('settleScale must be a positive number');
|
|
1394
1429
|
for (const vector of set.vectors)
|
|
1395
1430
|
for (const [name, observation] of Object.entries(vector.observe ?? {}))
|
|
1396
1431
|
if (!observation.check) throw new TypeError(`${vector.id}: ${name} lacks a check`);
|
|
@@ -1425,6 +1460,14 @@ export function run(set, options) {
|
|
|
1425
1460
|
|
|
1426
1461
|
async function runAll(set, options, flights) {
|
|
1427
1462
|
const opts = { capabilities: {}, variant: Object.keys(set.variants), parallel: 1, ...options };
|
|
1463
|
+
opts.targets = {
|
|
1464
|
+
...Object.fromEntries(
|
|
1465
|
+
Object.entries(set.targets ?? {})
|
|
1466
|
+
.filter(([, target]) => typeof target?.url === 'string')
|
|
1467
|
+
.map(([name, target]) => [name, target.url])
|
|
1468
|
+
),
|
|
1469
|
+
...opts.targets,
|
|
1470
|
+
};
|
|
1428
1471
|
const variants = validate(set, opts);
|
|
1429
1472
|
const jobs = set.vectors.flatMap((vector) =>
|
|
1430
1473
|
vector.targets.flatMap((target) => variants.map((variant) => ({ vector, target, variant })))
|
package/guard-vectors.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.
|
|
3
|
+
"setVersion": "1.8.0",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
@@ -48,6 +48,22 @@
|
|
|
48
48
|
},
|
|
49
49
|
"ordinary-same-site": {
|
|
50
50
|
"description": "an unprotected origin same-site with the target"
|
|
51
|
+
},
|
|
52
|
+
"chrome-settings": {
|
|
53
|
+
"url": "chrome://settings/",
|
|
54
|
+
"description": "Chrome's settings page, a privileged chrome: page"
|
|
55
|
+
},
|
|
56
|
+
"chrome-passwords": {
|
|
57
|
+
"url": "chrome://password-manager/",
|
|
58
|
+
"description": "Chrome's password manager, a privileged chrome: page"
|
|
59
|
+
},
|
|
60
|
+
"chrome-extension-page": {
|
|
61
|
+
"url": "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html",
|
|
62
|
+
"description": "a page of Chrome's built-in PDF viewer extension, standing in for any extension page"
|
|
63
|
+
},
|
|
64
|
+
"file-root": {
|
|
65
|
+
"url": "file:///",
|
|
66
|
+
"description": "the local file system's root, standing in for any file: page"
|
|
51
67
|
}
|
|
52
68
|
},
|
|
53
69
|
"variants": {
|
|
@@ -79,7 +95,8 @@
|
|
|
79
95
|
"beacon": "guard-driver.mjs beacon({log}, {tag})",
|
|
80
96
|
"probe": "guard-driver.mjs probe({log}, {tag})",
|
|
81
97
|
"pageTarget": "the current page target id",
|
|
82
|
-
"responseOrigin": "the fixture response CORS origin"
|
|
98
|
+
"responseOrigin": "the fixture response CORS origin",
|
|
99
|
+
"browserUi": "the id of one of Chrome's own browser_ui targets, which the runner looks up with its own CDP"
|
|
83
100
|
},
|
|
84
101
|
"vectors": [
|
|
85
102
|
{
|
|
@@ -12339,6 +12356,399 @@
|
|
|
12339
12356
|
"expected": "the same setup and trigger against a plain, unprotected test origin must SUCCEED."
|
|
12340
12357
|
},
|
|
12341
12358
|
"expected": "as each vector"
|
|
12359
|
+
},
|
|
12360
|
+
{
|
|
12361
|
+
"id": "C8.navigate",
|
|
12362
|
+
"family": "C",
|
|
12363
|
+
"kind": "cdp",
|
|
12364
|
+
"scope": "any",
|
|
12365
|
+
"defaultContext": true,
|
|
12366
|
+
"setup": [
|
|
12367
|
+
{
|
|
12368
|
+
"text": "a plain tab navigates itself to a privileged chrome:, extension or file: page"
|
|
12369
|
+
},
|
|
12370
|
+
{
|
|
12371
|
+
"page": "tab",
|
|
12372
|
+
"url": "{ordinary}/doc?n={tag}"
|
|
12373
|
+
},
|
|
12374
|
+
{
|
|
12375
|
+
"cdp": "Page.enable"
|
|
12376
|
+
}
|
|
12377
|
+
],
|
|
12378
|
+
"trigger": [
|
|
12379
|
+
{
|
|
12380
|
+
"cdp": "Page.navigate",
|
|
12381
|
+
"params": {
|
|
12382
|
+
"url": "{target}"
|
|
12383
|
+
},
|
|
12384
|
+
"as": "nav",
|
|
12385
|
+
"refusable": true,
|
|
12386
|
+
"refusal": {
|
|
12387
|
+
"match": "refused"
|
|
12388
|
+
}
|
|
12389
|
+
},
|
|
12390
|
+
{
|
|
12391
|
+
"settle": {
|
|
12392
|
+
"ms": 2000
|
|
12393
|
+
}
|
|
12394
|
+
}
|
|
12395
|
+
],
|
|
12396
|
+
"targets": ["chrome-settings", "chrome-passwords", "chrome-extension-page", "file-root"],
|
|
12397
|
+
"observe": {
|
|
12398
|
+
"navigated": {
|
|
12399
|
+
"means": "the page committed {target}",
|
|
12400
|
+
"dependsOn": ["nav"],
|
|
12401
|
+
"check": {
|
|
12402
|
+
"attached": "{target}",
|
|
12403
|
+
"observer": "client"
|
|
12404
|
+
}
|
|
12405
|
+
},
|
|
12406
|
+
"refused": {
|
|
12407
|
+
"means": "the navigation was refused",
|
|
12408
|
+
"dependsOn": [],
|
|
12409
|
+
"check": {
|
|
12410
|
+
"refused": ["nav"],
|
|
12411
|
+
"observer": "client"
|
|
12412
|
+
}
|
|
12413
|
+
}
|
|
12414
|
+
},
|
|
12415
|
+
"live": "the guard returned its refusal for the step, or the runner's own CDP sees the effect",
|
|
12416
|
+
"control": {
|
|
12417
|
+
"origin": "ordinary",
|
|
12418
|
+
"via": "unguarded",
|
|
12419
|
+
"expected": {
|
|
12420
|
+
"navigated": true,
|
|
12421
|
+
"refused": false
|
|
12422
|
+
}
|
|
12423
|
+
},
|
|
12424
|
+
"expected": {
|
|
12425
|
+
"navigated": false,
|
|
12426
|
+
"refused": true
|
|
12427
|
+
},
|
|
12428
|
+
"liveCheck": {
|
|
12429
|
+
"any": [
|
|
12430
|
+
{
|
|
12431
|
+
"refused": ["nav"],
|
|
12432
|
+
"observer": "client"
|
|
12433
|
+
},
|
|
12434
|
+
{
|
|
12435
|
+
"attached": "{target}",
|
|
12436
|
+
"observer": "client"
|
|
12437
|
+
}
|
|
12438
|
+
],
|
|
12439
|
+
"observer": "client"
|
|
12440
|
+
}
|
|
12441
|
+
},
|
|
12442
|
+
{
|
|
12443
|
+
"id": "C8.view-source",
|
|
12444
|
+
"family": "C",
|
|
12445
|
+
"kind": "cdp",
|
|
12446
|
+
"scope": "any",
|
|
12447
|
+
"defaultContext": true,
|
|
12448
|
+
"setup": [
|
|
12449
|
+
{
|
|
12450
|
+
"text": "a plain tab navigates itself to view-source: of a privileged page"
|
|
12451
|
+
},
|
|
12452
|
+
{
|
|
12453
|
+
"page": "tab",
|
|
12454
|
+
"url": "{ordinary}/doc?n={tag}"
|
|
12455
|
+
},
|
|
12456
|
+
{
|
|
12457
|
+
"cdp": "Page.enable"
|
|
12458
|
+
}
|
|
12459
|
+
],
|
|
12460
|
+
"trigger": [
|
|
12461
|
+
{
|
|
12462
|
+
"cdp": "Page.navigate",
|
|
12463
|
+
"params": {
|
|
12464
|
+
"url": "view-source:{target}"
|
|
12465
|
+
},
|
|
12466
|
+
"as": "nav",
|
|
12467
|
+
"refusable": true,
|
|
12468
|
+
"refusal": {
|
|
12469
|
+
"match": "refused"
|
|
12470
|
+
}
|
|
12471
|
+
},
|
|
12472
|
+
{
|
|
12473
|
+
"settle": {
|
|
12474
|
+
"ms": 2000
|
|
12475
|
+
}
|
|
12476
|
+
}
|
|
12477
|
+
],
|
|
12478
|
+
"targets": ["chrome-settings", "chrome-extension-page", "file-root"],
|
|
12479
|
+
"observe": {
|
|
12480
|
+
"navigated": {
|
|
12481
|
+
"means": "the page committed view-source: of {target}",
|
|
12482
|
+
"dependsOn": ["nav"],
|
|
12483
|
+
"check": {
|
|
12484
|
+
"attached": "{target}",
|
|
12485
|
+
"observer": "client"
|
|
12486
|
+
}
|
|
12487
|
+
},
|
|
12488
|
+
"refused": {
|
|
12489
|
+
"means": "the navigation was refused",
|
|
12490
|
+
"dependsOn": [],
|
|
12491
|
+
"check": {
|
|
12492
|
+
"refused": ["nav"],
|
|
12493
|
+
"observer": "client"
|
|
12494
|
+
}
|
|
12495
|
+
}
|
|
12496
|
+
},
|
|
12497
|
+
"live": "the guard returned its refusal for the step, or the runner's own CDP sees the effect",
|
|
12498
|
+
"control": {
|
|
12499
|
+
"origin": "ordinary",
|
|
12500
|
+
"via": "unguarded",
|
|
12501
|
+
"expected": {
|
|
12502
|
+
"navigated": true,
|
|
12503
|
+
"refused": false
|
|
12504
|
+
}
|
|
12505
|
+
},
|
|
12506
|
+
"expected": {
|
|
12507
|
+
"navigated": false,
|
|
12508
|
+
"refused": true
|
|
12509
|
+
},
|
|
12510
|
+
"liveCheck": {
|
|
12511
|
+
"any": [
|
|
12512
|
+
{
|
|
12513
|
+
"refused": ["nav"],
|
|
12514
|
+
"observer": "client"
|
|
12515
|
+
},
|
|
12516
|
+
{
|
|
12517
|
+
"attached": "{target}",
|
|
12518
|
+
"observer": "client"
|
|
12519
|
+
}
|
|
12520
|
+
],
|
|
12521
|
+
"observer": "client"
|
|
12522
|
+
}
|
|
12523
|
+
},
|
|
12524
|
+
{
|
|
12525
|
+
"id": "C8.create",
|
|
12526
|
+
"family": "C",
|
|
12527
|
+
"kind": "cdp",
|
|
12528
|
+
"scope": "any",
|
|
12529
|
+
"setup": [
|
|
12530
|
+
{
|
|
12531
|
+
"text": "the client opens a new tab straight on a privileged chrome: page"
|
|
12532
|
+
}
|
|
12533
|
+
],
|
|
12534
|
+
"trigger": [
|
|
12535
|
+
{
|
|
12536
|
+
"cdp": "Target.createTarget",
|
|
12537
|
+
"params": {
|
|
12538
|
+
"url": "{target}"
|
|
12539
|
+
},
|
|
12540
|
+
"session": "browser",
|
|
12541
|
+
"as": "created",
|
|
12542
|
+
"requires": ["browserSession"],
|
|
12543
|
+
"refusable": true,
|
|
12544
|
+
"refusal": {
|
|
12545
|
+
"match": "refused"
|
|
12546
|
+
}
|
|
12547
|
+
},
|
|
12548
|
+
{
|
|
12549
|
+
"settle": {
|
|
12550
|
+
"ms": 1500
|
|
12551
|
+
}
|
|
12552
|
+
}
|
|
12553
|
+
],
|
|
12554
|
+
"targets": ["chrome-settings", "chrome-passwords"],
|
|
12555
|
+
"requires": ["browserSession"],
|
|
12556
|
+
"observe": {
|
|
12557
|
+
"opened": {
|
|
12558
|
+
"means": "Target.createTarget returned a target",
|
|
12559
|
+
"dependsOn": ["created"],
|
|
12560
|
+
"check": {
|
|
12561
|
+
"result": "created",
|
|
12562
|
+
"path": "result.targetId",
|
|
12563
|
+
"equals": {
|
|
12564
|
+
"$regex": "."
|
|
12565
|
+
},
|
|
12566
|
+
"observer": "client"
|
|
12567
|
+
}
|
|
12568
|
+
},
|
|
12569
|
+
"refused": {
|
|
12570
|
+
"means": "Target.createTarget was refused",
|
|
12571
|
+
"dependsOn": [],
|
|
12572
|
+
"check": {
|
|
12573
|
+
"refused": ["created"],
|
|
12574
|
+
"observer": "client"
|
|
12575
|
+
}
|
|
12576
|
+
}
|
|
12577
|
+
},
|
|
12578
|
+
"live": "the guard returned its refusal for the step, or the runner's own CDP sees the effect",
|
|
12579
|
+
"control": {
|
|
12580
|
+
"origin": "ordinary",
|
|
12581
|
+
"via": "unguarded",
|
|
12582
|
+
"expected": {
|
|
12583
|
+
"opened": true,
|
|
12584
|
+
"refused": false
|
|
12585
|
+
}
|
|
12586
|
+
},
|
|
12587
|
+
"expected": {
|
|
12588
|
+
"opened": false,
|
|
12589
|
+
"refused": true
|
|
12590
|
+
},
|
|
12591
|
+
"liveCheck": {
|
|
12592
|
+
"any": [
|
|
12593
|
+
{
|
|
12594
|
+
"refused": ["created"],
|
|
12595
|
+
"observer": "client"
|
|
12596
|
+
},
|
|
12597
|
+
{
|
|
12598
|
+
"result": "created",
|
|
12599
|
+
"path": "ok",
|
|
12600
|
+
"equals": true,
|
|
12601
|
+
"observer": "client"
|
|
12602
|
+
}
|
|
12603
|
+
],
|
|
12604
|
+
"observer": "client"
|
|
12605
|
+
}
|
|
12606
|
+
},
|
|
12607
|
+
{
|
|
12608
|
+
"id": "C8.browser-ui-listed",
|
|
12609
|
+
"family": "C",
|
|
12610
|
+
"kind": "cdp",
|
|
12611
|
+
"scope": "any",
|
|
12612
|
+
"setup": [
|
|
12613
|
+
{
|
|
12614
|
+
"text": "the client lists the browser's targets; Chrome's own browser_ui targets are among them"
|
|
12615
|
+
}
|
|
12616
|
+
],
|
|
12617
|
+
"trigger": [
|
|
12618
|
+
{
|
|
12619
|
+
"cdp": "Target.getTargets",
|
|
12620
|
+
"session": "browser",
|
|
12621
|
+
"as": "targets",
|
|
12622
|
+
"requires": ["browserSession"],
|
|
12623
|
+
"refusable": true,
|
|
12624
|
+
"refusal": {
|
|
12625
|
+
"match": "refused"
|
|
12626
|
+
}
|
|
12627
|
+
}
|
|
12628
|
+
],
|
|
12629
|
+
"targets": ["chrome-settings"],
|
|
12630
|
+
"requires": ["browserSession"],
|
|
12631
|
+
"observe": {
|
|
12632
|
+
"listed": {
|
|
12633
|
+
"means": "Target.getTargets lists a browser_ui target",
|
|
12634
|
+
"dependsOn": ["targets"],
|
|
12635
|
+
"check": {
|
|
12636
|
+
"result": "targets",
|
|
12637
|
+
"path": "result.targetInfos",
|
|
12638
|
+
"equals": {
|
|
12639
|
+
"$some": {
|
|
12640
|
+
"type": "browser_ui"
|
|
12641
|
+
}
|
|
12642
|
+
},
|
|
12643
|
+
"observer": "client"
|
|
12644
|
+
}
|
|
12645
|
+
}
|
|
12646
|
+
},
|
|
12647
|
+
"live": "the runner's own CDP lists a browser_ui target",
|
|
12648
|
+
"control": {
|
|
12649
|
+
"origin": "ordinary",
|
|
12650
|
+
"via": "unguarded",
|
|
12651
|
+
"expected": {
|
|
12652
|
+
"listed": true
|
|
12653
|
+
}
|
|
12654
|
+
},
|
|
12655
|
+
"expected": {
|
|
12656
|
+
"listed": false
|
|
12657
|
+
},
|
|
12658
|
+
"liveCheck": {
|
|
12659
|
+
"any": [
|
|
12660
|
+
{
|
|
12661
|
+
"refused": ["targets"],
|
|
12662
|
+
"observer": "client"
|
|
12663
|
+
},
|
|
12664
|
+
{
|
|
12665
|
+
"result": "targets",
|
|
12666
|
+
"path": "ok",
|
|
12667
|
+
"equals": true,
|
|
12668
|
+
"observer": "client"
|
|
12669
|
+
}
|
|
12670
|
+
],
|
|
12671
|
+
"observer": "client"
|
|
12672
|
+
}
|
|
12673
|
+
},
|
|
12674
|
+
{
|
|
12675
|
+
"id": "C8.browser-ui-attach",
|
|
12676
|
+
"family": "C",
|
|
12677
|
+
"kind": "cdp",
|
|
12678
|
+
"scope": "any",
|
|
12679
|
+
"setup": [
|
|
12680
|
+
{
|
|
12681
|
+
"text": "the client attaches to one of Chrome's own browser_ui targets by its id"
|
|
12682
|
+
}
|
|
12683
|
+
],
|
|
12684
|
+
"trigger": [
|
|
12685
|
+
{
|
|
12686
|
+
"cdp": "Target.attachToTarget",
|
|
12687
|
+
"params": {
|
|
12688
|
+
"targetId": "{browserUi}",
|
|
12689
|
+
"flatten": true
|
|
12690
|
+
},
|
|
12691
|
+
"session": "browser",
|
|
12692
|
+
"as": "attach",
|
|
12693
|
+
"requires": ["browserSession"],
|
|
12694
|
+
"refusable": true,
|
|
12695
|
+
"refusal": {
|
|
12696
|
+
"match": "refused"
|
|
12697
|
+
}
|
|
12698
|
+
}
|
|
12699
|
+
],
|
|
12700
|
+
"targets": ["chrome-settings"],
|
|
12701
|
+
"requires": ["browserSession"],
|
|
12702
|
+
"observe": {
|
|
12703
|
+
"attached": {
|
|
12704
|
+
"means": "Target.attachToTarget returned a session",
|
|
12705
|
+
"dependsOn": ["attach"],
|
|
12706
|
+
"check": {
|
|
12707
|
+
"result": "attach",
|
|
12708
|
+
"path": "result.sessionId",
|
|
12709
|
+
"equals": {
|
|
12710
|
+
"$regex": "."
|
|
12711
|
+
},
|
|
12712
|
+
"observer": "client"
|
|
12713
|
+
}
|
|
12714
|
+
},
|
|
12715
|
+
"refused": {
|
|
12716
|
+
"means": "Target.attachToTarget was refused",
|
|
12717
|
+
"dependsOn": [],
|
|
12718
|
+
"check": {
|
|
12719
|
+
"refused": ["attach"],
|
|
12720
|
+
"observer": "client"
|
|
12721
|
+
}
|
|
12722
|
+
}
|
|
12723
|
+
},
|
|
12724
|
+
"live": "the guard returned its refusal for the step, or the runner's own CDP sees the effect",
|
|
12725
|
+
"control": {
|
|
12726
|
+
"origin": "ordinary",
|
|
12727
|
+
"via": "unguarded",
|
|
12728
|
+
"expected": {
|
|
12729
|
+
"attached": true,
|
|
12730
|
+
"refused": false
|
|
12731
|
+
}
|
|
12732
|
+
},
|
|
12733
|
+
"expected": {
|
|
12734
|
+
"attached": false,
|
|
12735
|
+
"refused": true
|
|
12736
|
+
},
|
|
12737
|
+
"liveCheck": {
|
|
12738
|
+
"any": [
|
|
12739
|
+
{
|
|
12740
|
+
"refused": ["attach"],
|
|
12741
|
+
"observer": "client"
|
|
12742
|
+
},
|
|
12743
|
+
{
|
|
12744
|
+
"result": "attach",
|
|
12745
|
+
"path": "ok",
|
|
12746
|
+
"equals": true,
|
|
12747
|
+
"observer": "client"
|
|
12748
|
+
}
|
|
12749
|
+
],
|
|
12750
|
+
"observer": "client"
|
|
12751
|
+
}
|
|
12342
12752
|
}
|
|
12343
12753
|
]
|
|
12344
12754
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
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,7 +65,7 @@
|
|
|
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",
|
|
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",
|
|
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",
|
package/protected.json
CHANGED
|
@@ -284,15 +284,15 @@
|
|
|
284
284
|
},
|
|
285
285
|
{
|
|
286
286
|
"url": "chrome://version/",
|
|
287
|
-
"protected":
|
|
287
|
+
"protected": true
|
|
288
288
|
},
|
|
289
289
|
{
|
|
290
290
|
"url": "chrome-extension://abcdefghijklmnopabcdefghijklmnop/page.html",
|
|
291
|
-
"protected":
|
|
291
|
+
"protected": true
|
|
292
292
|
},
|
|
293
293
|
{
|
|
294
294
|
"url": "file:///tmp/seven.sliccy.ai/index.html",
|
|
295
|
-
"protected":
|
|
295
|
+
"protected": true
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"url": "",
|
|
@@ -321,6 +321,170 @@
|
|
|
321
321
|
{
|
|
322
322
|
"url": "https://vault-feature-x.sliccy.ai/",
|
|
323
323
|
"protected": true
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"url": "chrome://settings/",
|
|
327
|
+
"protected": true
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"url": "chrome://settings/passwords",
|
|
331
|
+
"protected": true
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"url": "chrome://password-manager/passwords",
|
|
335
|
+
"protected": true
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"url": "CHROME://Settings/",
|
|
339
|
+
"protected": true
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"url": "chrome://omnibox-popup.top-chrome/",
|
|
343
|
+
"protected": true
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"url": "chrome-untrusted://print/",
|
|
347
|
+
"protected": true
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"url": "view-source:chrome://settings/",
|
|
351
|
+
"protected": true
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"url": "chrome://newtab/",
|
|
355
|
+
"protected": false
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"url": "chrome://newtab",
|
|
359
|
+
"protected": false
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"url": "chrome://newtab-x/",
|
|
363
|
+
"protected": true
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"url": "edge://settings/passwords",
|
|
367
|
+
"protected": true
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"url": "edge://wallet/",
|
|
371
|
+
"protected": true
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"url": "EDGE://Settings/",
|
|
375
|
+
"protected": true
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"url": "brave://settings/",
|
|
379
|
+
"protected": true
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"url": "brave://wallet/",
|
|
383
|
+
"protected": true
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"url": "opera://settings/",
|
|
387
|
+
"protected": true
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"url": "vivaldi://settings/",
|
|
391
|
+
"protected": true
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"url": "arc://settings/",
|
|
395
|
+
"protected": true
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"url": "edge://newtab/",
|
|
399
|
+
"protected": false
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"url": "brave://newtab/",
|
|
403
|
+
"protected": false
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"url": "opera://startpage/",
|
|
407
|
+
"protected": false
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"url": "about:settings",
|
|
411
|
+
"protected": true
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"url": "about:passwords",
|
|
415
|
+
"protected": true
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"url": "about:password-manager",
|
|
419
|
+
"protected": true
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"url": "ABOUT:Settings",
|
|
423
|
+
"protected": true
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"url": "about:version",
|
|
427
|
+
"protected": true
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
"url": "about:newtab",
|
|
431
|
+
"protected": false
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"url": "about:blank#x",
|
|
435
|
+
"protected": false
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"url": "about:srcdoc",
|
|
439
|
+
"protected": false
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"url": "chrome-extension://aeblfdkhhhdcdjpifhhbdiojplfjncoa/popup.html",
|
|
443
|
+
"protected": true
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"url": "chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/home.html#unlock",
|
|
447
|
+
"protected": true
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
"url": "blob:chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/0b9e0f6c",
|
|
451
|
+
"protected": true
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"url": "filesystem:chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/temporary/x",
|
|
455
|
+
"protected": true
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"url": "view-source:chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/home.html",
|
|
459
|
+
"protected": true
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"url": "file:///etc/passwd",
|
|
463
|
+
"protected": true
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"url": "file:///Users/me/.ssh/id_ed25519",
|
|
467
|
+
"protected": true
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"url": "file:///C:/Users/me/.aws/credentials",
|
|
471
|
+
"protected": true
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"url": "FILE:///",
|
|
475
|
+
"protected": true
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"url": "view-source:file:///etc/hosts",
|
|
479
|
+
"protected": true
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"url": "view-source:https://example.test/",
|
|
483
|
+
"protected": false
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
"url": "blob:https://example.test/0b9e0f6c",
|
|
487
|
+
"protected": false
|
|
324
488
|
}
|
|
325
489
|
],
|
|
326
490
|
"targets": [
|
|
@@ -413,7 +577,7 @@
|
|
|
413
577
|
{
|
|
414
578
|
"url": "about:version",
|
|
415
579
|
"previous": true,
|
|
416
|
-
"protected":
|
|
580
|
+
"protected": true
|
|
417
581
|
},
|
|
418
582
|
{
|
|
419
583
|
"url": "data:text/html,x",
|
|
@@ -436,6 +600,51 @@
|
|
|
436
600
|
"url": "http://127.0.0.1:5711/index.html",
|
|
437
601
|
"previous": true,
|
|
438
602
|
"protected": false
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"url": "chrome://omnibox-popup.top-chrome/",
|
|
606
|
+
"type": "browser_ui",
|
|
607
|
+
"protected": true
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
"url": "https://example.test/",
|
|
611
|
+
"type": "browser_ui",
|
|
612
|
+
"protected": true
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"url": "chrome://newtab/",
|
|
616
|
+
"type": "page",
|
|
617
|
+
"protected": false
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"url": "https://example.test/",
|
|
621
|
+
"type": "page",
|
|
622
|
+
"protected": false
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"url": "chrome-extension://aeblfdkhhhdcdjpifhhbdiojplfjncoa/background.js",
|
|
626
|
+
"type": "service_worker",
|
|
627
|
+
"protected": true
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
"url": "chrome-extension://aeblfdkhhhdcdjpifhhbdiojplfjncoa/background.html",
|
|
631
|
+
"type": "background_page",
|
|
632
|
+
"protected": true
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
"url": "chrome-extension://aeblfdkhhhdcdjpifhhbdiojplfjncoa/offscreen.html",
|
|
636
|
+
"type": "other",
|
|
637
|
+
"protected": true
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
"url": "file:///Users/me/notes.html",
|
|
641
|
+
"type": "page",
|
|
642
|
+
"protected": true
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
"url": "file:///Users/me/frame.html",
|
|
646
|
+
"type": "iframe",
|
|
647
|
+
"protected": true
|
|
439
648
|
}
|
|
440
649
|
],
|
|
441
650
|
"cookieDomains": [
|
package/protected.mjs
CHANGED
|
@@ -34,11 +34,19 @@ export function protectedOrigins(rules, extraOrigins = []) {
|
|
|
34
34
|
return u;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
const alias = (u) => {
|
|
38
|
+
if (!u || scheme(u) !== 'about' || !rules.aboutScheme) return u;
|
|
39
|
+
if (rules.inheriting.includes(`about:${u.pathname.toLowerCase()}`)) return u;
|
|
40
|
+
return parse(`${rules.aboutScheme}://${u.pathname}${u.search}${u.hash}`) ?? u;
|
|
41
|
+
};
|
|
42
|
+
|
|
37
43
|
const url = (text) => {
|
|
38
|
-
const u = unwrap(parse(text));
|
|
44
|
+
const u = alias(unwrap(parse(text)));
|
|
39
45
|
if (!u) return false;
|
|
40
46
|
const s = scheme(u);
|
|
41
47
|
if (rules.anySchemes.includes(s)) return true;
|
|
48
|
+
if ((rules.protectedSchemes ?? []).includes(s))
|
|
49
|
+
return !(rules.schemeExceptions ?? []).includes(`${s}://${hostOf(u)}`);
|
|
42
50
|
if (rules.schemes.includes(s) && rules.hosts.includes(hostOf(u))) return true;
|
|
43
51
|
if (rules.schemes.includes(s) && rules.domains.some((d) => under(hostOf(u), d))) return true;
|
|
44
52
|
const o = originOf(u);
|
|
@@ -55,7 +63,8 @@ export function protectedOrigins(rules, extraOrigins = []) {
|
|
|
55
63
|
);
|
|
56
64
|
};
|
|
57
65
|
|
|
58
|
-
const target = ({ url: text, previous = false, opener }) => {
|
|
66
|
+
const target = ({ url: text, previous = false, opener, type }) => {
|
|
67
|
+
if (type && (rules.targetTypes ?? []).includes(type)) return true;
|
|
59
68
|
if (inheriting(text)) return previous || opener === 'protected' || opener === 'unknown';
|
|
60
69
|
return url(text);
|
|
61
70
|
};
|
|
@@ -180,6 +180,10 @@
|
|
|
180
180
|
"schemes",
|
|
181
181
|
"innerSchemes",
|
|
182
182
|
"anySchemes",
|
|
183
|
+
"protectedSchemes",
|
|
184
|
+
"schemeExceptions",
|
|
185
|
+
"aboutScheme",
|
|
186
|
+
"targetTypes",
|
|
183
187
|
"inheriting",
|
|
184
188
|
"refusal"
|
|
185
189
|
],
|
|
@@ -204,6 +208,18 @@
|
|
|
204
208
|
"schemes": { "$ref": "#/$defs/schemes", "type": "array", "minItems": 1 },
|
|
205
209
|
"innerSchemes": { "$ref": "#/$defs/schemes" },
|
|
206
210
|
"anySchemes": { "$ref": "#/$defs/schemes" },
|
|
211
|
+
"protectedSchemes": { "$ref": "#/$defs/schemes" },
|
|
212
|
+
"schemeExceptions": {
|
|
213
|
+
"type": "array",
|
|
214
|
+
"uniqueItems": true,
|
|
215
|
+
"items": { "type": "string", "pattern": "^[a-z][a-z0-9+.-]*://[a-z0-9.-]+$" }
|
|
216
|
+
},
|
|
217
|
+
"aboutScheme": { "type": "string", "pattern": "^[a-z][a-z0-9+.-]*$" },
|
|
218
|
+
"targetTypes": {
|
|
219
|
+
"type": "array",
|
|
220
|
+
"uniqueItems": true,
|
|
221
|
+
"items": { "type": "string", "pattern": "^[a-z_]+$" }
|
|
222
|
+
},
|
|
207
223
|
"inheriting": {
|
|
208
224
|
"type": "array",
|
|
209
225
|
"uniqueItems": true,
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
"type": "string",
|
|
57
57
|
"pattern": "^[a-z0-9.-]+$"
|
|
58
58
|
},
|
|
59
|
+
"url": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"pattern": "^[a-z][a-z0-9+.-]*://\\S+$"
|
|
62
|
+
},
|
|
59
63
|
"description": {
|
|
60
64
|
"type": "string",
|
|
61
65
|
"minLength": 1
|
|
@@ -152,7 +156,17 @@
|
|
|
152
156
|
}
|
|
153
157
|
},
|
|
154
158
|
"target": {
|
|
155
|
-
"enum": [
|
|
159
|
+
"enum": [
|
|
160
|
+
"seven",
|
|
161
|
+
"vault",
|
|
162
|
+
"vault-x",
|
|
163
|
+
"apex",
|
|
164
|
+
"own",
|
|
165
|
+
"chrome-settings",
|
|
166
|
+
"chrome-passwords",
|
|
167
|
+
"chrome-extension-page",
|
|
168
|
+
"file-root"
|
|
169
|
+
]
|
|
156
170
|
},
|
|
157
171
|
"expected": {
|
|
158
172
|
"oneOf": [
|
|
@@ -266,6 +280,9 @@
|
|
|
266
280
|
}
|
|
267
281
|
}
|
|
268
282
|
},
|
|
283
|
+
"defaultContext": {
|
|
284
|
+
"const": true
|
|
285
|
+
},
|
|
269
286
|
"designDependent": {
|
|
270
287
|
"type": "array",
|
|
271
288
|
"uniqueItems": true,
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"required": ["url", "protected"],
|
|
29
29
|
"properties": {
|
|
30
30
|
"url": { "type": "string" },
|
|
31
|
+
"type": { "type": "string" },
|
|
31
32
|
"previous": { "type": "boolean" },
|
|
32
33
|
"opener": { "enum": ["protected", "unprotected", "unknown"] },
|
|
33
34
|
"protected": { "type": "boolean" }
|