@ai-ecoverse/slicc-browsers 1.2.0 → 1.4.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 CHANGED
@@ -4,6 +4,8 @@ The browser catalog, JSON schemas and conformance corpus shared by the two progr
4
4
  - `slicc browsers` and `slicc launch` in [slicc-cli](https://github.com/ai-ecoverse/slicc-cli) (Go, `CGO_ENABLED=0`);
5
5
  - `playwright-cli list` and `open` in [slicc-cdp](https://github.com/ai-ecoverse/slicc-cdp) (Rust, also built for `wasm32-wasip1`).
6
6
 
7
+ It also holds the [protected origins](#protected-origins): the origins browser control must never reach, which slicc-agent checks too.
8
+
7
9
  They can't share code, so they share data. Three packages wrap the same files, at one version:
8
10
 
9
11
  | package | what it gives you |
@@ -20,6 +22,8 @@ They can't share code, so they share data. Three packages wrap the same files, a
20
22
  | `schema/corpus.schema.json` | JSON Schema for `corpus.json` |
21
23
  | `corpus/<case>/case.json`, `corpus/<case>/expected.json` | the conformance cases |
22
24
  | `corpus.json` | every case in one file, generated from `corpus/` by `npm run corpus` |
25
+ | `protected.json`, `schema/protected.schema.json` | the protected-origin test vectors and their schema |
26
+ | `protected.mjs` | the reference protected-origin matcher, in JavaScript (npm only) |
23
27
 
24
28
  ## Go
25
29
 
@@ -32,7 +36,8 @@ b, ok := c.Browser("chrome")
32
36
 
33
37
  - `Default() *Catalog` parses the embedded `catalog.json` once and panics if it doesn't parse (CI makes sure it does). `Load() (*Catalog, error)` is the same without the panic. Treat the result as read-only. `Parse([]byte)` parses another copy and rejects a `schema` other than 1.
34
38
  - `(*Catalog).Browser(id)` and `(*Catalog).Priority(id)`, the index in `browsers` (its length for an unknown id).
35
- - `CatalogJSON()`, `CatalogSchemaJSON()`, `BrowsersSchemaJSON()` and `CorpusJSON()` return copies of the embedded files.
39
+ - `CatalogJSON()`, `CatalogSchemaJSON()`, `BrowsersSchemaJSON()`, `CorpusJSON()`, `ProtectedJSON()` and `ProtectedSchemaJSON()` return copies of the embedded files.
40
+ - `(*Catalog).ProtectedOrigins` is `catalog.json`'s `protectedOrigins`. `Protected() (*ProtectedVectors, error)` parses `protected.json` (`Origins`, `URLs`, `Targets`, `CookieDomains`, `StorageKeys`; a target's `Opener` is `""`, `OpenerProtected`, `OpenerUnprotected` or `OpenerUnknown`).
36
41
  - `Corpus() ([]Case, error)` returns the cases. See [Running the corpus](#running-the-corpus) for the helpers.
37
42
 
38
43
  ## Rust
@@ -45,15 +50,17 @@ slicc-browsers = "1"
45
50
  slicc-browsers = { version = "1", features = ["corpus"] }
46
51
  ```
47
52
 
48
- - Always: `CATALOG`, `CATALOG_SCHEMA`, `BROWSERS_SCHEMA` and `CORPUS_SCHEMA`, each the file's text via `include_str!`, and `SCHEMA` (1). No dependencies.
49
- - Feature `serde`: `Catalog` and the other types, `Catalog::parse(&str)`, `catalog()` (parses `CATALOG`), `Catalog::browser` and `Catalog::priority`. It pulls in `serde` and `serde_json`.
50
- - Feature `corpus` (implies `serde`): `CORPUS`, the text of `corpus.json`, and the module `corpus` with `corpus()`, `Case`, `Layout` and the helpers below. Use it from `[dev-dependencies]` only; with resolver 2 it stays out of normal builds.
53
+ - Always: `CATALOG`, `CATALOG_SCHEMA`, `BROWSERS_SCHEMA`, `CORPUS_SCHEMA` and `PROTECTED_SCHEMA`, each the file's text via `include_str!`, and `SCHEMA` (1). No dependencies.
54
+ - Feature `serde`: `Catalog` and the other types (`protected_origins` is a `ProtectedOrigins`), `Catalog::parse(&str)`, `catalog()` (parses `CATALOG`), `Catalog::browser` and `Catalog::priority`. It pulls in `serde` and `serde_json`.
55
+ - Feature `corpus` (implies `serde`): `CORPUS` and `PROTECTED`, the text of `corpus.json` and `protected.json`; the module `corpus` with `corpus()`, `Case`, `Layout` and the helpers below; and the module `protected` with `vectors()`, `Vectors` and the `OPENER_*` constants. Use it from `[dev-dependencies]` only; with resolver 2 it stays out of normal builds.
51
56
 
52
57
  Every feature builds for `wasm32-wasip1`.
53
58
 
54
59
  ## npm
55
60
 
56
- `import catalog from '@ai-ecoverse/slicc-browsers' with { type: 'json' }`. The exports are `.` (`catalog.json`), `./catalog.json`, `./corpus.json`, `./schema/catalog.schema.json`, `./schema/browsers.schema.json` and `./schema/corpus.schema.json`.
61
+ `import catalog from '@ai-ecoverse/slicc-browsers' with { type: 'json' }`. The exports are `.` (`catalog.json`), `./catalog.json`, `./corpus.json`, `./protected.json`, `./protected.mjs`, `./schema/catalog.schema.json`, `./schema/browsers.schema.json`, `./schema/corpus.schema.json` and `./schema/protected.schema.json`.
62
+
63
+ `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) }`, each a boolean. It needs only the WHATWG `URL`, so it runs in Node and in a browser.
57
64
 
58
65
  ## `catalog.json`
59
66
 
@@ -82,6 +89,7 @@ Every feature builds for `wasm32-wasip1`.
82
89
  - `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);
