@ai-ecoverse/slicc-browsers 1.21.0 → 1.22.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 +5 -4
- package/guard-driver.mjs +18 -2
- package/guard-runner.mjs +8 -1
- package/guard-vectors.json +997 -63
- package/package.json +4 -4
- package/protected-error-page.mjs +10 -3
- package/schema/guard-vectors.schema.json +30 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The browser knowledge that SLICC's tools share. SLICC drives a real Chromium bro
|
|
|
7
7
|
- which origins browser control must **never** touch, starting with seven's own origin and the vault;
|
|
8
8
|
- how to prove that a CDP guard really keeps a client away from them.
|
|
9
9
|
|
|
10
|
-
These programs can't share code, so they share data: one catalog, four sets
|
|
10
|
+
These programs can't share code, so they share data: one catalog, four vector sets and schemas, as a Go module, Rust crate and npm package at one version.
|
|
11
11
|
|
|
12
12
|
| who | uses it for |
|
|
13
13
|
| --- | --- |
|
|
@@ -223,7 +223,7 @@ await fixture.close();
|
|
|
223
223
|
You supply:
|
|
224
224
|
- **a guarded client** per row: your guard's CDP connection, with `send(method, params, sessionId)`, `waitEvent(predicate, ms)` and an `events` array;
|
|
225
225
|
- **an unguarded observer**: a direct CDP connection to the same browser, which runs the controls and checks liveness;
|
|
226
|
-
- **origins**: the fixture server reachable as seven, the vault and an ordinary site. Point the protected hostnames at it with Chrome's `--host-resolver-rules`, so
|
|
226
|
+
- **origins**: the fixture server reachable as seven, the vault and an ordinary site. Point the protected hostnames at it with Chrome's `--host-resolver-rules`, so tests never touch the real seven;
|
|
227
227
|
- **capabilities**: what your transport can do. A row that needs something you don't declare is skipped as `not-supported:<capability>`.
|
|
228
228
|
|
|
229
229
|
| capability | means the transport can |
|
|
@@ -239,6 +239,7 @@ You supply:
|
|
|
239
239
|
| `siteIsolationOff`, `inboundDelay`, `forwardAudit` | no site isolation; hold inbound; audit forwarded commands |
|
|
240
240
|
| `secureOrigin` | HTTPS origins (service workers) |
|
|
241
241
|
| `stockSetup`, `syntheticAutoAttach` | admit stock clients' setup calls; synthesize auto-attach |
|
|
242
|
+
| `closeProtected` | close a protected tab |
|
|
242
243
|
|
|
243
244
|
### Reading the result
|
|
244
245
|
|
|
@@ -248,13 +249,13 @@ Each row reports `control`, `guarded` and `live` as `pass`, `fail`, `skip`, `inc
|
|
|
248
249
|
GUARD-PASS my-guard@1a2b3c4 (vectors @ai-ecoverse/slicc-browsers@1.18.5)
|
|
249
250
|
```
|
|
250
251
|
|
|
251
|
-
The run passes only when every row passes and none is missing. A skip with a reason the runner would never give
|
|
252
|
+
The run passes only when every row passes and none is missing. A skip with a reason the runner would never give is forged; skipped and incomplete rows are counted on the line, so it can't overstate what was tested.
|
|
252
253
|
|
|
253
254
|
To find out what a vector checks, read it in `guard-vectors.json`: each one lists its steps, each observation's `means`, its `live` condition and its expected outcome. [`schema/guard-vectors.schema.json`](schema/guard-vectors.schema.json) defines the format.
|
|
254
255
|
|
|
255
256
|
## Development
|
|
256
257
|
|
|
257
|
-
You need Go 1.26, Rust stable with
|
|
258
|
+
You need Go 1.26, Rust stable with `wasm32-wasip1`, Node 24+, and Chrome or Chromium for the browser tests: they take `CHROME_PATH`, or Chrome for Testing from `npx slicc-cdp install-browser`, and run headless unless `CI` is set (`SLICC_HEADLESS=1` or `0` overrides).
|
|
258
259
|
|
|
259
260
|
| command | what it does |
|
|
260
261
|
| --- | --- |
|
package/guard-driver.mjs
CHANGED
|
@@ -197,7 +197,9 @@ export function judge(vector, side, observed) {
|
|
|
197
197
|
}
|
|
198
198
|
const skipped = Object.keys(want).filter(
|
|
199
199
|
(k) =>
|
|
200
|
-
(observed?.[k] === 'not-supported' &&
|
|
200
|
+
(observed?.[k] === 'not-supported' &&
|
|
201
|
+
((vector.observe?.[k]?.requires?.length ?? 0) > 0 ||
|
|
202
|
+
(vector.observe?.[k]?.unless?.length ?? 0) > 0)) ||
|
|
201
203
|
(vector.observe?.[k]?.dependsOn ?? []).some((d) => refused.includes(d))
|
|
202
204
|
);
|
|
203
205
|
const incomplete = Object.keys(want).filter(
|
|
@@ -310,7 +312,9 @@ const capabilities = (value) =>
|
|
|
310
312
|
? value.flatMap(capabilities)
|
|
311
313
|
: value && typeof value === 'object'
|
|
312
314
|
? Object.entries(value).flatMap(([key, item]) =>
|
|
313
|
-
key === 'requires' && Array.isArray(item)
|
|
315
|
+
(key === 'requires' || key === 'unless') && Array.isArray(item)
|
|
316
|
+
? item
|
|
317
|
+
: capabilities(item)
|
|
314
318
|
)
|
|
315
319
|
: [];
|
|
316
320
|
|
|
@@ -389,6 +393,17 @@ function skipWhy(vector, row, known, guard, declared) {
|
|
|
389
393
|
return `skip with a reason the runner never gives: ${reason}`;
|
|
390
394
|
}
|
|
391
395
|
|
|
396
|
+
function observationWhy(vector, row, declared) {
|
|
397
|
+
return (row.notSupported ?? []).flatMap((name) => {
|
|
398
|
+
const observation = vector?.observe?.[name];
|
|
399
|
+
if (!observation) return [`not-supported observation ${name} the vector lacks`];
|
|
400
|
+
const missing = (observation.requires ?? []).some((cap) => !declared.includes(cap));
|
|
401
|
+
const unless = (observation.unless ?? []).some((cap) => declared.includes(cap));
|
|
402
|
+
if (missing || unless) return [];
|
|
403
|
+
return [`not-supported:${name} while the observation should run`];
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
392
407
|
function forgeries(set, results, guard, capabilitiesList) {
|
|
393
408
|
const vectors = new Map(set.vectors.map((v) => [v.id, v]));
|
|
394
409
|
const declared = declaredCapabilities(capabilitiesList);
|
|
@@ -405,6 +420,7 @@ function forgeries(set, results, guard, capabilitiesList) {
|
|
|
405
420
|
if (legs.includes('not-run') && vector?.kind !== 'text')
|
|
406
421
|
why.push('not-run on a vector that is not kind text');
|
|
407
422
|
if (legs.includes('skip')) why.push(skipWhy(vector, row, known, guard, declared));
|
|
423
|
+
why.push(...observationWhy(vector, row, declared));
|
|
408
424
|
const found = why.filter(Boolean);
|
|
409
425
|
if (!found.length) return [];
|
|
410
426
|
const { id, target, variant, transport } = row;
|
package/guard-runner.mjs
CHANGED
|
@@ -50,6 +50,13 @@ const supported = (requires, capabilities) =>
|
|
|
50
50
|
(requires ?? []).every((c) =>
|
|
51
51
|
Array.isArray(capabilities) ? capabilities.includes(c) : capabilities[c] === true
|
|
52
52
|
);
|
|
53
|
+
const declared = (capability, capabilities) =>
|
|
54
|
+
Array.isArray(capabilities)
|
|
55
|
+
? capabilities.includes(capability)
|
|
56
|
+
: capabilities[capability] === true;
|
|
57
|
+
const gatedOut = (observation, capabilities) =>
|
|
58
|
+
!supported(observation?.requires, capabilities) ||
|
|
59
|
+
(observation?.unless ?? []).some((capability) => declared(capability, capabilities));
|
|
53
60
|
const matches = (value, predicate) => {
|
|
54
61
|
if (predicate === null || typeof predicate !== 'object') return value === predicate;
|
|
55
62
|
return Object.entries(predicate).every(([key, want]) => {
|
|
@@ -1616,7 +1623,7 @@ class Execution {
|
|
|
1616
1623
|
const observed = { ...this.refused };
|
|
1617
1624
|
const incomplete = [];
|
|
1618
1625
|
for (const [name, observation] of Object.entries(this.vector.observe ?? {})) {
|
|
1619
|
-
if (
|
|
1626
|
+
if (gatedOut(observation, this.options.capabilities)) {
|
|
1620
1627
|
observed[name] = 'not-supported';
|
|
1621
1628
|
continue;
|
|
1622
1629
|
}
|