@colixsystems/widget-sdk 0.272.0 → 0.273.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 -1
- package/dist/contract.cjs +11 -1
- package/dist/contract.js +11 -1
- package/dist/linter.cjs +31 -0
- package/dist/linter.js +31 -0
- package/package.json +1 -1
- package/src/contract.js +11 -1
package/README.md
CHANGED
|
@@ -86,7 +86,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
86
86
|
|
|
87
87
|
## Status
|
|
88
88
|
|
|
89
|
-
`v0.
|
|
89
|
+
`v0.273.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
90
|
+
|
|
91
|
+
### What's new in 0.273.0 (contract 1.223.0)
|
|
92
|
+
|
|
93
|
+
**A widget never mints or sets its own permission header.** The host attaches each widget's `X-Widget-Scopes` header itself, from what that widget was granted. Source that calls `/widgets/scope-token` or sets `X-Widget-Scopes` (any casing) is now a blocking `no-scope-header-mint` finding, listed in the new `CONTRACT.scopeMintPatterns`. Comments are not scanned. Declare the scopes your hooks need in `manifest.requestedScopes`, as before; a widget that does that is unaffected.
|
|
90
94
|
|
|
91
95
|
### What's new in 0.272.0 (contract 1.222.0)
|
|
92
96
|
|
package/dist/contract.cjs
CHANGED
|
@@ -4956,6 +4956,12 @@ const TRANSLATION_API_HOSTS = [
|
|
|
4956
4956
|
"lingvanex.com",
|
|
4957
4957
|
];
|
|
4958
4958
|
|
|
4959
|
+
// sc-12906: the host mints a widget's `X-Widget-Scopes` header, never the
|
|
4960
|
+
// widget. Bundles share the host's JS realm, so a widget calling the mint
|
|
4961
|
+
// itself, or forging the header, could borrow another widget's scopes. Matched
|
|
4962
|
+
// case-insensitively against code (comments are blanked before matching).
|
|
4963
|
+
const SCOPE_MINT_PATTERNS = ["/widgets/scope-token", "x-widget-scopes"];
|
|
4964
|
+
|
|
4959
4965
|
// sc-4686 — the currency a price renders in when the HOST does not supply the
|
|
4960
4966
|
// workspace's own (a host predating the workspace.currency slice, or a caller
|
|
4961
4967
|
// that forgot to wire it). A rendering fallback so a price is never blank or
|
|
@@ -6559,7 +6565,10 @@ const CONTRACT = deepFreeze({
|
|
|
6559
6565
|
// 1.222.0: stricter (sc-14479) — `self`, `top`, `parent`, `frames`,
|
|
6560
6566
|
// `opener`, `frameElement` and `.constructor` join bannedApis; `eval` and
|
|
6561
6567
|
// `Function` are refused as any reference, not only as a call.
|
|
6562
|
-
|
|
6568
|
+
// 1.223.0: stricter (sc-12906) — `scopeMintPatterns`; a widget that calls
|
|
6569
|
+
// the scope-header mint or sets `X-Widget-Scopes` is refused
|
|
6570
|
+
// (`no-scope-header-mint`).
|
|
6571
|
+
version: "1.223.0",
|
|
6563
6572
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6564
6573
|
hooks: HOOKS,
|
|
6565
6574
|
primitives: PRIMITIVES,
|
|
@@ -6604,6 +6613,7 @@ const CONTRACT = deepFreeze({
|
|
|
6604
6613
|
currencyFormats: CURRENCY_FORMATS,
|
|
6605
6614
|
currencyFormatFallback: CURRENCY_FORMAT_FALLBACK,
|
|
6606
6615
|
translationApiHosts: TRANSLATION_API_HOSTS,
|
|
6616
|
+
scopeMintPatterns: SCOPE_MINT_PATTERNS,
|
|
6607
6617
|
});
|
|
6608
6618
|
|
|
6609
6619
|
function isHookAllowed(name) {
|
package/dist/contract.js
CHANGED
|
@@ -4953,6 +4953,12 @@ const TRANSLATION_API_HOSTS = [
|
|
|
4953
4953
|
"lingvanex.com",
|
|
4954
4954
|
];
|
|
4955
4955
|
|
|
4956
|
+
// sc-12906: the host mints a widget's `X-Widget-Scopes` header, never the
|
|
4957
|
+
// widget. Bundles share the host's JS realm, so a widget calling the mint
|
|
4958
|
+
// itself, or forging the header, could borrow another widget's scopes. Matched
|
|
4959
|
+
// case-insensitively against code (comments are blanked before matching).
|
|
4960
|
+
const SCOPE_MINT_PATTERNS = ["/widgets/scope-token", "x-widget-scopes"];
|
|
4961
|
+
|
|
4956
4962
|
// sc-4686 — the currency a price renders in when the HOST does not supply the
|
|
4957
4963
|
// workspace's own (a host predating the workspace.currency slice, or a caller
|
|
4958
4964
|
// that forgot to wire it). A rendering fallback so a price is never blank or
|
|
@@ -6556,7 +6562,10 @@ const CONTRACT = deepFreeze({
|
|
|
6556
6562
|
// 1.222.0: stricter (sc-14479) — `self`, `top`, `parent`, `frames`,
|
|
6557
6563
|
// `opener`, `frameElement` and `.constructor` join bannedApis; `eval` and
|
|
6558
6564
|
// `Function` are refused as any reference, not only as a call.
|
|
6559
|
-
|
|
6565
|
+
// 1.223.0: stricter (sc-12906) — `scopeMintPatterns`; a widget that calls
|
|
6566
|
+
// the scope-header mint or sets `X-Widget-Scopes` is refused
|
|
6567
|
+
// (`no-scope-header-mint`).
|
|
6568
|
+
version: "1.223.0",
|
|
6560
6569
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6561
6570
|
hooks: HOOKS,
|
|
6562
6571
|
primitives: PRIMITIVES,
|
|
@@ -6601,6 +6610,7 @@ const CONTRACT = deepFreeze({
|
|
|
6601
6610
|
currencyFormats: CURRENCY_FORMATS,
|
|
6602
6611
|
currencyFormatFallback: CURRENCY_FORMAT_FALLBACK,
|
|
6603
6612
|
translationApiHosts: TRANSLATION_API_HOSTS,
|
|
6613
|
+
scopeMintPatterns: SCOPE_MINT_PATTERNS,
|
|
6604
6614
|
});
|
|
6605
6615
|
|
|
6606
6616
|
function isHookAllowed(name) {
|
package/dist/linter.cjs
CHANGED
|
@@ -763,6 +763,36 @@ function _translationApiRules(source) {
|
|
|
763
763
|
return findings;
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
+
// sc-12906 — no-scope-header-mint.
|
|
767
|
+
//
|
|
768
|
+
// The host mints each widget's `X-Widget-Scopes` header from the widget's own
|
|
769
|
+
// installation. A bundle runs in the host's JS realm, so one that calls the
|
|
770
|
+
// mint itself, or sets the header, could borrow another widget's scopes. The
|
|
771
|
+
// server refuses a built-in nobody placed; this catches the attempt at publish.
|
|
772
|
+
// An error with no opt-out: no correct widget touches either.
|
|
773
|
+
function _scopeMintRules(source) {
|
|
774
|
+
const findings = [];
|
|
775
|
+
const needles = CONTRACT.scopeMintPatterns || [];
|
|
776
|
+
const lines = _stripNonCode(source, { keepStrings: true }).split(/\r?\n/);
|
|
777
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
778
|
+
const lowered = lines[i].toLowerCase();
|
|
779
|
+
const needle = needles.find((n) => lowered.includes(n));
|
|
780
|
+
if (!needle) continue;
|
|
781
|
+
findings.push({
|
|
782
|
+
rule: "no-scope-header-mint",
|
|
783
|
+
severity: "error",
|
|
784
|
+
label:
|
|
785
|
+
`source references "${needle}" — the host attaches each widget's ` +
|
|
786
|
+
`permission header itself. Never call that endpoint or set that ` +
|
|
787
|
+
`header; declare the scopes your hooks need in ` +
|
|
788
|
+
`manifest.requestedScopes.`,
|
|
789
|
+
line: i + 1,
|
|
790
|
+
snippet: lines[i].trim().slice(0, 200),
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return findings;
|
|
794
|
+
}
|
|
795
|
+
|
|
766
796
|
// REQ-USERMGMT / REQ-ACL-SYS M3 — scope-required-for-user-mutation. See
|
|
767
797
|
// linter.js for the rationale comment. The two files must stay in
|
|
768
798
|
// lockstep (the contract test asserts behaviour-equivalence).
|
|
@@ -2460,6 +2490,7 @@ function lintSource(source, options) {
|
|
|
2460
2490
|
// sc-6970 — soft warning: HTML markup spliced into a content string.
|
|
2461
2491
|
findings.push(..._htmlInContentRules(source));
|
|
2462
2492
|
findings.push(..._translationApiRules(source));
|
|
2493
|
+
findings.push(..._scopeMintRules(source));
|
|
2463
2494
|
findings.push(..._handBuiltPageUrlRules(source));
|
|
2464
2495
|
findings.push(..._lucideIconRules(source));
|
|
2465
2496
|
findings.push(..._bundledAssetRules(source, options && options.assetFiles));
|
package/dist/linter.js
CHANGED
|
@@ -695,6 +695,36 @@ function _translationApiRules(source) {
|
|
|
695
695
|
return findings;
|
|
696
696
|
}
|
|
697
697
|
|
|
698
|
+
// sc-12906 — no-scope-header-mint.
|
|
699
|
+
//
|
|
700
|
+
// The host mints each widget's `X-Widget-Scopes` header from the widget's own
|
|
701
|
+
// installation. A bundle runs in the host's JS realm, so one that calls the
|
|
702
|
+
// mint itself, or sets the header, could borrow another widget's scopes. The
|
|
703
|
+
// server refuses a built-in nobody placed; this catches the attempt at publish.
|
|
704
|
+
// An error with no opt-out: no correct widget touches either.
|
|
705
|
+
function _scopeMintRules(source) {
|
|
706
|
+
const findings = [];
|
|
707
|
+
const needles = CONTRACT.scopeMintPatterns || [];
|
|
708
|
+
const lines = _stripNonCode(source, { keepStrings: true }).split(/\r?\n/);
|
|
709
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
710
|
+
const lowered = lines[i].toLowerCase();
|
|
711
|
+
const needle = needles.find((n) => lowered.includes(n));
|
|
712
|
+
if (!needle) continue;
|
|
713
|
+
findings.push({
|
|
714
|
+
rule: "no-scope-header-mint",
|
|
715
|
+
severity: "error",
|
|
716
|
+
label:
|
|
717
|
+
`source references "${needle}" — the host attaches each widget's ` +
|
|
718
|
+
`permission header itself. Never call that endpoint or set that ` +
|
|
719
|
+
`header; declare the scopes your hooks need in ` +
|
|
720
|
+
`manifest.requestedScopes.`,
|
|
721
|
+
line: i + 1,
|
|
722
|
+
snippet: lines[i].trim().slice(0, 200),
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
return findings;
|
|
726
|
+
}
|
|
727
|
+
|
|
698
728
|
// REQ-USERMGMT / REQ-ACL-SYS M3 — scope-required-for-user-mutation.
|
|
699
729
|
//
|
|
700
730
|
// A widget that calls `useUsers().invite()` / `.deactivate()` /
|
|
@@ -2520,6 +2550,7 @@ export function lintSource(source, options) {
|
|
|
2520
2550
|
// sc-6970 — soft warning: HTML markup spliced into a content string.
|
|
2521
2551
|
findings.push(..._htmlInContentRules(source));
|
|
2522
2552
|
findings.push(..._translationApiRules(source));
|
|
2553
|
+
findings.push(..._scopeMintRules(source));
|
|
2523
2554
|
findings.push(..._handBuiltPageUrlRules(source));
|
|
2524
2555
|
findings.push(..._lucideIconRules(source));
|
|
2525
2556
|
findings.push(..._bundledAssetRules(source, options && options.assetFiles));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.273.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -4953,6 +4953,12 @@ const TRANSLATION_API_HOSTS = [
|
|
|
4953
4953
|
"lingvanex.com",
|
|
4954
4954
|
];
|
|
4955
4955
|
|
|
4956
|
+
// sc-12906: the host mints a widget's `X-Widget-Scopes` header, never the
|
|
4957
|
+
// widget. Bundles share the host's JS realm, so a widget calling the mint
|
|
4958
|
+
// itself, or forging the header, could borrow another widget's scopes. Matched
|
|
4959
|
+
// case-insensitively against code (comments are blanked before matching).
|
|
4960
|
+
const SCOPE_MINT_PATTERNS = ["/widgets/scope-token", "x-widget-scopes"];
|
|
4961
|
+
|
|
4956
4962
|
// sc-4686 — the currency a price renders in when the HOST does not supply the
|
|
4957
4963
|
// workspace's own (a host predating the workspace.currency slice, or a caller
|
|
4958
4964
|
// that forgot to wire it). A rendering fallback so a price is never blank or
|
|
@@ -6556,7 +6562,10 @@ const CONTRACT = deepFreeze({
|
|
|
6556
6562
|
// 1.222.0: stricter (sc-14479) — `self`, `top`, `parent`, `frames`,
|
|
6557
6563
|
// `opener`, `frameElement` and `.constructor` join bannedApis; `eval` and
|
|
6558
6564
|
// `Function` are refused as any reference, not only as a call.
|
|
6559
|
-
|
|
6565
|
+
// 1.223.0: stricter (sc-12906) — `scopeMintPatterns`; a widget that calls
|
|
6566
|
+
// the scope-header mint or sets `X-Widget-Scopes` is refused
|
|
6567
|
+
// (`no-scope-header-mint`).
|
|
6568
|
+
version: "1.223.0",
|
|
6560
6569
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6561
6570
|
hooks: HOOKS,
|
|
6562
6571
|
primitives: PRIMITIVES,
|
|
@@ -6601,6 +6610,7 @@ const CONTRACT = deepFreeze({
|
|
|
6601
6610
|
currencyFormats: CURRENCY_FORMATS,
|
|
6602
6611
|
currencyFormatFallback: CURRENCY_FORMAT_FALLBACK,
|
|
6603
6612
|
translationApiHosts: TRANSLATION_API_HOSTS,
|
|
6613
|
+
scopeMintPatterns: SCOPE_MINT_PATTERNS,
|
|
6604
6614
|
});
|
|
6605
6615
|
|
|
6606
6616
|
function isHookAllowed(name) {
|