83
90
  - `cleanExit`: values that mark the last exit clean;
84
91
  - `form` says where a key comes from: `six` (SLICC six's keys) or `chrome-154` (what Chrome 154 reads).
92
+ - `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.
85
93
  - `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)):
86
94
  - `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.
87
95
  - `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`.
@@ -89,6 +97,32 @@ Every feature builds for `wasm32-wasip1`.
89
97
  - `launch`: `chromiumArgs` (with `{profile}` for the profile dir), then the seven flags, then `headless` with `--headless`. Electron apps get `electron.portFlag` with the first free port from `electron.minPort` up that isn't in `electron.reservedPorts`.
90
98
  - `platforms`: the config dir per OS, the Windows roots, and the Windows registry keys. A location is the environment variable `env` when it's set (on Linux only an absolute path counts), else `absolute`, else `home` under the home dir. `registry`: under each of `hives`, every subkey of each `startMenuInternet` key, with its `openCommand` default value, and the default value of `appPaths\<name>` for each of `appPathNames`.
91
99
 
100
+ ## Protected origins
101
+
102
+ Browser control must never navigate to, open or attach to a target on seven's own origin, or read or write its cookies or storage: not slicc-cli's `cdp` relay, not slicc-cdp's `playwright-cli`, not slicc-agent. `protectedOrigins` in `catalog.json` says what is protected, and every implementation runs the vectors in `protected.json`.
103
+
104
+ **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:
105
+ 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>`.
106
+ 2. A scheme in `anySchemes` is protected whatever follows: a DevTools front end (`devtools://`) can be inspecting seven.
107
+ 3. 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.
108
+ 4. 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.
109
+
110
+ This is wider than the rule for finding seven (`seven.originHostPattern`, which leaves out `www`). 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`.
111
+
112
+ **The vault.** `vault.sliccy.ai` is in `hosts` by name. `sliccy.ai` already covers it, but it 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.
113
+
114
+ **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.
115
+
116
+ 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).
117
+
118
+ **Cookies.** A cookie domain, lower-cased and without a leading or trailing dot, is protected when it is one of `domains` or under one, or when it is one of `hosts`, the host of a protected domain or an extra origin, or a parent of one: a cookie on `.example.test` reaches `https://seven.example.test`.
119
+
120
+ **Storage keys** (`Storage`, `IndexedDB`, `CacheStorage` and `FileSystem` in CDP): a key is protected when its origin (up to the first `^`) or its top-level site (a part after `^0`) is a protected URL.
121
+
122
+ **Refusals** carry the text in `refusal`, `slicc: refused: target is on seven's origin`.
123
+
124
+ **`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.
125
+
92
126
  ## The process source
93
127
 
94
128
  The process scan finds browsers started with a debugging port by something else:
@@ -198,7 +232,7 @@ for case in corpus().cases {
198
232
 
199
233
  Go 1.26, Rust stable with `wasm32-wasip1`, Node ≥ 24.
200
234
  - `npm run corpus` regenerates `corpus.json` from `corpus/`; the pre-commit hook and CI fail when it's stale.
201
- - `npm run validate` checks `catalog.json` and `corpus.json` against the schemas, plus what a schema can't say: unique ids, bundle ids and names, known Windows roots, rising tiers, compiling patterns, known tokens, tier order, `sevenOpen`, `six` and `auto` in each `expected.json`, and no port from 9222 to 9229.
235
+ - `npm run validate` checks `catalog.json`, `corpus.json` and `protected.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`, `six` and `auto` in each `expected.json`, and no port from 9222 to 9229.
202
236
  - `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.
