@aglyn/aglyn 1.0.0-beta.211 → 1.0.0-beta.213
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/package.json +11 -11
- package/src/lib/app-utils/docs-index.generated.js +14 -7
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.d.ts +3 -2
- package/src/lib/app-utils/plugin-bundle-checks.js +242 -3
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-abi.d.ts +61 -0
- package/src/lib/app-utils/plugin-host-abi.js +68 -0
- package/src/lib/app-utils/plugin-host-abi.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.d.ts +9 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +143 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -0
- package/src/lib/app-utils/search-engine-verification.d.ts +98 -0
- package/src/lib/app-utils/search-engine-verification.js +140 -0
- package/src/lib/app-utils/search-engine-verification.js.map +1 -0
- package/src/lib/app-utils/site-entity-json-ld.d.ts +9 -1
- package/src/lib/app-utils/site-entity-json-ld.js +25 -7
- package/src/lib/app-utils/site-entity-json-ld.js.map +1 -1
- package/src/lib/app-utils/variables.d.ts +18 -19
- package/src/lib/app-utils/variables.js +24 -23
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/webhook-delivery.js +3 -1
- package/src/lib/app-utils/webhook-delivery.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +24 -0
- package/src/lib/foundation/definitions/platform.types.js +2 -1
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/enabled-plugins.d.ts +6 -0
- package/src/lib/plugin-manager/enabled-plugins.js +5 -0
- package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +6 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +8 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contributions.d.ts +59 -0
- package/src/lib/plugin-manager/plugin-contributions.js +113 -0
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.d.ts +8 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +101 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -0
- package/src/lib/plugin-manager/realm-plugins.d.ts +3 -2
- package/src/lib/plugin-manager/realm-plugins.js +8 -3
- package/src/lib/plugin-manager/realm-plugins.js.map +1 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { PluginContributions } from '../plugin-manager/plugin-contributions';
|
|
18
18
|
/** The areas the verifier reports on, in the order a reviewer reads them. */
|
|
19
|
-
export type BundleCheckId = 'parse' | 'entry' | 'self-contained' | 'size' | 'code-execution' | 'globals' | 'storage' | 'dynamic-import' | 'network' | 'obfuscation' | 'contributions' | 'credentials';
|
|
19
|
+
export type BundleCheckId = 'parse' | 'entry' | 'self-contained' | 'host-abi' | 'size' | 'code-execution' | 'globals' | 'storage' | 'dynamic-import' | 'network' | 'obfuscation' | 'contributions' | 'credentials';
|
|
20
20
|
export interface BundleCheckProblem {
|
|
21
21
|
level: 'error' | 'warning';
|
|
22
22
|
message: string;
|
|
@@ -87,8 +87,9 @@ export declare const MAX_PLUGIN_BUNDLE_BYTES = 1000000;
|
|
|
87
87
|
* 6 — registrations are compared with the declared contributions (AGL-3116).
|
|
88
88
|
* 7 — inputs that collect a password, card or one-time code (AGL-3362).
|
|
89
89
|
* 8 — declared network origins that are a brand's lookalike (AGL-3365).
|
|
90
|
+
* 9 — lookups on the host ABI name only keys a host provides (AGL-3392).
|
|
90
91
|
*/
|
|
91
|
-
export declare const PLUGIN_VERIFIER_VERSION =
|
|
92
|
+
export declare const PLUGIN_VERIFIER_VERSION = 10;
|
|
92
93
|
/** A verdict as stored on a `pluginVersions` doc (AGL-962). */
|
|
93
94
|
export interface StoredBundleVerdict {
|
|
94
95
|
ok?: boolean;
|
|
@@ -38,10 +38,13 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
38
38
|
// type, autocomplete token and raw HTML (AGL-3362).
|
|
39
39
|
import { credentialForInputAttribute, credentialInHtml } from "@aglyn/shared-util-email/hosted-page-screen";
|
|
40
40
|
import { lookalikeBrandForHost } from "@aglyn/shared-util-email/outbound-phishing-screen";
|
|
41
|
+
import { PLUGIN_HOST_ABI_KEYS, PLUGIN_HOST_GLOBAL, PLUGIN_HOST_MODULE_KEYS } from "./plugin-host-abi.js";
|
|
42
|
+
import { REALM_HOST_SURFACE_NAMES } from "./realm-host-surface.generated.js";
|
|
41
43
|
const CHECK_LABELS = {
|
|
42
44
|
parse: 'Parses as an ES module',
|
|
43
45
|
entry: 'Exports an entry point',
|
|
44
46
|
'self-contained': 'Self-contained (no static imports)',
|
|
47
|
+
'host-abi': 'Reads only what the host provides',
|
|
45
48
|
size: 'Within the size limit',
|
|
46
49
|
'code-execution': 'No eval / Function constructor',
|
|
47
50
|
globals: 'No computed access on a global',
|
|
@@ -56,6 +59,7 @@ const CHECK_LABELS = {
|
|
|
56
59
|
'parse',
|
|
57
60
|
'entry',
|
|
58
61
|
'self-contained',
|
|
62
|
+
'host-abi',
|
|
59
63
|
'size',
|
|
60
64
|
'code-execution',
|
|
61
65
|
'globals',
|
|
@@ -85,7 +89,8 @@ export const MAX_PLUGIN_BUNDLE_BYTES = 1000000;
|
|
|
85
89
|
* 6 — registrations are compared with the declared contributions (AGL-3116).
|
|
86
90
|
* 7 — inputs that collect a password, card or one-time code (AGL-3362).
|
|
87
91
|
* 8 — declared network origins that are a brand's lookalike (AGL-3365).
|
|
88
|
-
|
|
92
|
+
* 9 — lookups on the host ABI name only keys a host provides (AGL-3392).
|
|
93
|
+
*/ export const PLUGIN_VERIFIER_VERSION = 10;
|
|
89
94
|
/**
|
|
90
95
|
* Whether a stored verdict may be served instead of re-running the checks.
|
|
91
96
|
*
|
|
@@ -605,6 +610,207 @@ const BASE64_BLOB = /^[A-Za-z0-9+/\r\n]+={0,2}$/;
|
|
|
605
610
|
}
|
|
606
611
|
return messages;
|
|
607
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* Walks every expression-position node with its parent, by the same rules as
|
|
615
|
+
* {@link walk}.
|
|
616
|
+
*/ function walkWithParents(root, visit) {
|
|
617
|
+
const stack = [
|
|
618
|
+
[
|
|
619
|
+
root,
|
|
620
|
+
null
|
|
621
|
+
]
|
|
622
|
+
];
|
|
623
|
+
while(stack.length){
|
|
624
|
+
const entry = stack.pop();
|
|
625
|
+
if (!entry) continue;
|
|
626
|
+
const [node, parent] = entry;
|
|
627
|
+
visit(node, parent);
|
|
628
|
+
for (const key of Object.keys(node)){
|
|
629
|
+
if (key === 'type' || key === 'start' || key === 'end') continue;
|
|
630
|
+
if (skippedChild(node, key)) continue;
|
|
631
|
+
const value = node[key];
|
|
632
|
+
const children = Array.isArray(value) ? value : [
|
|
633
|
+
value
|
|
634
|
+
];
|
|
635
|
+
for (const child of children){
|
|
636
|
+
if (child && typeof child === 'object' && typeof child.type === 'string') {
|
|
637
|
+
stack.push([
|
|
638
|
+
child,
|
|
639
|
+
node
|
|
640
|
+
]);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* What a bundle reads from the host ABI (AGL-3392), read off the bytes.
|
|
648
|
+
*
|
|
649
|
+
* The realm Rollup configs compile each host import into
|
|
650
|
+
* `const host = globalThis.__AGLYN_PLUGIN_HOST__; … host["mui"]`, and each use
|
|
651
|
+
* of an import into a member read on that: `_aglynHost_mui.Button`. So:
|
|
652
|
+
*
|
|
653
|
+
* - `read` / `unknown` / `computed` are the KEYS a bundle reads off the host
|
|
654
|
+
* object. A key outside `PLUGIN_HOST_ABI_KEYS` is undefined on every host
|
|
655
|
+
* and the bundle fails where it is used; a key chosen at runtime cannot be
|
|
656
|
+
* reviewed at all.
|
|
657
|
+
* - `imports` is what it reads from each host MODULE, by name, so each name
|
|
658
|
+
* can be checked against what the host holds. A module handed around as a
|
|
659
|
+
* value (`import * as M`, a spread, a computed read) cannot be narrowed and
|
|
660
|
+
* records `'*'`.
|
|
661
|
+
*
|
|
662
|
+
* The host is reached through `globalThis.__AGLYN_PLUGIN_HOST__`, a name bound
|
|
663
|
+
* to it, or the parameter of the exported `register(host)` — the last only
|
|
664
|
+
* inside `register` itself, since the same name elsewhere is another variable.
|
|
665
|
+
* A bad key on that parameter is a warning rather than a refusal: bundles
|
|
666
|
+
* written before this check read it freely, and refusing them would report a
|
|
667
|
+
* regression on bytes that are already installed.
|
|
668
|
+
*/ function hostAbiReads(program, constants, isGlobalRef) {
|
|
669
|
+
const parents = new Map();
|
|
670
|
+
const identifiers = new Map();
|
|
671
|
+
walkWithParents(program, (node, parent)=>{
|
|
672
|
+
parents.set(node, parent);
|
|
673
|
+
if (node.type === 'Identifier') {
|
|
674
|
+
const name = node['name'];
|
|
675
|
+
const list = identifiers.get(name);
|
|
676
|
+
if (list) list.push(node);
|
|
677
|
+
else identifiers.set(name, [
|
|
678
|
+
node
|
|
679
|
+
]);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
const within = (node, ancestor)=>{
|
|
683
|
+
for(let at = node; at; at = parents.get(at)){
|
|
684
|
+
if (at === ancestor) return true;
|
|
685
|
+
}
|
|
686
|
+
return false;
|
|
687
|
+
};
|
|
688
|
+
const isHostGlobal = (node)=>(node == null ? void 0 : node.type) === 'Identifier' && node['name'] === PLUGIN_HOST_GLOBAL || (node == null ? void 0 : node.type) === 'MemberExpression' && isGlobalRef(node['object']) && propertyName(node) === PLUGIN_HOST_GLOBAL;
|
|
689
|
+
// Names bound to the host object, each valid everywhere (`scope` null) or
|
|
690
|
+
// only inside one function (the `register(host)` parameter).
|
|
691
|
+
const hostNames = new Map();
|
|
692
|
+
walk(program, (node)=>{
|
|
693
|
+
var _node_id;
|
|
694
|
+
if (node.type === 'VariableDeclarator') {
|
|
695
|
+
const id = node['id'];
|
|
696
|
+
const init = node['init'];
|
|
697
|
+
const roots = (init == null ? void 0 : init.type) === 'LogicalExpression' ? [
|
|
698
|
+
init['left'],
|
|
699
|
+
init['right']
|
|
700
|
+
] : [
|
|
701
|
+
init
|
|
702
|
+
];
|
|
703
|
+
if ((id == null ? void 0 : id.type) === 'Identifier' && roots.some(isHostGlobal)) {
|
|
704
|
+
hostNames.set(id['name'], null);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (node.type === 'FunctionDeclaration' && ((_node_id = node['id']) == null ? void 0 : _node_id['name']) === 'register') {
|
|
708
|
+
var _node_params;
|
|
709
|
+
const param = ((_node_params = node['params']) != null ? _node_params : [])[0];
|
|
710
|
+
if ((param == null ? void 0 : param.type) === 'Identifier' && !hostNames.has(param['name'])) {
|
|
711
|
+
hostNames.set(param['name'], node);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
/** `'global'` for the host reached by name, `'param'` for `register(host)`. */ const hostRef = (node)=>{
|
|
716
|
+
if (isHostGlobal(node)) return 'global';
|
|
717
|
+
if ((node == null ? void 0 : node.type) !== 'Identifier') return null;
|
|
718
|
+
const name = node['name'];
|
|
719
|
+
if (!hostNames.has(name)) return null;
|
|
720
|
+
const scope = hostNames.get(name);
|
|
721
|
+
if (!scope) return 'global';
|
|
722
|
+
return within(node, scope) ? 'param' : null;
|
|
723
|
+
};
|
|
724
|
+
const modules = new Set(PLUGIN_HOST_MODULE_KEYS);
|
|
725
|
+
const allowed = new Set(PLUGIN_HOST_ABI_KEYS);
|
|
726
|
+
const read = new Set();
|
|
727
|
+
const unknown = new Set();
|
|
728
|
+
const unknownOnParam = new Set();
|
|
729
|
+
let computed = 0;
|
|
730
|
+
let computedOnParam = 0;
|
|
731
|
+
const names = new Map();
|
|
732
|
+
const whole = (key)=>{
|
|
733
|
+
names.set(key, '*');
|
|
734
|
+
};
|
|
735
|
+
const note = (key, name)=>{
|
|
736
|
+
if (name === null) return whole(key);
|
|
737
|
+
const current = names.get(key);
|
|
738
|
+
if (current === '*') return;
|
|
739
|
+
if (current) current.add(name);
|
|
740
|
+
else names.set(key, new Set([
|
|
741
|
+
name
|
|
742
|
+
]));
|
|
743
|
+
};
|
|
744
|
+
const seenAliases = new Set();
|
|
745
|
+
/** Records how one reference to a lazy key's module is used. */ const useOf = (key, ref)=>{
|
|
746
|
+
const parent = parents.get(ref);
|
|
747
|
+
// A presence check reads no name from the module: `if (!h.aglyn) return`
|
|
748
|
+
// is how a bundle stays inert on a host without the key, and every
|
|
749
|
+
// example plugin does it.
|
|
750
|
+
if ((parent == null ? void 0 : parent.type) === 'UnaryExpression' && (parent['operator'] === '!' || parent['operator'] === 'typeof') || ((parent == null ? void 0 : parent.type) === 'IfStatement' || (parent == null ? void 0 : parent.type) === 'ConditionalExpression') && parent['test'] === ref) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if ((parent == null ? void 0 : parent.type) === 'MemberExpression' && parent['object'] === ref) {
|
|
754
|
+
return note(key, propertyName(parent, constants));
|
|
755
|
+
}
|
|
756
|
+
if ((parent == null ? void 0 : parent.type) === 'VariableDeclarator' && parent['init'] === ref) {
|
|
757
|
+
const id = parent['id'];
|
|
758
|
+
if ((id == null ? void 0 : id.type) === 'Identifier') {
|
|
759
|
+
var _identifiers_get;
|
|
760
|
+
const alias = id['name'];
|
|
761
|
+
if (seenAliases.has(alias)) return;
|
|
762
|
+
seenAliases.add(alias);
|
|
763
|
+
for (const use of (_identifiers_get = identifiers.get(alias)) != null ? _identifiers_get : []){
|
|
764
|
+
if (use !== id) useOf(key, use);
|
|
765
|
+
}
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if ((id == null ? void 0 : id.type) === 'ObjectPattern') {
|
|
769
|
+
var _id_properties;
|
|
770
|
+
for (const property of (_id_properties = id['properties']) != null ? _id_properties : []){
|
|
771
|
+
if (property.type !== 'Property' || property['computed']) {
|
|
772
|
+
return whole(key);
|
|
773
|
+
}
|
|
774
|
+
const propertyKey = property['key'];
|
|
775
|
+
const name = (propertyKey == null ? void 0 : propertyKey.type) === 'Identifier' ? propertyKey['name'] : staticString(propertyKey);
|
|
776
|
+
note(key, name);
|
|
777
|
+
}
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
whole(key);
|
|
782
|
+
};
|
|
783
|
+
walk(program, (node)=>{
|
|
784
|
+
if (node.type !== 'MemberExpression') return;
|
|
785
|
+
const via = hostRef(node['object']);
|
|
786
|
+
if (!via) return;
|
|
787
|
+
const key = propertyName(node, constants);
|
|
788
|
+
if (key === null) {
|
|
789
|
+
if (via === 'param') computedOnParam += 1;
|
|
790
|
+
else computed += 1;
|
|
791
|
+
} else if (!allowed.has(key)) {
|
|
792
|
+
;
|
|
793
|
+
(via === 'param' ? unknownOnParam : unknown).add(key);
|
|
794
|
+
} else {
|
|
795
|
+
read.add(key);
|
|
796
|
+
if (modules.has(key)) useOf(key, node);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
const imports = {};
|
|
800
|
+
for (const [key, value] of names){
|
|
801
|
+
imports[key] = value === '*' ? '*' : [
|
|
802
|
+
...value
|
|
803
|
+
].sort();
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
read,
|
|
807
|
+
unknown,
|
|
808
|
+
unknownOnParam,
|
|
809
|
+
computed,
|
|
810
|
+
computedOnParam,
|
|
811
|
+
imports
|
|
812
|
+
};
|
|
813
|
+
}
|
|
608
814
|
function analyseBundle(source, options) {
|
|
609
815
|
var _ref;
|
|
610
816
|
const problems = [];
|
|
@@ -645,7 +851,7 @@ function analyseBundle(source, options) {
|
|
|
645
851
|
details.set('size', `${bytes.toLocaleString('en-US')} bytes of ${maxBytes.toLocaleString('en-US')}`);
|
|
646
852
|
if (bytes === 0) {
|
|
647
853
|
add('error', 'size', 'bundle is empty');
|
|
648
|
-
unknownFrom('parse', 'entry', 'self-contained', 'code-execution', 'globals', 'storage', 'dynamic-import', 'network', 'obfuscation', 'contributions', 'credentials');
|
|
854
|
+
unknownFrom('parse', 'entry', 'self-contained', 'host-abi', 'code-execution', 'globals', 'storage', 'dynamic-import', 'network', 'obfuscation', 'contributions', 'credentials');
|
|
649
855
|
return {
|
|
650
856
|
ok: false,
|
|
651
857
|
problems,
|
|
@@ -674,7 +880,7 @@ function analyseBundle(source, options) {
|
|
|
674
880
|
// Nothing else ran, and saying so is the whole point of `unknown`: a
|
|
675
881
|
// bundle nobody could read must not show nine green rows.
|
|
676
882
|
add('error', 'parse', `bundle does not parse as an ES module: ${error instanceof Error ? error.message : String(error)}`);
|
|
677
|
-
unknownFrom('entry', 'self-contained', 'code-execution', 'globals', 'storage', 'dynamic-import', 'network', 'obfuscation', 'contributions', 'credentials');
|
|
883
|
+
unknownFrom('entry', 'self-contained', 'host-abi', 'code-execution', 'globals', 'storage', 'dynamic-import', 'network', 'obfuscation', 'contributions', 'credentials');
|
|
678
884
|
return {
|
|
679
885
|
ok: false,
|
|
680
886
|
problems,
|
|
@@ -1041,6 +1247,39 @@ function analyseBundle(source, options) {
|
|
|
1041
1247
|
runtimeUrls ? `${runtimeUrls} call(s) with a URL only known at runtime` : ''
|
|
1042
1248
|
].filter(Boolean).join(' · '));
|
|
1043
1249
|
}
|
|
1250
|
+
const abi = hostAbiReads(program, constants, isGlobalRef);
|
|
1251
|
+
for (const key of abi.unknown){
|
|
1252
|
+
add('error', 'host-abi', `bundle reads "${key}" from the host, which provides only ` + `${PLUGIN_HOST_ABI_KEYS.join(', ')} — it would be undefined on every site`);
|
|
1253
|
+
}
|
|
1254
|
+
for (const key of abi.unknownOnParam){
|
|
1255
|
+
add('warning', 'host-abi', `register() reads "${key}" from its host argument, which provides only ` + `${PLUGIN_HOST_ABI_KEYS.join(', ')} — it will be undefined`);
|
|
1256
|
+
}
|
|
1257
|
+
if (abi.computedOnParam) {
|
|
1258
|
+
add('warning', 'host-abi', `register() reads its host argument by a key chosen at runtime ` + `(${abi.computedOnParam}×)`);
|
|
1259
|
+
}
|
|
1260
|
+
if (abi.computed) {
|
|
1261
|
+
add('error', 'host-abi', `bundle reads the host by a key chosen at runtime (${abi.computed}×) — ` + 'a lookup that cannot be named cannot be reviewed');
|
|
1262
|
+
}
|
|
1263
|
+
// Each name the bundle reads from a host module must be one the host holds
|
|
1264
|
+
// (AGL-3392): core's realm plugin surface, or the whole of the others.
|
|
1265
|
+
for (const key of PLUGIN_HOST_MODULE_KEYS){
|
|
1266
|
+
const read = abi.imports[key];
|
|
1267
|
+
if (!read) continue;
|
|
1268
|
+
const holds = REALM_HOST_SURFACE_NAMES[key];
|
|
1269
|
+
if (read === '*') {
|
|
1270
|
+
if (key === 'aglyn') {
|
|
1271
|
+
add('warning', 'host-abi', 'uses @aglyn/aglyn as a namespace value — only the realm plugin ' + 'surface is on the host, so any other name read through it is ' + 'undefined; import what you use by name');
|
|
1272
|
+
}
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
const missing = read.filter((name)=>!holds.has(name));
|
|
1276
|
+
if (missing.length) {
|
|
1277
|
+
add('error', 'host-abi', `reads ${missing.join(', ')} from the host's ${key}, which does not ` + `hold ${missing.length === 1 ? 'it' : 'them'}` + (key === 'aglyn' ? ' — only the realm plugin surface of @aglyn/aglyn is on the host' : ''));
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
details.set('host-abi', abi.read.size ? `reads ${[
|
|
1281
|
+
...abi.read
|
|
1282
|
+
].sort().join(', ')}` : 'reads nothing by name');
|
|
1044
1283
|
const detected = detectRegistrations(program, constants);
|
|
1045
1284
|
for (const message of credentialInputsIn(program, constants)){
|
|
1046
1285
|
add('warning', 'credentials', message);
|