@ai-ecoverse/slicc-browsers 1.10.2 → 1.11.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 +14 -6
- package/catalog.json +5 -0
- package/guard-runner.mjs +23 -3
- package/guard-vectors.json +459 -1
- package/package.json +6 -5
- package/protected-error-page.mjs +243 -0
- package/protected.json +37 -0
- package/protected.mjs +24 -2
- package/schema/catalog.schema.json +10 -0
- package/schema/guard-vectors.schema.json +10 -0
- package/schema/protected.schema.json +13 -0
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Every feature builds for `wasm32-wasip1`.
|
|
|
65
65
|
|
|
66
66
|
`guard-driver.mjs` is described under [Guard vectors](#guard-vectors).
|
|
67
67
|
|
|
68
|
-
`protected.mjs` is the reference matcher for [protected origins](#protected-origins): `import { protectedOrigins } from '@ai-ecoverse/slicc-browsers/protected.mjs'`, then `protectedOrigins(catalog.protectedOrigins, extraOrigins)` returns `{ url(text), target({ url, previous, opener }), cookieDomain(domain), storageKey(key), unparseable(text) }`, each a boolean. `url(text)` uses the load/loaded-URL fail-closed rule; `url(text, { readOnly: true })` preserves the old parse-failure result for read-only keys and value redaction, and `storageKey(key)` uses that mode internally. `unparseable(text)` identifies parse or unwrap failures without changing whether a parsed origin is protected. It needs only the WHATWG `URL`, so it runs in Node and in a browser.
|
|
68
|
+
`protected.mjs` is the reference matcher for [protected origins](#protected-origins): `import { protectedOrigins } from '@ai-ecoverse/slicc-browsers/protected.mjs'`, then `protectedOrigins(catalog.protectedOrigins, extraOrigins)` returns `{ url(text), target({ url, previous, opener, type, documentURL, unreachableURL, navigating }), cookieDomain(domain), storageKey(key), unparseable(text) }`, each a boolean. `url(text)` uses the load/loaded-URL fail-closed rule; `url(text, { readOnly: true })` preserves the old parse-failure result for read-only keys and value redaction, and `storageKey(key)` uses that mode internally. `unparseable(text)` identifies parse or unwrap failures without changing whether a parsed origin is protected. It needs only the WHATWG `URL`, so it runs in Node and in a browser. `protected.mjs` and `seven.mjs` import nothing and re-export nothing, so an extension or service worker can ship either file alone; `npm run validate` (through `scripts/self-contained.mjs`) evaluates each as a module in a fresh `node:vm` context that has only `URL`, with no linker resolution allowed.
|
|
69
69
|
|
|
70
70
|
## `catalog.json`
|
|
71
71
|
|
|
@@ -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-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`), `unparseable` (`protected`) with `unparseableRefusal`, and `refusal`, the text of a parsed protected-origin 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`), `errorPage` (`url: "chrome-error://chromewebdata/"`, `target: "previous"`, `redact: "origin"`), `unparseable` (`protected`) with `unparseableRefusal`, and `refusal`, the text of a parsed protected-origin 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`.
|
|
@@ -147,6 +147,10 @@ A guard must refuse navigation, creation and attach on protected schemes, includ
|
|
|
147
147
|
|
|
148
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.
|
|
149
149
|
|
|
150
|
+
**Protected-URL error pages.** `errorPage` in both the catalog and `protected.json` retains the previous target decision; it never locks an allowed target or unlocks an already-protected target. The exemption requires the last main-frame different-document commit to be `chrome-error://chromewebdata/`, with opaque `securityOrigin` (`://`), and `unreachableUrl` exactly equal to the reported `TargetInfo.url`, with no main-frame navigation in flight. A URL mismatch, unavailable commit evidence or an in-flight navigation does not qualify; guards fail closed while their state is unknown. `protected.mjs.target` takes validated `documentURL`, `unreachableURL` and `navigating` evidence in addition to `previous`; callers must track commits and readiness, rather than trust `TargetInfo.url` alone. Browser UI target types remain protected. Read-only discovery still redacts the URL **and title** to the protected origin in `getTargets`, `getTargetInfo`, `targetCreated`, `targetInfoChanged` and `attachedToTarget`, including held sessions released later. Path, query and fragment secrets must never survive in those fields. Navigation destinations remain protected: the exception permits recovery, not a protected `Page.navigate` destination.
|
|
151
|
+
|
|
152
|
+
Main-frame loaders start at `Page.frameStartedNavigating`, excluding `sameDocument` and `historySameDocument`, and end on the corresponding commit, `Network.loadingFailed` with matching requestId, or main-frame `Page.frameStoppedLoading`. A different-document commit clears older loaders; `BackForwardCacheRestore` clears the entire set, even though it reuses the restored document’s old loaderId. A guard that is not ready, cannot parse parameters, or has more than 64 loaders in flight must fail closed. BFCache can report the protected TargetInfo before its restore commit, so waiting for `frameNavigated` alone is not sufficient.
|
|
153
|
+
|
|
150
154
|
**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
155
|
|
|
152
156
|
**Unparseable URL boundaries.** Fail closed on command destinations: navigation and target creation, the URL and `userTypedURL` of a selected history entry, `Fetch.continueRequest` and legacy interception overrides, `Network.loadNetworkResource`, PWA install/launch, and cookie-command URLs when no domain is supplied. Also fail closed on security-relevant Chrome-reported URLs: targets, main-frame `securityOrigin`, execution-context origins, requests/redirect responses, web sockets and web transports, and service-worker scopes/scripts. A protected default execution context still cuts its session; a protected foreign isolated world stays hidden. This is not a rule to reject arbitrary strings a guard merely reads: storage-command origin keys, storage keys/cache ids, script/stack URLs, generic value redaction and storage events retain the old not-protected result for a parse failure. Cookie domains remain suffix matches, so `exa%20mple.sliccy.ai` is already covered.
|
|
@@ -167,7 +171,7 @@ A same-site frame shares its parent's process and has no target of its own. Ever
|
|
|
167
171
|
|
|
168
172
|
**Refusals** carry the text in `refusal`, `slicc: refused: target is on seven's origin`.
|
|
169
173
|
|
|
170
|
-
**`protected.json`** (schema `schema/protected.schema.json`): `unparseable` (`protected`) and `unparseableRefusal`, in step with the catalog; `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.
|
|
174
|
+
**`protected.json`** (schema `schema/protected.schema.json`): `errorPage` (the committed-error previous-decision and origin-redaction rule), `unparseable` (`protected`) and `unparseableRefusal`, in step with the catalog; `origins`, the extra origins every vector runs with; `urls` (`url`, `protected`); `targets` (`url`, `documentURL?` and `unreachableURL?` for validated main-frame error-commit evidence, `navigating?` for a main-frame navigation in flight; `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.
|
|
171
175
|
|
|
172
176
|
## Guard vectors
|
|
173
177
|
|
|
@@ -188,6 +192,8 @@ The integration launcher loads shared-web 1.25.2’s harness extension exactly o
|
|
|
188
192
|
|
|
189
193
|
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/`.
|
|
190
194
|
|
|
195
|
+
`C8.protected-error-page` touches an allowed tab, scripts a protected navigation, and requires the guard to fail its Document request. The independent observer verifies the committed error document and the exact unreachable/reported URL pair. The session stays attached; reload, navigation to `about:blank` and close succeed, while discovery and target events redact URL and title to the origin. The unguarded control uses independent Fetch failure injection and keeps raw discovery values. `C8.protected-document-restore` seeds a real protected document before attachment, navigates away, then restores it from BFCache in the same tab after the error/recovery sequence; evaluate, attach and close must be refused. A real `BackForwardCacheRestore` commit is required, so a second failed network request cannot masquerade as this control. `C8.protected-error-page-sticky` additionally fails a request after that real protected restore and requires the already-protected tab to remain locked. All three require `browserSession`, have unguarded controls and clean up their own targets. The regression tests include URL-only locking, unconditional error-page unlocking, and a plain client with only request-failure injection.
|
|
196
|
+
|
|
191
197
|
`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.
|
|
192
198
|
|
|
193
199
|
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.
|
|
@@ -247,6 +253,8 @@ Popups are observed by `Target.targetCreated` with the opener's `openerId`, or b
|
|
|
247
253
|
|
|
248
254
|
`{ "extensionWorld": "hidden" | "select" | "debugger" | "drivable", "as": "world", "requires": ["extensionWorld"] }` runs the C9 compound probe on the current ordinary page. The runner attaches an independent observer to discover actual context ids, unique ids and names, and sends probes through the client session. Discovery matches the expected fixture extension’s exact origin, never the first extension world: `run()`’s optional `extensionWorldId` defaults to shared-web’s harness id `ankmbodmgimjemnojmeddfcdlbocolob`. Multiple unrelated content-script worlds are ignored for fixture selection, while guards must still protect foreign worlds. A custom fixture must provide the same content-script marker contract. Object handles are minted and validated on the unguarded control session, then replayed as guesses on the guarded leg; Chrome object handles and DOM node ids are session-local, so observer handles must never be mistaken for client handles. Numeric ids from the control are also replayed alongside fresh observer-discovered selectors, but numeric ids are local to a tab: a control id may name the next tab’s exposed default context, which must work and return no extension marker. The same-tab hidden world’s actual numeric id and every extension unique-id guess still require a matched refusal. Unknown numeric ids and stale object handles (`not exposed`) need explicit refusals rather than Chrome’s invalid-id errors. `guessesRefused` aggregates only required refusals; `guessesReached` must be false on the guarded leg, and `exposedContextsWork` must remain true. Its result contains boolean evidence consumed by ordinary `result` checks. The unguarded leg validates those selectors against Chrome itself before guarded evidence can pass.
|
|
249
255
|
|
|
256
|
+
`{ "errorPage": "recover", "as": "errorPage", "requires": ["browserSession"] }` (also modes `restore` and `sticky`) runs the protected-error-page compound probe with independent commit evidence, discovery redaction and recovery or sticky-lock checks. The runner leaves this probe’s independent Fetch sessions to the probe rather than answering their pauses itself.
|
|
257
|
+
|
|
250
258
|
`{ "unparseable": true, "as": "unparseable" }` runs the C8 malformed-URL compound probe on the current ordinary page: independent browser ground truth, raw/canonical destination commands, discovery/attach and cookie checks. Boolean evidence is consumed by ordinary `result` checks, with native Chrome outcomes retained in its diagnostics.
|
|
251
259
|
|
|
252
260
|
**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`.
|
|
@@ -283,7 +291,7 @@ It exports:
|
|
|
283
291
|
- `instrument(client, sessionId, log, tag)`, `load(client, sessionId, url)` and `popup(client, openerId)`;
|
|
284
292
|
- `judge(vector, side, observed)`: `{ pass, refused, skipped, mismatches }`, where `refused` lists the leg's refusals and `skipped` the observations that depend on them or explicitly record unavailable observation capabilities;
|
|
285
293
|
- `result(vector, run)`: a report row from `{ target, variant, transport, via?, capabilities?, control, guarded, live }`. `capabilities` is the transport's; without it, the transport has them all;
|
|
286
|
-
- `parseURL(value, base?)`: a `URL`, or `null` for anything `new URL` rejects or a non-string; every parse of a fixture log entry or a Chrome-reported URL in the runner
|
|
294
|
+
- `parseURL(value, base?)`: a `URL`, or `null` for anything `new URL` rejects or a non-string; every parse of a fixture log entry or a Chrome-reported URL in the runner and the fixture goes through it (`protected.mjs` keeps its own copy, since it imports nothing), so an unparseable entry never throws;
|
|
287
295
|
- `owned(entries, tag)`: the fixture log entries whose raw URL contains `tag`, the row's attribution through its `n=`, `tag=` or `to=` query;
|
|
288
296
|
- `summary({ guard, sha, set, results })`: `set` is the parsed `guard-vectors.json`. It returns `{ set, schemaVersion, guard, sha, variants, targets, transports, results, skipped, notRun, missing, pass, line }`. `missing` lists each `{ id, target, variant, transport }` without a row, for every vector whose `scope` is `any` or the guard, every target it lists, every variant in `variants` or `results`, and every transport in `results`; `pass` needs it empty, where `line` is `GUARD-PASS <guard>@<sha> (vectors @ai-ecoverse/slicc-browsers@X.Y.Z)` or `GUARD-FAIL …`.
|
|
289
297
|
|
|
@@ -599,10 +607,10 @@ for case in corpus().cases {
|
|
|
599
607
|
|
|
600
608
|
Go 1.26, Rust stable with `wasm32-wasip1`, Node ≥ 24.
|
|
601
609
|
- `npm run corpus` regenerates `corpus.json` from `corpus/`; the pre-commit hook and CI fail when it's stale.
|
|
602
|
-
- `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.
|
|
610
|
+
- `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`, loads `protected.mjs` and `seven.mjs` with only `URL`, 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.
|
|
603
611
|
- 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 14 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.
|
|
604
612
|
- `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.
|
|
605
|
-
- `npm test`: `validate`, the Go tests in `test/integration/`, and `cargo test --all-features`.
|
|
613
|
+
- `npm test`: `validate`, `test/integration/self-contained.test.mjs`, the Go tests in `test/integration/`, and `cargo test --all-features`.
|
|
606
614
|
- `npm run build`: the Go module, the crate (also for `wasm32-wasip1`), and `npm pack --dry-run`.
|
|
607
615
|
|
|
608
616
|
Changing a table in `slicc-cli/internal/browser` means changing `catalog.json` here, releasing, and bumping the dependency; slicc-cli has no tables of its own. A new behaviour gets a corpus case.
|
package/catalog.json
CHANGED
|
@@ -738,6 +738,11 @@
|
|
|
738
738
|
"aboutScheme": "chrome",
|
|
739
739
|
"targetTypes": ["browser_ui"],
|
|
740
740
|
"inheriting": ["about:blank", "about:srcdoc"],
|
|
741
|
+
"errorPage": {
|
|
742
|
+
"url": "chrome-error://chromewebdata/",
|
|
743
|
+
"target": "previous",
|
|
744
|
+
"redact": "origin"
|
|
745
|
+
},
|
|
741
746
|
"unparseable": "protected",
|
|
742
747
|
"unparseableRefusal": "slicc: refused: slicc can't parse this URL, so it could be a protected page",
|
|
743
748
|
"refusal": "slicc: refused: target is on seven's origin"
|
package/guard-runner.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
result,
|
|
14
14
|
steps,
|
|
15
15
|
} from './guard-driver.mjs';
|
|
16
|
+
import { protectedErrorPage } from './protected-error-page.mjs';
|
|
16
17
|
import { canonicalURL, unparseableURLs } from './unparseable-url.mjs';
|
|
17
18
|
|
|
18
19
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -115,6 +116,7 @@ class Execution {
|
|
|
115
116
|
this.errors = [];
|
|
116
117
|
this.notes = [];
|
|
117
118
|
this.answers = [];
|
|
119
|
+
this.internalSessions = new Set();
|
|
118
120
|
this.attachSettings = new Map();
|
|
119
121
|
this.childSteps = [];
|
|
120
122
|
this.deferred = [];
|
|
@@ -450,7 +452,10 @@ class Execution {
|
|
|
450
452
|
for (const event of this.client.events.slice(this.eventStart)) {
|
|
451
453
|
if (this.processed.has(event)) continue;
|
|
452
454
|
this.processed.add(event);
|
|
453
|
-
if (
|
|
455
|
+
if (
|
|
456
|
+
(event.method === 'Fetch.requestPaused' || event.method === 'Fetch.authRequired') &&
|
|
457
|
+
!this.internalSessions.has(event.sessionId)
|
|
458
|
+
) {
|
|
454
459
|
this.pending.push(
|
|
455
460
|
this.answer(event).catch((error) => this.errors.push(String(error.message ?? error)))
|
|
456
461
|
);
|
|
@@ -604,9 +609,9 @@ class Execution {
|
|
|
604
609
|
this.errors.push(
|
|
605
610
|
`${step.refusal.name} failed without a refusal: ${String(out.error?.message ?? out.error)}`
|
|
606
611
|
);
|
|
607
|
-
if ((step.extensionWorld || step.unparseable) && !out.ok)
|
|
612
|
+
if ((step.extensionWorld || step.unparseable || step.errorPage) && !out.ok)
|
|
608
613
|
this.errors.push(
|
|
609
|
-
`${step.unparseable ? 'unparseable' : 'extensionWorld'}: ${out.error?.message ?? 'probe failed'}`
|
|
614
|
+
`${step.unparseable ? 'unparseable' : step.errorPage ? 'errorPage' : 'extensionWorld'}: ${out.error?.message ?? 'probe failed'}`
|
|
610
615
|
);
|
|
611
616
|
if (step.as && !out.result?.pending) this.calls[step.as] = out;
|
|
612
617
|
if (step.cdp) (this.calls.$cdp ??= []).push({ method: step.cdp, ...out });
|
|
@@ -657,6 +662,21 @@ class Execution {
|
|
|
657
662
|
await this.settle(step.settle);
|
|
658
663
|
return;
|
|
659
664
|
}
|
|
665
|
+
if (step.errorPage)
|
|
666
|
+
return outcome(
|
|
667
|
+
protectedErrorPage({
|
|
668
|
+
client: this.client,
|
|
669
|
+
reader: this.reader,
|
|
670
|
+
session,
|
|
671
|
+
targetId: this.pageTarget,
|
|
672
|
+
ordinary: this.vars.ordinary,
|
|
673
|
+
protectedOrigin: this.vars.seven,
|
|
674
|
+
tag: this.vars.tag,
|
|
675
|
+
internalSessions: this.internalSessions,
|
|
676
|
+
mode: step.errorPage,
|
|
677
|
+
side: this.side,
|
|
678
|
+
})
|
|
679
|
+
);
|
|
660
680
|
if (step.unparseable)
|
|
661
681
|
return outcome(
|
|
662
682
|
unparseableURLs({
|
package/guard-vectors.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"setVersion": "1.
|
|
3
|
+
"setVersion": "1.11.0",
|
|
4
4
|
"sources": [
|
|
5
5
|
{
|
|
6
6
|
"repo": "ai-ecoverse/slicc-cli",
|
|
@@ -118,6 +118,464 @@
|
|
|
118
118
|
"browserUi": "the id of one of Chrome's own browser_ui targets, which the runner looks up with its own CDP"
|
|
119
119
|
},
|
|
120
120
|
"vectors": [
|
|
121
|
+
{
|
|
122
|
+
"id": "C8.protected-error-page",
|
|
123
|
+
"family": "C",
|
|
124
|
+
"kind": "cdp",
|
|
125
|
+
"scope": "any",
|
|
126
|
+
"defaultContext": true,
|
|
127
|
+
"setup": [
|
|
128
|
+
{
|
|
129
|
+
"page": "tab",
|
|
130
|
+
"url": "{ordinary}/blank"
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"trigger": [
|
|
134
|
+
{
|
|
135
|
+
"errorPage": "recover",
|
|
136
|
+
"requires": ["browserSession"],
|
|
137
|
+
"as": "errorPage"
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"targets": ["seven"],
|
|
141
|
+
"requires": ["browserSession"],
|
|
142
|
+
"observe": {
|
|
143
|
+
"live": {
|
|
144
|
+
"means": "protected error-page recover: live",
|
|
145
|
+
"check": {
|
|
146
|
+
"result": "errorPage",
|
|
147
|
+
"path": "result.live",
|
|
148
|
+
"equals": true,
|
|
149
|
+
"observer": "client"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"attached": {
|
|
153
|
+
"means": "protected error-page recover: attached",
|
|
154
|
+
"check": {
|
|
155
|
+
"result": "errorPage",
|
|
156
|
+
"path": "result.attached",
|
|
157
|
+
"equals": true,
|
|
158
|
+
"observer": "client"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"infoRedacted": {
|
|
162
|
+
"means": "protected error-page recover: infoRedacted",
|
|
163
|
+
"check": {
|
|
164
|
+
"result": "errorPage",
|
|
165
|
+
"path": "result.infoRedacted",
|
|
166
|
+
"equals": true,
|
|
167
|
+
"observer": "client"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"listRedacted": {
|
|
171
|
+
"means": "protected error-page recover: listRedacted",
|
|
172
|
+
"check": {
|
|
173
|
+
"result": "errorPage",
|
|
174
|
+
"path": "result.listRedacted",
|
|
175
|
+
"equals": true,
|
|
176
|
+
"observer": "client"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"eventsRedacted": {
|
|
180
|
+
"means": "protected error-page recover: eventsRedacted",
|
|
181
|
+
"check": {
|
|
182
|
+
"result": "errorPage",
|
|
183
|
+
"path": "result.eventsRedacted",
|
|
184
|
+
"equals": true,
|
|
185
|
+
"observer": "client"
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"reload": {
|
|
189
|
+
"means": "protected error-page recover: reload",
|
|
190
|
+
"check": {
|
|
191
|
+
"result": "errorPage",
|
|
192
|
+
"path": "result.reload",
|
|
193
|
+
"equals": true,
|
|
194
|
+
"observer": "client"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"blank": {
|
|
198
|
+
"means": "protected error-page recover: blank",
|
|
199
|
+
"check": {
|
|
200
|
+
"result": "errorPage",
|
|
201
|
+
"path": "result.blank",
|
|
202
|
+
"equals": true,
|
|
203
|
+
"observer": "client"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"close": {
|
|
207
|
+
"means": "protected error-page recover: close",
|
|
208
|
+
"check": {
|
|
209
|
+
"result": "errorPage",
|
|
210
|
+
"path": "result.close",
|
|
211
|
+
"equals": true,
|
|
212
|
+
"observer": "client"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"live": "the independent observer sees chrome-error://chromewebdata/ with unreachableUrl exactly matching targetInfo.url; real-document controls require a BackForwardCacheRestore commit",
|
|
217
|
+
"liveCheck": {
|
|
218
|
+
"result": "errorPage",
|
|
219
|
+
"path": "result.live",
|
|
220
|
+
"equals": true,
|
|
221
|
+
"observer": "client"
|
|
222
|
+
},
|
|
223
|
+
"control": {
|
|
224
|
+
"origin": "ordinary",
|
|
225
|
+
"via": "unguarded",
|
|
226
|
+
"expected": {
|
|
227
|
+
"live": true,
|
|
228
|
+
"attached": true,
|
|
229
|
+
"infoRedacted": false,
|
|
230
|
+
"listRedacted": false,
|
|
231
|
+
"eventsRedacted": false,
|
|
232
|
+
"reload": true,
|
|
233
|
+
"blank": true,
|
|
234
|
+
"close": true
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"expected": {
|
|
238
|
+
"live": true,
|
|
239
|
+
"attached": true,
|
|
240
|
+
"infoRedacted": true,
|
|
241
|
+
"listRedacted": true,
|
|
242
|
+
"eventsRedacted": true,
|
|
243
|
+
"reload": true,
|
|
244
|
+
"blank": true,
|
|
245
|
+
"close": true
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"id": "C8.protected-document-restore",
|
|
250
|
+
"family": "C",
|
|
251
|
+
"kind": "cdp",
|
|
252
|
+
"scope": "any",
|
|
253
|
+
"defaultContext": true,
|
|
254
|
+
"setup": [
|
|
255
|
+
{
|
|
256
|
+
"page": "tab",
|
|
257
|
+
"url": "{ordinary}/blank"
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
"trigger": [
|
|
261
|
+
{
|
|
262
|
+
"errorPage": "restore",
|
|
263
|
+
"requires": ["browserSession"],
|
|
264
|
+
"as": "errorPage"
|
|
265
|
+
}
|
|
266
|
+
],
|
|
267
|
+
"targets": ["seven"],
|
|
268
|
+
"requires": ["browserSession"],
|
|
269
|
+
"observe": {
|
|
270
|
+
"live": {
|
|
271
|
+
"means": "protected error-page restore: live",
|
|
272
|
+
"check": {
|
|
273
|
+
"result": "errorPage",
|
|
274
|
+
"path": "result.live",
|
|
275
|
+
"equals": true,
|
|
276
|
+
"observer": "client"
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
"attached": {
|
|
280
|
+
"means": "protected error-page restore: attached",
|
|
281
|
+
"check": {
|
|
282
|
+
"result": "errorPage",
|
|
283
|
+
"path": "result.attached",
|
|
284
|
+
"equals": true,
|
|
285
|
+
"observer": "client"
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"infoRedacted": {
|
|
289
|
+
"means": "protected error-page restore: infoRedacted",
|
|
290
|
+
"check": {
|
|
291
|
+
"result": "errorPage",
|
|
292
|
+
"path": "result.infoRedacted",
|
|
293
|
+
"equals": true,
|
|
294
|
+
"observer": "client"
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"listRedacted": {
|
|
298
|
+
"means": "protected error-page restore: listRedacted",
|
|
299
|
+
"check": {
|
|
300
|
+
"result": "errorPage",
|
|
301
|
+
"path": "result.listRedacted",
|
|
302
|
+
"equals": true,
|
|
303
|
+
"observer": "client"
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"eventsRedacted": {
|
|
307
|
+
"means": "protected error-page restore: eventsRedacted",
|
|
308
|
+
"check": {
|
|
309
|
+
"result": "errorPage",
|
|
310
|
+
"path": "result.eventsRedacted",
|
|
311
|
+
"equals": true,
|
|
312
|
+
"observer": "client"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
"reload": {
|
|
316
|
+
"means": "protected error-page restore: reload",
|
|
317
|
+
"check": {
|
|
318
|
+
"result": "errorPage",
|
|
319
|
+
"path": "result.reload",
|
|
320
|
+
"equals": true,
|
|
321
|
+
"observer": "client"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"blank": {
|
|
325
|
+
"means": "protected error-page restore: blank",
|
|
326
|
+
"check": {
|
|
327
|
+
"result": "errorPage",
|
|
328
|
+
"path": "result.blank",
|
|
329
|
+
"equals": true,
|
|
330
|
+
"observer": "client"
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
"restored": {
|
|
334
|
+
"means": "protected error-page restore: restored",
|
|
335
|
+
"check": {
|
|
336
|
+
"result": "errorPage",
|
|
337
|
+
"path": "result.restored",
|
|
338
|
+
"equals": true,
|
|
339
|
+
"observer": "client"
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"evaluateRefused": {
|
|
343
|
+
"means": "protected error-page restore: evaluateRefused",
|
|
344
|
+
"check": {
|
|
345
|
+
"result": "errorPage",
|
|
346
|
+
"path": "result.evaluateRefused",
|
|
347
|
+
"equals": true,
|
|
348
|
+
"observer": "client"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"attachRefused": {
|
|
352
|
+
"means": "protected error-page restore: attachRefused",
|
|
353
|
+
"check": {
|
|
354
|
+
"result": "errorPage",
|
|
355
|
+
"path": "result.attachRefused",
|
|
356
|
+
"equals": true,
|
|
357
|
+
"observer": "client"
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
"closeRefused": {
|
|
361
|
+
"means": "protected error-page restore: closeRefused",
|
|
362
|
+
"check": {
|
|
363
|
+
"result": "errorPage",
|
|
364
|
+
"path": "result.closeRefused",
|
|
365
|
+
"equals": true,
|
|
366
|
+
"observer": "client"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
"live": "the independent observer sees chrome-error://chromewebdata/ with unreachableUrl exactly matching targetInfo.url; real-document controls require a BackForwardCacheRestore commit",
|
|
371
|
+
"liveCheck": {
|
|
372
|
+
"result": "errorPage",
|
|
373
|
+
"path": "result.live",
|
|
374
|
+
"equals": true,
|
|
375
|
+
"observer": "client"
|
|
376
|
+
},
|
|
377
|
+
"control": {
|
|
378
|
+
"origin": "ordinary",
|
|
379
|
+
"via": "unguarded",
|
|
380
|
+
"expected": {
|
|
381
|
+
"live": true,
|
|
382
|
+
"attached": true,
|
|
383
|
+
"infoRedacted": false,
|
|
384
|
+
"listRedacted": false,
|
|
385
|
+
"eventsRedacted": false,
|
|
386
|
+
"reload": true,
|
|
387
|
+
"blank": true,
|
|
388
|
+
"restored": true,
|
|
389
|
+
"evaluateRefused": false,
|
|
390
|
+
"attachRefused": false,
|
|
391
|
+
"closeRefused": false
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
"expected": {
|
|
395
|
+
"live": true,
|
|
396
|
+
"attached": true,
|
|
397
|
+
"infoRedacted": true,
|
|
398
|
+
"listRedacted": true,
|
|
399
|
+
"eventsRedacted": true,
|
|
400
|
+
"reload": true,
|
|
401
|
+
"blank": true,
|
|
402
|
+
"restored": true,
|
|
403
|
+
"evaluateRefused": true,
|
|
404
|
+
"attachRefused": true,
|
|
405
|
+
"closeRefused": true
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"id": "C8.protected-error-page-sticky",
|
|
410
|
+
"family": "C",
|
|
411
|
+
"kind": "cdp",
|
|
412
|
+
"scope": "any",
|
|
413
|
+
"defaultContext": true,
|
|
414
|
+
"setup": [
|
|
415
|
+
{
|
|
416
|
+
"page": "tab",
|
|
417
|
+
"url": "{ordinary}/blank"
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
"trigger": [
|
|
421
|
+
{
|
|
422
|
+
"errorPage": "sticky",
|
|
423
|
+
"requires": ["browserSession"],
|
|
424
|
+
"as": "errorPage"
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
"targets": ["seven"],
|
|
428
|
+
"requires": ["browserSession"],
|
|
429
|
+
"observe": {
|
|
430
|
+
"live": {
|
|
431
|
+
"means": "protected error-page sticky: live",
|
|
432
|
+
"check": {
|
|
433
|
+
"result": "errorPage",
|
|
434
|
+
"path": "result.live",
|
|
435
|
+
"equals": true,
|
|
436
|
+
"observer": "client"
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
"attached": {
|
|
440
|
+
"means": "protected error-page sticky: attached",
|
|
441
|
+
"check": {
|
|
442
|
+
"result": "errorPage",
|
|
443
|
+
"path": "result.attached",
|
|
444
|
+
"equals": true,
|
|
445
|
+
"observer": "client"
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
"infoRedacted": {
|
|
449
|
+
"means": "protected error-page sticky: infoRedacted",
|
|
450
|
+
"check": {
|
|
451
|
+
"result": "errorPage",
|
|
452
|
+
"path": "result.infoRedacted",
|
|
453
|
+
"equals": true,
|
|
454
|
+
"observer": "client"
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
"listRedacted": {
|
|
458
|
+
"means": "protected error-page sticky: listRedacted",
|
|
459
|
+
"check": {
|
|
460
|
+
"result": "errorPage",
|
|
461
|
+
"path": "result.listRedacted",
|
|
462
|
+
"equals": true,
|
|
463
|
+
"observer": "client"
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
"eventsRedacted": {
|
|
467
|
+
"means": "protected error-page sticky: eventsRedacted",
|
|
468
|
+
"check": {
|
|
469
|
+
"result": "errorPage",
|
|
470
|
+
"path": "result.eventsRedacted",
|
|
471
|
+
"equals": true,
|
|
472
|
+
"observer": "client"
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
"reload": {
|
|
476
|
+
"means": "protected error-page sticky: reload",
|
|
477
|
+
"check": {
|
|
478
|
+
"result": "errorPage",
|
|
479
|
+
"path": "result.reload",
|
|
480
|
+
"equals": true,
|
|
481
|
+
"observer": "client"
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
"blank": {
|
|
485
|
+
"means": "protected error-page sticky: blank",
|
|
486
|
+
"check": {
|
|
487
|
+
"result": "errorPage",
|
|
488
|
+
"path": "result.blank",
|
|
489
|
+
"equals": true,
|
|
490
|
+
"observer": "client"
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
"restored": {
|
|
494
|
+
"means": "protected error-page sticky: restored",
|
|
495
|
+
"check": {
|
|
496
|
+
"result": "errorPage",
|
|
497
|
+
"path": "result.restored",
|
|
498
|
+
"equals": true,
|
|
499
|
+
"observer": "client"
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
"evaluateRefused": {
|
|
503
|
+
"means": "protected error-page sticky: evaluateRefused",
|
|
504
|
+
"check": {
|
|
505
|
+
"result": "errorPage",
|
|
506
|
+
"path": "result.evaluateRefused",
|
|
507
|
+
"equals": true,
|
|
508
|
+
"observer": "client"
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
"attachRefused": {
|
|
512
|
+
"means": "protected error-page sticky: attachRefused",
|
|
513
|
+
"check": {
|
|
514
|
+
"result": "errorPage",
|
|
515
|
+
"path": "result.attachRefused",
|
|
516
|
+
"equals": true,
|
|
517
|
+
"observer": "client"
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
"closeRefused": {
|
|
521
|
+
"means": "protected error-page sticky: closeRefused",
|
|
522
|
+
"check": {
|
|
523
|
+
"result": "errorPage",
|
|
524
|
+
"path": "result.closeRefused",
|
|
525
|
+
"equals": true,
|
|
526
|
+
"observer": "client"
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
"stickyError": {
|
|
530
|
+
"means": "protected error-page sticky: stickyError",
|
|
531
|
+
"check": {
|
|
532
|
+
"result": "errorPage",
|
|
533
|
+
"path": "result.stickyError",
|
|
534
|
+
"equals": true,
|
|
535
|
+
"observer": "client"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
"live": "the independent observer sees chrome-error://chromewebdata/ with unreachableUrl exactly matching targetInfo.url; real-document controls require a BackForwardCacheRestore commit",
|
|
540
|
+
"liveCheck": {
|
|
541
|
+
"result": "errorPage",
|
|
542
|
+
"path": "result.live",
|
|
543
|
+
"equals": true,
|
|
544
|
+
"observer": "client"
|
|
545
|
+
},
|
|
546
|
+
"control": {
|
|
547
|
+
"origin": "ordinary",
|
|
548
|
+
"via": "unguarded",
|
|
549
|
+
"expected": {
|
|
550
|
+
"live": true,
|
|
551
|
+
"attached": true,
|
|
552
|
+
"infoRedacted": false,
|
|
553
|
+
"listRedacted": false,
|
|
554
|
+
"eventsRedacted": false,
|
|
555
|
+
"reload": true,
|
|
556
|
+
"blank": true,
|
|
557
|
+
"restored": true,
|
|
558
|
+
"evaluateRefused": false,
|
|
559
|
+
"attachRefused": false,
|
|
560
|
+
"closeRefused": false,
|
|
561
|
+
"stickyError": true
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
"expected": {
|
|
565
|
+
"live": true,
|
|
566
|
+
"attached": true,
|
|
567
|
+
"infoRedacted": true,
|
|
568
|
+
"listRedacted": true,
|
|
569
|
+
"eventsRedacted": true,
|
|
570
|
+
"reload": true,
|
|
571
|
+
"blank": true,
|
|
572
|
+
"restored": true,
|
|
573
|
+
"evaluateRefused": true,
|
|
574
|
+
"attachRefused": true,
|
|
575
|
+
"closeRefused": true,
|
|
576
|
+
"stickyError": true
|
|
577
|
+
}
|
|
578
|
+
},
|
|
121
579
|
{
|
|
122
580
|
"id": "C8.unparseable",
|
|
123
581
|
"family": "C",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-browsers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"guard-runner.mjs",
|
|
53
53
|
"fixture-server.mjs",
|
|
54
54
|
"extension-world.mjs",
|
|
55
|
-
"unparseable-url.mjs"
|
|
55
|
+
"unparseable-url.mjs",
|
|
56
|
+
"protected-error-page.mjs"
|
|
56
57
|
],
|
|
57
58
|
"scripts": {
|
|
58
59
|
"prepare": "lefthook install",
|
|
@@ -60,21 +61,21 @@
|
|
|
60
61
|
"validate": "node scripts/validate.mjs",
|
|
61
62
|
"lint": "slicc-lint && test -z \"$(gofmt -l .)\" && go mod tidy -diff && go vet ./... && golangci-lint run && cargo fmt --check && cargo clippy --all-features --all-targets -- -D warnings",
|
|
62
63
|
"format": "biome check --write . && gofmt -w . && cargo fmt",
|
|
63
|
-
"test": "npm run validate && go test -race -count=1 ./test/integration/... && cargo test --all-features",
|
|
64
|
+
"test": "npm run validate && node --test test/integration/self-contained.test.mjs && go test -race -count=1 ./test/integration/... && cargo test --all-features",
|
|
64
65
|
"test:unit": "go test -race -cover ./...",
|
|
65
66
|
"build": "go build ./... && cargo build --all-features && cargo build --target wasm32-wasip1 --all-features && npm pack --dry-run",
|
|
66
67
|
"trust": "npx fledgling sync -y --skip-publish",
|
|
67
68
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish",
|
|
68
69
|
"release": "semantic-release",
|
|
69
70
|
"test:guard": "npm run test:guard-integration && npm run test:guard-sweep",
|
|
70
|
-
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs 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-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs",
|
|
71
|
+
"test:guard-integration": "node --test --test-concurrency=2 test/integration/guard-lifecycle.test.mjs 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-worlds.test.mjs test/integration/guard-world-discovery.test.mjs test/integration/guard-world-guesses.test.mjs test/integration/guard-unparseable.test.mjs test/integration/guard-error-page.test.mjs test/integration/guard-scales.test.mjs test/integration/guard-shared-log.test.mjs test/integration/self-contained.test.mjs",
|
|
71
72
|
"test:guard-sweep": "node scripts/test-guards.mjs",
|
|
72
73
|
"test:gates": "node --test test/integration/gates.test.mjs",
|
|
73
74
|
"test:guard-target": "node --test test/integration/guard-runner.test.mjs",
|
|
74
75
|
"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"
|
|
75
76
|
},
|
|
76
77
|
"devDependencies": {
|
|
77
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
78
|
+
"@ai-ecoverse/slicc-shared-web": "1.28.0",
|
|
78
79
|
"@biomejs/biome": "2.5.15",
|
|
79
80
|
"ajv": "8.20.0",
|
|
80
81
|
"ajv-formats": "3.0.1",
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { outcome } from './guard-driver.mjs';
|
|
2
|
+
|
|
3
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
const refused = (out) => !out.ok && /^slicc: refused:/.test(out.error?.message ?? '');
|
|
5
|
+
|
|
6
|
+
async function until(check) {
|
|
7
|
+
const deadline = Date.now() + 5000;
|
|
8
|
+
do {
|
|
9
|
+
if (await check()) return true;
|
|
10
|
+
await pause(25);
|
|
11
|
+
} while (Date.now() < deadline);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function loadedDocument(reader, observer, url) {
|
|
16
|
+
return until(async () => {
|
|
17
|
+
const { frameTree } = await reader.send('Page.getFrameTree', {}, observer);
|
|
18
|
+
if (frameTree.frame.url !== url || frameTree.frame.unreachableUrl) return false;
|
|
19
|
+
const ready = await outcome(
|
|
20
|
+
reader.send(
|
|
21
|
+
'Runtime.evaluate',
|
|
22
|
+
{ expression: 'document.readyState', returnByValue: true },
|
|
23
|
+
observer
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
return ready.result?.result?.value === 'complete';
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function lockChecks({ client, session, targetId, result }) {
|
|
31
|
+
result.evaluateRefused = refused(
|
|
32
|
+
await outcome(
|
|
33
|
+
client.send(
|
|
34
|
+
'Runtime.evaluate',
|
|
35
|
+
{ expression: 'document.title', returnByValue: true },
|
|
36
|
+
session
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
result.attachRefused = refused(
|
|
41
|
+
await outcome(client.send('Target.attachToTarget', { targetId, flatten: true }))
|
|
42
|
+
);
|
|
43
|
+
result.closeRefused = refused(await outcome(client.send('Target.closeTarget', { targetId })));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function redaction({ client, targetId, failedURL, protectedOrigin, start, result }) {
|
|
47
|
+
const info = await outcome(client.send('Target.getTargetInfo', { targetId }));
|
|
48
|
+
const list = await outcome(client.send('Target.getTargets'));
|
|
49
|
+
const origin = new URL(failedURL).origin;
|
|
50
|
+
result.infoRedacted =
|
|
51
|
+
info.ok && info.result.targetInfo.url === origin && info.result.targetInfo.title === origin;
|
|
52
|
+
result.listRedacted =
|
|
53
|
+
list.ok &&
|
|
54
|
+
list.result.targetInfos.some(
|
|
55
|
+
(i) => i.targetId === targetId && i.url === origin && i.title === origin
|
|
56
|
+
);
|
|
57
|
+
await pause(100);
|
|
58
|
+
const targetEvents = client.events
|
|
59
|
+
.slice(start)
|
|
60
|
+
.filter(
|
|
61
|
+
(e) =>
|
|
62
|
+
e.params?.targetInfo?.targetId === targetId &&
|
|
63
|
+
e.params.targetInfo.url.includes(new URL(protectedOrigin).host)
|
|
64
|
+
);
|
|
65
|
+
result.eventsRedacted =
|
|
66
|
+
targetEvents.length > 0 &&
|
|
67
|
+
targetEvents.every(
|
|
68
|
+
(e) => e.params.targetInfo.url === origin && e.params.targetInfo.title === origin
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function protectedErrorPage({
|
|
73
|
+
client,
|
|
74
|
+
reader,
|
|
75
|
+
ordinary,
|
|
76
|
+
protectedOrigin,
|
|
77
|
+
tag,
|
|
78
|
+
mode,
|
|
79
|
+
side,
|
|
80
|
+
internalSessions = new Set(),
|
|
81
|
+
}) {
|
|
82
|
+
const initial = `${protectedOrigin}/doc?n=${tag}-history`;
|
|
83
|
+
const failedURL = `${protectedOrigin}/doc/path-secret-${tag}?query-secret=${tag}#fragment-secret-${tag}`;
|
|
84
|
+
const result = {};
|
|
85
|
+
const start = client.events.length;
|
|
86
|
+
const { targetId } = await reader.send('Target.createTarget', { url: initial, background: true });
|
|
87
|
+
let session;
|
|
88
|
+
let observer;
|
|
89
|
+
let failing = false;
|
|
90
|
+
let cursor = reader.events.length;
|
|
91
|
+
const { sessionId } = await reader.send('Target.attachToTarget', { targetId, flatten: true });
|
|
92
|
+
observer = sessionId;
|
|
93
|
+
internalSessions.add(observer);
|
|
94
|
+
const frame = async () => (await reader.send('Page.getFrameTree', {}, observer)).frameTree.frame;
|
|
95
|
+
const loaded = (url) => loadedDocument(reader, observer, url);
|
|
96
|
+
let stopped = false;
|
|
97
|
+
const fail = (async () => {
|
|
98
|
+
while (!stopped) {
|
|
99
|
+
for (const event of reader.events.slice(cursor)) {
|
|
100
|
+
cursor++;
|
|
101
|
+
if (event.method === 'Fetch.requestPaused' && event.sessionId === observer)
|
|
102
|
+
await outcome(
|
|
103
|
+
reader.send(
|
|
104
|
+
failing ? 'Fetch.failRequest' : 'Fetch.continueRequest',
|
|
105
|
+
failing
|
|
106
|
+
? { requestId: event.params.requestId, errorReason: 'BlockedByClient' }
|
|
107
|
+
: { requestId: event.params.requestId },
|
|
108
|
+
observer
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
await pause(5);
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
try {
|
|
116
|
+
await reader.send('Page.enable', {}, observer);
|
|
117
|
+
if (!(await loaded(initial)))
|
|
118
|
+
throw new Error('protected-error-page: history seed did not commit');
|
|
119
|
+
await reader.send('Page.navigate', { url: `${ordinary}/blank` }, observer);
|
|
120
|
+
if (!(await loaded(`${ordinary}/blank`)))
|
|
121
|
+
throw new Error('protected-error-page: allowed document did not commit');
|
|
122
|
+
const attached = await outcome(
|
|
123
|
+
client.send('Target.attachToTarget', { targetId, flatten: true })
|
|
124
|
+
);
|
|
125
|
+
session = attached.result?.sessionId;
|
|
126
|
+
if (!session) return { live: false, attached: false };
|
|
127
|
+
await client.send('Page.enable', {}, session);
|
|
128
|
+
await client.send('Runtime.enable', {}, session);
|
|
129
|
+
await client.send('Target.setDiscoverTargets', { discover: true });
|
|
130
|
+
if (side === 'control') {
|
|
131
|
+
failing = true;
|
|
132
|
+
await reader.send(
|
|
133
|
+
'Fetch.enable',
|
|
134
|
+
{ patterns: [{ resourceType: 'Document', requestStage: 'Request' }] },
|
|
135
|
+
observer
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
await client.send(
|
|
139
|
+
'Runtime.evaluate',
|
|
140
|
+
{ expression: `setTimeout(()=>location.href=${JSON.stringify(failedURL)},0)` },
|
|
141
|
+
session
|
|
142
|
+
);
|
|
143
|
+
result.live = await until(async () => {
|
|
144
|
+
const f = await frame();
|
|
145
|
+
const { targetInfo } = await reader.send('Target.getTargetInfo', { targetId });
|
|
146
|
+
result.evidence = { frame: f, targetInfo };
|
|
147
|
+
return (
|
|
148
|
+
f.url === 'chrome-error://chromewebdata/' &&
|
|
149
|
+
f.securityOrigin === '://' &&
|
|
150
|
+
f.unreachableUrl === failedURL &&
|
|
151
|
+
targetInfo.url === failedURL
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
if (!result.live && side !== 'control') return result;
|
|
155
|
+
if (!result.live)
|
|
156
|
+
throw new Error(
|
|
157
|
+
`protected-error-page: missing error commit ${JSON.stringify(result.evidence)}`
|
|
158
|
+
);
|
|
159
|
+
await redaction({ client, targetId, failedURL, protectedOrigin, start, result });
|
|
160
|
+
const beforeReload = (await frame()).loaderId;
|
|
161
|
+
result.reload = (await outcome(client.send('Page.reload', {}, session))).ok;
|
|
162
|
+
if (result.reload) {
|
|
163
|
+
result.reloadedError = await until(async () => {
|
|
164
|
+
const f = await frame();
|
|
165
|
+
return f.loaderId !== beforeReload && f.unreachableUrl === failedURL;
|
|
166
|
+
});
|
|
167
|
+
result.reload = result.reloadedError;
|
|
168
|
+
}
|
|
169
|
+
result.blank = (
|
|
170
|
+
await outcome(client.send('Page.navigate', { url: 'about:blank' }, session))
|
|
171
|
+
).ok;
|
|
172
|
+
if (result.blank) result.blank = await loaded('about:blank');
|
|
173
|
+
result.attached = !client.events
|
|
174
|
+
.slice(start)
|
|
175
|
+
.some((e) => e.method === 'Target.detachedFromTarget' && e.params.sessionId === session);
|
|
176
|
+
if (mode === 'recover') {
|
|
177
|
+
if (result.blank) {
|
|
178
|
+
await client.send(
|
|
179
|
+
'Runtime.evaluate',
|
|
180
|
+
{ expression: `setTimeout(()=>location.href=${JSON.stringify(failedURL)},0)` },
|
|
181
|
+
session
|
|
182
|
+
);
|
|
183
|
+
result.closeError = await until(async () => (await frame()).unreachableUrl === failedURL);
|
|
184
|
+
}
|
|
185
|
+
result.attached &&= !client.events
|
|
186
|
+
.slice(start)
|
|
187
|
+
.some((e) => e.method === 'Target.detachedFromTarget' && e.params.sessionId === session);
|
|
188
|
+
const close = await outcome(client.send('Target.closeTarget', { targetId }));
|
|
189
|
+
result.close = result.closeError === true && close.result?.success === true;
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
if (side === 'control') await reader.send('Fetch.disable', {}, observer);
|
|
193
|
+
failing = false;
|
|
194
|
+
const { entries, currentIndex } = await reader.send('Page.getNavigationHistory', {}, observer);
|
|
195
|
+
const index = entries.findIndex((e) => e.url === initial);
|
|
196
|
+
if (index < 0) throw new Error('protected-error-page: missing protected history');
|
|
197
|
+
const beforeRestore = reader.events.length;
|
|
198
|
+
await reader.send(
|
|
199
|
+
'Runtime.evaluate',
|
|
200
|
+
{ expression: `history.go(${index - currentIndex})` },
|
|
201
|
+
observer
|
|
202
|
+
);
|
|
203
|
+
result.restored = await until(() =>
|
|
204
|
+
reader.events
|
|
205
|
+
.slice(beforeRestore)
|
|
206
|
+
.some(
|
|
207
|
+
(e) =>
|
|
208
|
+
e.sessionId === observer &&
|
|
209
|
+
e.method === 'Page.frameNavigated' &&
|
|
210
|
+
e.params.type === 'BackForwardCacheRestore' &&
|
|
211
|
+
e.params.frame.url === initial
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
if (!result.restored)
|
|
215
|
+
throw new Error('protected-error-page: real protected BFCache restore required');
|
|
216
|
+
await pause(100);
|
|
217
|
+
if (mode === 'sticky') {
|
|
218
|
+
await reader.send('Target.detachFromTarget', { sessionId: observer });
|
|
219
|
+
observer = (await reader.send('Target.attachToTarget', { targetId, flatten: true }))
|
|
220
|
+
.sessionId;
|
|
221
|
+
internalSessions.add(observer);
|
|
222
|
+
failing = true;
|
|
223
|
+
await reader.send(
|
|
224
|
+
'Fetch.enable',
|
|
225
|
+
{ patterns: [{ resourceType: 'Document', requestStage: 'Request' }] },
|
|
226
|
+
observer
|
|
227
|
+
);
|
|
228
|
+
await reader.send('Page.navigate', { url: failedURL }, observer);
|
|
229
|
+
result.stickyError = await until(async () => (await frame()).unreachableUrl === failedURL);
|
|
230
|
+
if (!result.stickyError)
|
|
231
|
+
throw new Error(
|
|
232
|
+
`protected-error-page: missing sticky error ${JSON.stringify(await frame())}`
|
|
233
|
+
);
|
|
234
|
+
await pause(100);
|
|
235
|
+
}
|
|
236
|
+
await lockChecks({ client, session, targetId, result });
|
|
237
|
+
return result;
|
|
238
|
+
} finally {
|
|
239
|
+
stopped = true;
|
|
240
|
+
await fail;
|
|
241
|
+
await outcome(reader.send('Target.closeTarget', { targetId }));
|
|
242
|
+
}
|
|
243
|
+
}
|
package/protected.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
+
"errorPage": { "url": "chrome-error://chromewebdata/", "target": "previous", "redact": "origin" },
|
|
3
4
|
"unparseable": "protected",
|
|
4
5
|
"unparseableRefusal": "slicc: refused: slicc can't parse this URL, so it could be a protected page",
|
|
5
6
|
"origins": ["http://127.0.0.1:5710", "https://seven.example.test"],
|
|
@@ -738,6 +739,42 @@
|
|
|
738
739
|
}
|
|
739
740
|
],
|
|
740
741
|
"targets": [
|
|
742
|
+
{
|
|
743
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
744
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
745
|
+
"unreachableURL": "https://seven.sliccy.ai/other",
|
|
746
|
+
"protected": true
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
750
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
751
|
+
"unreachableURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
752
|
+
"navigating": true,
|
|
753
|
+
"protected": true
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
757
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
758
|
+
"protected": true
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
762
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
763
|
+
"unreachableURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
764
|
+
"protected": false
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
768
|
+
"documentURL": "chrome-error://chromewebdata/",
|
|
769
|
+
"unreachableURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
770
|
+
"previous": true,
|
|
771
|
+
"protected": true
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
"url": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
775
|
+
"documentURL": "https://seven.sliccy.ai/path?secret=query#fragment",
|
|
776
|
+
"protected": true
|
|
777
|
+
},
|
|
741
778
|
{
|
|
742
779
|
"url": "http://exa mple.sliccy.ai/",
|
|
743
780
|
"protected": true
|
package/protected.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
const parse = (text) => {
|
|
2
|
+
if (typeof text !== 'string') return null;
|
|
3
|
+
try {
|
|
4
|
+
return new URL(text);
|
|
5
|
+
} catch {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
2
9
|
|
|
3
10
|
const scheme = (u) => u.protocol.slice(0, -1);
|
|
4
11
|
|
|
@@ -61,8 +68,23 @@ export function protectedOrigins(rules, extraOrigins = []) {
|
|
|
61
68
|
);
|
|
62
69
|
};
|
|
63
70
|
|
|
64
|
-
const target = ({
|
|
71
|
+
const target = ({
|
|
72
|
+
url: text,
|
|
73
|
+
previous = false,
|
|
74
|
+
opener,
|
|
75
|
+
type,
|
|
76
|
+
documentURL,
|
|
77
|
+
unreachableURL,
|
|
78
|
+
navigating = false,
|
|
79
|
+
}) => {
|
|
65
80
|
if (type && (rules.targetTypes ?? []).includes(type)) return true;
|
|
81
|
+
if (
|
|
82
|
+
rules.errorPage &&
|
|
83
|
+
documentURL === rules.errorPage.url &&
|
|
84
|
+
unreachableURL === text &&
|
|
85
|
+
!navigating
|
|
86
|
+
)
|
|
87
|
+
return previous;
|
|
66
88
|
if (inheriting(text)) return previous || opener === 'protected' || opener === 'unknown';
|
|
67
89
|
return url(text);
|
|
68
90
|
};
|
|
@@ -227,6 +227,16 @@
|
|
|
227
227
|
"uniqueItems": true,
|
|
228
228
|
"items": { "type": "string", "pattern": "^about:[a-z]+$" }
|
|
229
229
|
},
|
|
230
|
+
"errorPage": {
|
|
231
|
+
"type": "object",
|
|
232
|
+
"additionalProperties": false,
|
|
233
|
+
"required": ["url", "target", "redact"],
|
|
234
|
+
"properties": {
|
|
235
|
+
"url": { "const": "chrome-error://chromewebdata/" },
|
|
236
|
+
"target": { "const": "previous" },
|
|
237
|
+
"redact": { "const": "origin" }
|
|
238
|
+
}
|
|
239
|
+
},
|
|
230
240
|
"unparseable": { "const": "protected" },
|
|
231
241
|
"unparseableRefusal": {
|
|
232
242
|
"const": "slicc: refused: slicc can't parse this URL, so it could be a protected page"
|
|
@@ -427,6 +427,16 @@
|
|
|
427
427
|
},
|
|
428
428
|
"step": {
|
|
429
429
|
"oneOf": [
|
|
430
|
+
{
|
|
431
|
+
"type": "object",
|
|
432
|
+
"additionalProperties": false,
|
|
433
|
+
"required": ["errorPage", "as"],
|
|
434
|
+
"properties": {
|
|
435
|
+
"errorPage": { "enum": ["recover", "restore", "sticky"] },
|
|
436
|
+
"requires": { "$ref": "#/$defs/requires" },
|
|
437
|
+
"as": { "$ref": "#/$defs/name" }
|
|
438
|
+
}
|
|
439
|
+
},
|
|
430
440
|
{
|
|
431
441
|
"type": "object",
|
|
432
442
|
"additionalProperties": false,
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
],
|
|
17
17
|
"properties": {
|
|
18
18
|
"schema": { "const": 1 },
|
|
19
|
+
"errorPage": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"required": ["url", "target", "redact"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"url": { "const": "chrome-error://chromewebdata/" },
|
|
25
|
+
"target": { "const": "previous" },
|
|
26
|
+
"redact": { "const": "origin" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
19
29
|
"unparseable": { "const": "protected" },
|
|
20
30
|
"unparseableRefusal": {
|
|
21
31
|
"const": "slicc: refused: slicc can't parse this URL, so it could be a protected page"
|
|
@@ -43,6 +53,9 @@
|
|
|
43
53
|
"url": { "type": "string" },
|
|
44
54
|
"type": { "type": "string" },
|
|
45
55
|
"previous": { "type": "boolean" },
|
|
56
|
+
"documentURL": { "type": "string" },
|
|
57
|
+
"unreachableURL": { "type": "string" },
|
|
58
|
+
"navigating": { "type": "boolean" },
|
|
46
59
|
"opener": { "enum": ["protected", "unprotected", "unknown"] },
|
|
47
60
|
"protected": { "type": "boolean" }
|
|
48
61
|
}
|