@ai-ecoverse/slicc-browsers 1.21.1 → 1.22.1

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
@@ -183,9 +183,9 @@ It also offers `storageKey(key)` and `unparseable(text)`. A Go or Rust implement
183
183
 
184
184
  ## Guard vectors
185
185
 
186
- A guard sits between a CDP client, such as an agent's browser tool, and the browser, and keeps the client away from protected origins. `guard-vectors.json` holds more than a hundred attacks on that promise, and every guard must pass them all. They cover navigation, redirect chains, popups, frames, request interception, cookies, storage, privileged and error pages, foreign extension worlds, debugger tricks and session overrides that would leak into a protected page.
186
+ A guard sits between a CDP client and the browser and keeps the client away from protected origins. `guard-vectors.json` holds the attacks every guard must pass: navigation, redirects, popups, frames, interception, cookies, storage, privileged and error pages, foreign worlds, debugger tricks and session overrides.
187
187
 
188
- Each vector runs twice. The **control** runs it against an ordinary origin, where it must work. The **guarded** leg runs it through the guard against a protected target, where it must be refused or have no effect. A **liveness** check, made outside the guard, proves the protected target was really there. A row passes only when all three do, so a guard can't pass by breaking everything, and a test can't pass by never reaching the target.
188
+ Each vector runs twice. The **control** runs against an ordinary origin and must work. The **guarded** leg runs through the guard against a protected target and must be refused or have no effect. A **liveness** check outside the guard proves the target was really there. All three must pass.
189
189
 
190
190
  A guard may refuse every client navigation to an `about:` URL that inherits an origin, such as `about:blank`. Refusing an ordinary navigation away from a protected page still fails.
191
191
 
@@ -195,7 +195,7 @@ For stock Playwright, Puppeteer and raw CDP clients, a guard that declares `stoc
195
195
  - `Page.addScriptToEvaluateOnNewDocument` whose `source` matches `^(?:[ \t\n]|//[^\n\r\u2028\u2029]*)*$` on the whole string without the multiline flag (no code), with any `worldName` or `runImmediately`, and its removal;
196
196
  - exactly `Emulation.setFocusEmulationEnabled`, `setEmulatedMedia`, `setDeviceMetricsOverride` and `setTouchEmulationEnabled`;
197
197
  - `Page.createIsolatedWorld` on a frame the guard already classifies as ordinary: strip `grantUniveralAccess` and `contentSecurityPolicy`, return no `executionContextId` for any other frame; `Runtime.addBinding` scoped to such a world by a non-empty `executionContextName` or its `executionContextId`, with `Runtime.bindingCalled` only from those worlds;
198
- - `Target.setAutoAttach`: accept the call but narrow the filter to tab targets, hold pages under the tab like pages (same URL redaction in `targetInfo`); a transport that synthesizes it declares `syntheticAutoAttach`.
198
+ - `Target.setAutoAttach`: either narrow the filter to tab targets or hide per target; no protected-origin page, iframe or worker (dedicated, shared or service) may reach the client; pages under the tab are held like pages (same URL redaction in `targetInfo`); a transport that synthesizes it declares `syntheticAutoAttach`.
199
199
 
200
200
  The strip holds whether or not a given Chrome lets that world reach another origin. Code init scripts, main-world bindings and other overrides stay refused or harmless. Admitted display overrides and no-code init scripts must not remain in effect on a protected document — refuse that navigation, or clear them before it runs. Undeclared, they skip, never pass.
201
201
 
@@ -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 no test ever touches the real seven;
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, such as a made-up capability, is reported as forged, and skipped and incomplete rows are counted on the line, so the line can't overstate what was tested.
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 the `wasm32-wasip1` target, Node 24 or later, and Chrome or Chromium for the browser tests: they take `CHROME_PATH`, or the Chrome for Testing that `npx slicc-cdp install-browser` installs, and run it headless unless `CI` is set (`SLICC_HEADLESS=1` or `0` overrides).
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
  | --- | --- |
@@ -105,6 +105,13 @@ function route(req, url, send, html, redirects) {
105
105
  '/csp': () => document(url, html),
106
106
  '/sw.js': () =>
107
107
  send(200, "self.addEventListener('install',()=>self.skipWaiting())", 'text/javascript'),
108
+ '/worker.js': () => send(200, 'onmessage=e=>postMessage(e.data)', 'text/javascript'),
109
+ '/shared-worker.js': () =>
110
+ send(
111
+ 200,
112
+ 'onconnect=e=>{e.ports[0].onmessage=m=>e.ports[0].postMessage(m.data)}',
113
+ 'text/javascript'
114
+ ),
108
115
  };
109
116
  if (req.method === 'OPTIONS') return send(204, '');
110
117
  if (url.pathname.startsWith('/auth')) {
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' && vector.observe?.[k]?.requires?.length > 0) ||
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) ? item : capabilities(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;