203
237
  - `npm test`: `validate`, the Go tests in `test/integration/`, and `cargo test --all-features`.
204
238
  - `npm run build`: the Go module, the crate (also for `wasm32-wasip1`), and `npm pack --dry-run`.
package/catalog.json CHANGED
@@ -701,6 +701,15 @@
701
701
  }
702
702
  ]
703
703
  },
704
+ "protectedOrigins": {
705
+ "domains": ["sliccy.ai"],
706
+ "hosts": ["vault.sliccy.ai"],
707
+ "schemes": ["https", "http"],
708
+ "innerSchemes": ["blob", "filesystem", "view-source"],
709
+ "anySchemes": ["devtools"],
710
+ "inheriting": ["about:blank", "about:srcdoc"],
711
+ "refusal": "slicc: refused: target is on seven's origin"
712
+ },
704
713
  "profiles": {
705
714
  "root": ["slicc", "seven-profiles"],
706
715
  "snapRoot": ["slicc", "seven-profiles"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-browsers",
3
- "version": "1.2.0",
3
+ "version": "1.4.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",
@@ -25,14 +25,19 @@
25
25
  ".": "./catalog.json",
26
26
  "./catalog.json": "./catalog.json",
27
27
  "./corpus.json": "./corpus.json",
28
+ "./protected.json": "./protected.json",
29
+ "./protected.mjs": "./protected.mjs",
28
30
  "./schema/catalog.schema.json": "./schema/catalog.schema.json",
29
31
  "./schema/browsers.schema.json": "./schema/browsers.schema.json",
30
32
  "./schema/corpus.schema.json": "./schema/corpus.schema.json",
33
+ "./schema/protected.schema.json": "./schema/protected.schema.json",
31
34
  "./package.json": "./package.json"
32
35
  },
33
36
  "files": [
34
37
  "catalog.json",
35
38
  "corpus.json",
39
+ "protected.json",
40
+ "protected.mjs",
36
41
  "schema"
37
42
  ],
38
43
  "scripts": {
@@ -49,11 +54,11 @@
49
54
  "release": "semantic-release"
50
55
  },
51
56
  "devDependencies": {
52
- "@ai-ecoverse/slicc-shared-web": "1.12.3",
57
+ "@ai-ecoverse/slicc-shared-web": "1.13.0",
53
58
  "@biomejs/biome": "2.5.15",
54
59
  "ajv": "8.20.0",
55
60
  "ajv-formats": "3.0.1",
56
- "lefthook": "2.1.16",
61
+ "lefthook": "2.1.17",
57
62
  "semantic-release": "25.0.9"
58
63
  }
59
64
  }
