@ai-ecoverse/slicc-browsers 1.14.7 → 1.14.9
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 +4 -3
- package/guard-driver.mjs +37 -9
- package/guard-runner.mjs +2 -4
- package/guard-vectors.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -282,7 +282,7 @@ For remote/page harnesses, `fixtureClient(base)` is exported by `./guard-runner`
|
|
|
282
282
|
|
|
283
283
|
**Design-dependent outcomes.** A key listed in `designDependent` may expect `"either"`, which `true` and `false` both match: a guard that refuses a setup step lets the real document load, and one that blocks before commit doesn't. `detached` is design-dependent on the rows whose guarded leg proves the block (the pre-action beacon plus an exact-URL block or the history form): its `eitherWhen` holds the same proof, so a guard that blocks without detaching passes, and one that neither blocks nor detaches still fails. The hard negatives stay required.
|
|
284
284
|
|
|
285
|
-
**Observability.** `observable.own: false` marks a vector the guard host's own origin can't show (headers, auth, redirects); a runner
|
|
285
|
+
**Observability.** `observable.own: false` marks a vector the guard host's own origin can't show (headers, auth, redirects); a runner reports it as `skip` with `not-applicable` on that target.
|
|
286
286
|
|
|
287
287
|
**Applies to.** `appliesTo` lists the triggers a vector runs with (`href`, `iframe`, `popup`, `redirect`). Chrome doesn't apply some session overrides inside a cross-site iframe, so `E6.locale`, `E6.timezone`, `E6.network-state` and `E6.blocked-urls` have no `.iframe` row.
|
|
288
288
|
|
|
@@ -309,10 +309,11 @@ It exports:
|
|
|
309
309
|
- `waitFor(check, ms, step?)`: polls `check` every `step` ms (default 25) until it holds, checking once more after the bound. A timer heartbeat every `step` runs for the whole wait, including while `check` is awaiting a reply, and any gap between beats longer than a second (or four steps, if longer) extends the bound by that gap, so a synchronous event-loop stall elsewhere in the process does not consume it. The extensions add up to at most `max(4 × ms, 30000)` ms, so a throttled page cannot stretch a wait without limit. `load`, the runner's waits and the error-page and redirect-body probes use it;
|
|
310
310
|
- `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;
|
|
311
311
|
- `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;
|
|
312
|
+
- `inapplicable(vector, target, scope)`: whether the row is `not-applicable`: the vector's `skip.target` is `target`, its `observable[target]` is `false`, or its `scope` is neither `any` nor `scope`;
|
|
312
313
|
- `knownOrigins` and `originDefaults`: the origin names a runner can miss, and the origins `plain`, `cross`, `log` and `own` fall back to when unset;
|
|
313
314
|
- `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;
|
|
314
315
|
- `owned(entries, tag)`: the fixture log entries whose raw URL contains `tag`, the row's attribution through its `n=`, `tag=` or `to=` query;
|
|
315
|
-
- `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, forged, 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 …`. `forged` lists each `{ id, target, variant, transport, why }` that is `not-run` on a vector that isn't `kind: "text"`, or `skip` without a reason the runner gives (`not-applicable
|
|
316
|
+
- `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, forged, 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 …`. `forged` lists each `{ id, target, variant, transport, why }` that is `not-run` on a vector that isn't `kind: "text"`, or `skip` without a reason the runner gives (`not-applicable` when the vector's `skip.target` is the row's target, its `observable` is `false` for that target, or its `scope` is neither `any` nor the guard, the same `inapplicable(vector, target, scope)` the runner uses; `not-applicable:no-<origin>-origin` for an origin in `knownOrigins` that the row's target, `plain`, the control's origin, the steps or a check names, directly or through `originDefaults`, where only the first branch of an `any` counts, the vector's own `skip.reason` on its target, or `not-supported:<capability>` for a capability the set requires, `browserSession` or `unguarded-observer`); `pass` needs it empty too, and `line` then ends in `forged <n>`.
|
|
316
317
|
|
|
317
318
|
**`npm run validate`** checks that:
|
|
318
319
|
- the file matches the schema, and ids are unique;
|
|
@@ -361,7 +362,7 @@ otherwise they run serially. `capabilities` is an object of booleans or an array
|
|
|
361
362
|
of supported names, and a capability that isn't declared is unsupported: a CDP
|
|
362
363
|
transport over a DevTools port declares `chromeUrlsAttach: true`, and an
|
|
363
364
|
extension on `chrome.debugger` leaves it out, so `C8.newtab` is skipped with
|
|
364
|
-
`not-supported:chromeUrlsAttach`. `capabilities.scope` selects owner-specific rows
|
|
365
|
+
`not-supported:chromeUrlsAttach`. `capabilities.scope` selects owner-specific rows; pass the same name as `guard` to `summary()`.
|
|
365
366
|
|
|
366
367
|
Map `ordinary`, `ordinary-same-site`, `seven`, `vault`, `vault-x`, `apex`, `own`
|
|
367
368
|
and `client` to origins. `chrome-newtab`, `chrome-new-tab-page`, `chrome-settings`, `chrome-passwords`, `chrome-search`, `network-error`, `chrome-extension-page` and `file-root` default to their `url`.
|
package/guard-driver.mjs
CHANGED
|
@@ -305,27 +305,55 @@ const capabilities = (value) =>
|
|
|
305
305
|
)
|
|
306
306
|
: [];
|
|
307
307
|
|
|
308
|
+
const originNames = (value) =>
|
|
309
|
+
[...q(value ?? null).matchAll(/\{([A-Za-z][A-Za-z-]*)\}/g)]
|
|
310
|
+
.map((match) => (match[1] === 'beacon' || match[1] === 'probe' ? 'log' : match[1]))
|
|
311
|
+
.flatMap((name) => [name, originDefaults[name]]);
|
|
312
|
+
|
|
313
|
+
const checkOrigins = (check) => {
|
|
314
|
+
if (!check || typeof check !== 'object') return [];
|
|
315
|
+
if (check.any) return checkOrigins(check.any[0]);
|
|
316
|
+
if (check.all || check.not) return (check.all ?? [check.not]).flatMap(checkOrigins);
|
|
317
|
+
return originNames(check);
|
|
318
|
+
};
|
|
319
|
+
|
|
308
320
|
function originReason(vector, row, reason) {
|
|
309
321
|
const origin = /^not-applicable:no-(.+)-origin$/.exec(reason)?.[1];
|
|
310
322
|
if (!knownOrigins.includes(origin)) return false;
|
|
323
|
+
const fixed = [
|
|
324
|
+
row.target,
|
|
325
|
+
'{plain}',
|
|
326
|
+
vector?.control?.origin,
|
|
327
|
+
vector?.setup,
|
|
328
|
+
vector?.trigger,
|
|
329
|
+
].map((value) => (typeof value === 'string' && !value.startsWith('{') ? `{${value}}` : value));
|
|
311
330
|
return [
|
|
312
|
-
...
|
|
313
|
-
|
|
314
|
-
.
|
|
315
|
-
|
|
331
|
+
...originNames(fixed),
|
|
332
|
+
...checkOrigins(vector?.liveCheck),
|
|
333
|
+
...Object.values(vector?.observe ?? {}).flatMap((observation) =>
|
|
334
|
+
checkOrigins(observation?.check)
|
|
335
|
+
),
|
|
336
|
+
].includes(origin);
|
|
316
337
|
}
|
|
317
338
|
|
|
318
|
-
|
|
339
|
+
export const inapplicable = (vector, target, scope) =>
|
|
340
|
+
Boolean(vector) &&
|
|
341
|
+
(vector.skip?.target === target ||
|
|
342
|
+
vector.observable?.[target] === false ||
|
|
343
|
+
(vector.scope !== 'any' && vector.scope !== scope));
|
|
344
|
+
|
|
345
|
+
function skipWhy(vector, row, known, guard) {
|
|
319
346
|
const { reason } = row;
|
|
320
347
|
if (typeof reason !== 'string' || reason === '') return 'skip without a reason';
|
|
321
|
-
if (reason === 'not-applicable'
|
|
348
|
+
if (reason === 'not-applicable' && inapplicable(vector, row.target, guard)) return null;
|
|
349
|
+
if (originReason(vector, row, reason)) return null;
|
|
322
350
|
if (vector?.skip?.target === row.target && vector.skip.reason === reason) return null;
|
|
323
351
|
if (reason.startsWith('not-supported:') && known.has(reason.slice('not-supported:'.length)))
|
|
324
352
|
return null;
|
|
325
353
|
return `skip with a reason the runner never gives: ${reason}`;
|
|
326
354
|
}
|
|
327
355
|
|
|
328
|
-
function forgeries(set, results) {
|
|
356
|
+
function forgeries(set, results, guard) {
|
|
329
357
|
const vectors = new Map(set.vectors.map((v) => [v.id, v]));
|
|
330
358
|
const known = new Set([
|
|
331
359
|
'browserSession',
|
|
@@ -338,7 +366,7 @@ function forgeries(set, results) {
|
|
|
338
366
|
const why = [];
|
|
339
367
|
if (legs.includes('not-run') && vector?.kind !== 'text')
|
|
340
368
|
why.push('not-run on a vector that is not kind text');
|
|
341
|
-
if (legs.includes('skip')) why.push(skipWhy(vector, row, known));
|
|
369
|
+
if (legs.includes('skip')) why.push(skipWhy(vector, row, known, guard));
|
|
342
370
|
const found = why.filter(Boolean);
|
|
343
371
|
if (!found.length) return [];
|
|
344
372
|
const { id, target, variant, transport } = row;
|
|
@@ -356,7 +384,7 @@ export function summary({ guard, sha, set, results }) {
|
|
|
356
384
|
.flatMap((row) => variants.map((variant) => ({ ...row, variant })))
|
|
357
385
|
.flatMap((row) => uniq('transport').map((transport) => ({ ...row, transport })))
|
|
358
386
|
.filter((r) => !has.has(q([r.id, r.target, r.variant, r.transport])));
|
|
359
|
-
const forged = forgeries(set, results);
|
|
387
|
+
const forged = forgeries(set, results, guard);
|
|
360
388
|
const pass =
|
|
361
389
|
results.length > 0 && missing.length === 0 && forged.length === 0 && results.every(passed);
|
|
362
390
|
const name = `@ai-ecoverse/slicc-browsers@${set.setVersion}`;
|
package/guard-runner.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
canary,
|
|
9
9
|
expand,
|
|
10
10
|
hop,
|
|
11
|
+
inapplicable,
|
|
11
12
|
isRefusal,
|
|
12
13
|
knownOrigins,
|
|
13
14
|
loadedAfter,
|
|
@@ -1289,10 +1290,7 @@ const needsObserver = (vector) =>
|
|
|
1289
1290
|
);
|
|
1290
1291
|
|
|
1291
1292
|
const inactive = (vector, target, capabilities) =>
|
|
1292
|
-
vector
|
|
1293
|
-
vector.observable?.[target] === false ||
|
|
1294
|
-
!supported(vector.requires, capabilities) ||
|
|
1295
|
-
(vector.scope !== 'any' && vector.scope !== capabilities.scope);
|
|
1293
|
+
inapplicable(vector, target, capabilities.scope) || !supported(vector.requires, capabilities);
|
|
1296
1294
|
|
|
1297
1295
|
const allSteps = (list) => list.flatMap((step) => [step, ...allSteps(step.children ?? [])]);
|
|
1298
1296
|
const browserContextStep = (vector, capabilities) =>
|
package/guard-vectors.json
CHANGED