package/protected.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "schema": 1,
3
+ "origins": ["http://127.0.0.1:5710", "https://seven.example.test"],
4
+ "urls": [
5
+ { "url": "https://seven.sliccy.ai/", "protected": true },
6
+ { "url": "https://seven.sliccy.ai", "protected": true },
7
+ { "url": "https://seven.sliccy.ai/index.html?x=1#y", "protected": true },
8
+ { "url": "https://SEVEN.SLICCY.AI/", "protected": true },
9
+ { "url": "https://seven.sliccy.ai:443/", "protected": true },
10
+ { "url": "https://seven.sliccy.ai:8443/", "protected": true },
11
+ { "url": "http://seven.sliccy.ai/", "protected": true },
12
+ { "url": "https://main--seven.sliccy.ai/", "protected": true },
13
+ { "url": "https://www.sliccy.ai/", "protected": true },
14
+ { "url": "https://vault.sliccy.ai/", "protected": true },
15
+ { "url": "https://VAULT.sliccy.ai:443/unlock?x=1", "protected": true },
16
+ { "url": "http://vault.sliccy.ai./", "protected": true },
17
+ { "url": "blob:https://vault.sliccy.ai/0b9e0f6c", "protected": true },
18
+ { "url": "https://vault.sliccy.ai.example/", "protected": false },
19
+ { "url": "https://vault-sliccy.ai/", "protected": false },
20
+ { "url": "https://sliccy.ai/", "protected": true },
21
+ { "url": "https://a.b.sliccy.ai/", "protected": true },
22
+ { "url": "https://seven.sliccy.ai./", "protected": true },
23
+ { "url": "https://user:secret@seven.sliccy.ai/", "protected": true },
24
+ { "url": "https:\\\\seven.sliccy.ai\\x", "protected": true },
25
+ { "url": " https://seven.sliccy.ai/ ", "protected": true },
26
+ { "url": "ht\ttps://seven.sli\nccy.ai/", "protected": true },
27
+ { "url": "https:seven.sliccy.ai", "protected": true },
28
+ { "url": "https:///seven.sliccy.ai/", "protected": true },
29
+ { "url": "https://seven%2Esliccy%2Eai/", "protected": true },
30
+ { "url": "https://seven\u3002sliccy\u3002ai/", "protected": true },
31
+ { "url": "https://\uff53\uff45\uff56\uff45\uff4e.sliccy.ai/", "protected": true },
32
+ { "url": "https://se\u00adven.sliccy.ai/", "protected": true },
33
+ {
34
+ "url": "blob:https://seven.sliccy.ai/0b9e0f6c-1f0e-4c1a-9a43-5d3f6e2c7a10",
35
+ "protected": true
36
+ },
37
+ { "url": "filesystem:https://seven.sliccy.ai/temporary/notes.txt", "protected": true },
38
+ { "url": "view-source:https://seven.sliccy.ai/", "protected": true },
39
+ { "url": "view-source:blob:https://seven.sliccy.ai/0b9e0f6c", "protected": true },
40
+ {
41
+ "url": "devtools://devtools/bundled/inspector.html?ws=127.0.0.1:5711/devtools/page/AB12",
42
+ "protected": true
43
+ },
44
+ { "url": "http://127.0.0.1:5710/", "protected": true },
45
+ { "url": "http://127.0.0.1:5710", "protected": true },
46
+ { "url": "http://2130706433:5710/", "protected": true },
47
+ { "url": "http://0x7f.1:5710/", "protected": true },
48
+ { "url": "blob:http://127.0.0.1:5710/0b9e0f6c", "protected": true },
49
+ { "url": "https://seven.example.test/", "protected": true },
50
+ { "url": "https://SEVEN.example.test:443/x", "protected": true },
51
+ { "url": "https://seven.example.test./", "protected": true },
52
+ { "url": "http://localhost:5710/", "protected": false },
53
+ { "url": "http://127.0.0.1:5711/", "protected": false },
54
+ { "url": "https://127.0.0.1:5710/", "protected": false },
55
+ { "url": "http://127.0.0.1/", "protected": false },
56
+ { "url": "http://seven.example.test/", "protected": false },
57
+ { "url": "https://seven.example.test:8443/", "protected": false },
58
+ { "url": "https://x.seven.example.test/", "protected": false },
59
+ { "url": "https://example.test/", "protected": false },
60
+ { "url": "https://sliccy.ai.example.com/", "protected": false },
61
+ { "url": "https://notsliccy.ai/", "protected": false },
62
+ { "url": "https://seven.sliccy.ai.example/", "protected": false },
63
+ { "url": "https://sliccy.aix/", "protected": false },
64
+ { "url": "https://sliccy-ai.example/", "protected": false },
65
+ { "url": "https://example.com/?next=https://seven.sliccy.ai/", "protected": false },
66
+ { "url": "https://example.com/#https://seven.sliccy.ai/", "protected": false },
67
+ { "url": "https://seven.sliccy.ai@example.com/", "protected": false },
68
+ { "url": "wss://seven.sliccy.ai/", "protected": false },
69
+ { "url": "ftp://seven.sliccy.ai/", "protected": false },
70
+ { "url": "http://5173.kernel.localhost/", "protected": false },
71
+ { "url": "http://seven.sliccy.ai.kernel.localhost/", "protected": false },
72
+ { "url": "https://example.com/", "protected": false },
73
+ { "url": "about:blank", "protected": false },
74
+ { "url": "about:srcdoc", "protected": false },
75
+ { "url": "data:text/html,<a href=https://seven.sliccy.ai/>x</a>", "protected": false },
76
+ { "url": "blob:null/0b9e0f6c", "protected": false },
77
+ { "url": "blob:https://example.com/0b9e0f6c", "protected": false },
78
+ { "url": "filesystem:https://example.com/temporary/x", "protected": false },
79
+ { "url": "view-source:https://example.com/", "protected": false },
80
+ { "url": "javascript:location='https://seven.sliccy.ai/'", "protected": false },
81
+ { "url": "chrome://version/", "protected": false },
82
+ { "url": "chrome-extension://abcdefghijklmnopabcdefghijklmnop/page.html", "protected": false },
83
+ { "url": "file:///tmp/seven.sliccy.ai/index.html", "protected": false },
84
+ { "url": "", "protected": false },
85
+ { "url": "https://", "protected": false },
86
+ { "url": "seven.sliccy.ai", "protected": false },
87
+ { "url": "not a url", "protected": false }
88
+ ],
89
+ "targets": [
90
+ { "url": "https://seven.sliccy.ai/", "protected": true },
91
+ { "url": "https://vault.sliccy.ai/", "protected": true },
92
+ { "url": "about:blank", "opener": "protected", "protected": true },
93
+ { "url": "https://example.com/", "protected": false },
94
+ { "url": "https://example.com/", "previous": true, "protected": false },
95
+ { "url": "https://example.com/", "opener": "protected", "protected": false },
96
+ { "url": "about:blank", "protected": false },
97
+ { "url": "about:blank", "opener": "unprotected", "protected": false },
98
+ { "url": "about:blank", "opener": "protected", "protected": true },
99
+ { "url": "about:blank", "opener": "unknown", "protected": true },
100
+ { "url": "about:blank", "previous": true, "protected": true },
101
+ { "url": "about:blank", "previous": true, "opener": "unprotected", "protected": true },
102
+ { "url": "about:blank#top", "previous": true, "protected": true },
103
+ { "url": "about:blank?x=1", "opener": "protected", "protected": true },
104
+ { "url": "about:srcdoc", "previous": true, "protected": true },
105
+ { "url": "about:srcdoc", "opener": "unprotected", "protected": false },
106
+ { "url": "", "opener": "protected", "protected": true },
107
+ { "url": "", "protected": false },
108
+ { "url": "about:version", "previous": true, "protected": false },
109
+ { "url": "data:text/html,x", "previous": true, "protected": false },
110
+ { "url": "blob:https://seven.sliccy.ai/0b9e0f6c", "protected": true },
111
+ { "url": "devtools://devtools/bundled/devtools_app.html", "protected": true },
112
+ { "url": "http://127.0.0.1:5710/index.html", "protected": true },
113
+ { "url": "http://127.0.0.1:5711/index.html", "previous": true, "protected": false }
114
+ ],
115
+ "cookieDomains": [
116
+ { "domain": "sliccy.ai", "protected": true },
117
+ { "domain": ".sliccy.ai", "protected": true },
118
+ { "domain": "seven.sliccy.ai", "protected": true },
119
+ { "domain": "vault.sliccy.ai", "protected": true },
120
+ { "domain": ".www.sliccy.ai", "protected": true },
121
+ { "domain": "SLICCY.AI", "protected": true },
122
+ { "domain": "sliccy.ai.", "protected": true },
123
+ { "domain": "a.b.sliccy.ai", "protected": true },
124
+ { "domain": "127.0.0.1", "protected": true },
125
+ { "domain": "seven.example.test", "protected": true },
126
+ { "domain": ".example.test", "protected": true },
127
+ { "domain": "x.seven.example.test", "protected": false },
128
+ { "domain": "other.example.test", "protected": false },
129
+ { "domain": "notsliccy.ai", "protected": false },
130
+ { "domain": "sliccy.ai.example", "protected": false },
131
+ { "domain": "localhost", "protected": false },
132
+ { "domain": "5173.kernel.localhost", "protected": false },
133
+ { "domain": "example.com", "protected": false },
134
+ { "domain": "", "protected": false },
135
+ { "domain": ".", "protected": false }
136
+ ],
137
+ "storageKeys": [
138
+ { "key": "https://seven.sliccy.ai/", "protected": true },
139
+ { "key": "https://sliccy.ai/", "protected": true },
140
+ { "key": "https://vault.sliccy.ai/", "protected": true },
141
+ { "key": "http://127.0.0.1:5710/", "protected": true },
142
+ { "key": "https://widget.example/^0https://sliccy.ai", "protected": true },
143
+ { "key": "https://seven.sliccy.ai/^31", "protected": true },
144
+ { "key": "https://seven.sliccy.ai/^112345^267890", "protected": true },
145
+ { "key": "https://example.com/", "protected": false },
146
+ { "key": "https://example.com/^0https://example.org", "protected": false },
147
+ { "key": "https://example.com/^112345^2https://sliccy.ai", "protected": false },
148
+ { "key": "http://127.0.0.1:5711/", "protected": false },
149
+ { "key": "", "protected": false }
150
+ ]
151
+ }
package/protected.mjs ADDED
@@ -0,0 +1,78 @@
1
+ const parse = (text) => {
2
+ try {
3
+ return new URL(text);
4
+ } catch {
5
+ return null;
6
+ }
7
+ };
8
+
9
+ const scheme = (u) => u.protocol.slice(0, -1);
10
+
11
+ const hostOf = (u) => {
12
+ const h = u.hostname.toLowerCase();
13
+ return h.endsWith('.') ? h.slice(0, -1) : h;
14
+ };
15
+
16
+ const originOf = (u) => {
17
+ if (!u?.host) return null;
18
+ return `${scheme(u)}://${hostOf(u)}${u.port ? `:${u.port}` : ''}`;
19
+ };
20
+
21
+ const under = (host, domain) => host === domain || host.endsWith(`.${domain}`);
22
+
23
+ export function protectedOrigins(rules, extraOrigins = []) {
24
+ const extra = extraOrigins.map((o) => originOf(parse(o))).filter(Boolean);
25
+ const extraHosts = extraOrigins
26
+ .map((o) => parse(o))
27
+ .filter((u) => u?.host)
28
+ .map(hostOf);
29
+
30
+ const unwrap = (u) => {
31
+ for (let i = 0; u && rules.innerSchemes.includes(scheme(u)) && i < 8; i += 1) {
32
+ u = parse(u.href.slice(u.protocol.length));
33
+ }
34
+ return u;
35
+ };
36
+
37
+ const url = (text) => {
38
+ const u = unwrap(parse(text));
39
+ if (!u) return false;
40
+ const s = scheme(u);
41
+ if (rules.anySchemes.includes(s)) return true;
42
+ if (rules.schemes.includes(s) && rules.hosts.includes(hostOf(u))) return true;
43
+ if (rules.schemes.includes(s) && rules.domains.some((d) => under(hostOf(u), d))) return true;
44
+ const o = originOf(u);
45
+ return o !== null && extra.includes(o);
46
+ };
47
+
48
+ const inheriting = (text) => {
49
+ if (text === '') return true;
50
+ const u = parse(text);
51
+ return (
52
+ u !== null &&
53
+ scheme(u) === 'about' &&
54
+ rules.inheriting.includes(`about:${u.pathname.toLowerCase()}`)
55
+ );
56
+ };
57
+
58
+ const target = ({ url: text, previous = false, opener }) => {
59
+ if (inheriting(text)) return previous || opener === 'protected' || opener === 'unknown';
60
+ return url(text);
61
+ };
62
+
63
+ const cookieDomain = (domain) => {
64
+ let d = domain.toLowerCase();
65
+ if (d.startsWith('.')) d = d.slice(1);
66
+ if (d.endsWith('.')) d = d.slice(0, -1);
67
+ if (d === '') return false;
68
+ if (rules.domains.some((x) => under(d, x))) return true;
69
+ return [...rules.domains, ...rules.hosts, ...extraHosts].some((h) => under(h, d));
70
+ };
71
+
72
+ const storageKey = (key) => {
73
+ const [first, ...rest] = key.split('^');
74
+ return [first, ...rest.filter((p) => p.startsWith('0')).map((p) => p.slice(1))].some(url);
75
+ };
76
+
77
+ return { url, target, cookieDomain, storageKey };
78
+ }
@@ -12,6 +12,7 @@
12
12
  "rank",
13
13
  "discovery",
14
14
  "seven",
15
+ "protectedOrigins",
15
16
  "profiles",
16
17
  "launch",
17
18
  "platforms"
@@ -169,6 +170,47 @@
169
170
  "cleanExit": { "type": "array", "items": { "$ref": "#/$defs/pref" } }
170
171
  }
171
172
  },
173
+ "protectedOrigins": {
174
+ "type": "object",
175
+ "additionalProperties": false,
176
+ "required": [
177
+ "domains",
178
+ "hosts",
179
+ "schemes",
180
+ "innerSchemes",
181
+ "anySchemes",
182
+ "inheriting",
183
+ "refusal"
184
+ ],
185
+ "properties": {
186
+ "domains": {
187
+ "type": "array",
188
+ "minItems": 1,
189
+ "uniqueItems": true,
190
+ "items": {
191
+ "type": "string",
192
+ "pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$"
193
+ }
194
+ },
195
+ "hosts": {
196
+ "type": "array",
197
+ "uniqueItems": true,
198
+ "items": {
199
+ "type": "string",
200
+ "pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$"
201
+ }
202
+ },
203
+ "schemes": { "$ref": "#/$defs/schemes", "type": "array", "minItems": 1 },
204
+ "innerSchemes": { "$ref": "#/$defs/schemes" },
205
+ "anySchemes": { "$ref": "#/$defs/schemes" },
206
+ "inheriting": {
207
+ "type": "array",
208
+ "uniqueItems": true,
209
+ "items": { "type": "string", "pattern": "^about:[a-z]+$" }
210
+ },
211
+ "refusal": { "$ref": "#/$defs/name" }
212
+ }
213
+ },
172
214
  "profiles": {
173
215
  "type": "object",
174
216
  "additionalProperties": false,
@@ -267,6 +309,11 @@
267
309
  "$defs": {
268
310
  "name": { "type": "string", "minLength": 1 },
269
311
  "names": { "type": "array", "items": { "$ref": "#/$defs/name" } },
312
+ "schemes": {
313
+ "type": "array",
314
+ "uniqueItems": true,
315
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9+.-]*$" }
316
+ },
270
317
  "id": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
271
318
  "segments": {
272
319
  "description": "A relative path, one segment per item.",
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/ai-ecoverse/slicc-browsers/schema/protected.schema.json",
4
+ "title": "slicc-browsers protected-origin test vectors, schema 1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema", "origins", "urls", "targets", "cookieDomains", "storageKeys"],
8
+ "properties": {
9
+ "schema": { "const": 1 },
10
+ "origins": { "type": "array", "items": { "type": "string", "minLength": 1 } },
11
+ "urls": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["url", "protected"],
17
+ "properties": {
18
+ "url": { "type": "string" },
19
+ "protected": { "type": "boolean" }
20
+ }
21
+ }
22
+ },
23
+ "targets": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["url", "protected"],
29
+ "properties": {
30
+ "url": { "type": "string" },
31
+ "previous": { "type": "boolean" },
32
+ "opener": { "enum": ["protected", "unprotected", "unknown"] },
33
+ "protected": { "type": "boolean" }
34
+ }
35
+ }
36
+ },
37
+ "cookieDomains": {
38
+ "type": "array",
39
+ "items": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["domain", "protected"],
43
+ "properties": {
44
+ "domain": { "type": "string" },
45
+ "protected": { "type": "boolean" }
46
+ }
47
+ }
48
+ },
49
+ "storageKeys": {
50
+ "type": "array",
51
+ "items": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["key", "protected"],
55
+ "properties": {
56
+ "key": { "type": "string" },
57
+ "protected": { "type": "boolean" }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }