@colixsystems/widget-sdk 0.149.0 → 0.150.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/dist/linter.js CHANGED
@@ -530,13 +530,13 @@ function _scopeRules(source, manifest) {
530
530
  }
531
531
 
532
532
  // sc-7991 — every send spends the workspace's AI credits, so the manifest has
533
- // to declare the capability. This gate is ADVISORY, not an authorization
534
- // boundary: unlike users/groups (minted into X-Widget-Scopes and checked
535
- // server-side), nothing re-checks ai.invoke at call time, so a widget that
536
- // evades this regex still reaches the endpoint. What actually bounds abuse is
537
- // the app-user JWT, the per-actor and per-tenant limiters and the AI quota
538
- // gate. The scope's real job is DISCLOSURE — it is what the marketplace
539
- // install consent screen shows.
533
+ // to declare the capability. This gate is still ADVISORY: the scope itself is
534
+ // not minted into X-Widget-Scopes, so a widget that evades this regex still
535
+ // reaches the endpoint, and the scope's real job is DISCLOSURE — it is what
536
+ // the marketplace install consent screen shows.
537
+ // sc-8128 — but the endpoint is no longer unguarded: a turn is refused unless
538
+ // the agent is bound on a page the caller can read. Abuse is bounded by that,
539
+ // the app-user JWT, the per-actor and per-tenant limiters and the AI quota.
540
540
  if (/\buseAgent\s*\(/.test(source)) {
541
541
  const invokes = declared.has("ai.invoke:*") || declared.has("ai.invoke");
542
542
  if (!invokes) {
@@ -1667,6 +1667,19 @@ function _bracedSpan(code, from) {
1667
1667
  return null;
1668
1668
  }
1669
1669
 
1670
+ // The non-escape branch excludes `\` so the two branches can never both
1671
+ // match the same character. With a bare `.`, escape pairs before a quote
1672
+ // the anchor then rejects backtrack exponentially — 60 characters of an
1673
+ // authored widget draft wedge the single-threaded API pod for ~60s, on the
1674
+ // AI-widget-agent publish vet, not the package-upload path (sc-8051, sc-8041).
1675
+ //
1676
+ // It excludes the line terminators too, because a character class — unlike
1677
+ // `.` — matches them, which would widen the literal past its own line.
1678
+ // An odd trailing backslash run therefore no longer matches; `_bracedSpan`
1679
+ // shares the escape rule and never yields one.
1680
+ const _TEXT_LITERAL_CHILD_RE =
1681
+ /^(["'`])((?:\\.|(?!\1)[^\\\r\n\u2028\u2029])*)\1$/;
1682
+
1670
1683
  // Copy pinned in a `<Text>`: a bare literal child, or a braced child that is
1671
1684
  // NOTHING BUT a string literal (`{"Hello"}`) — the one-pair-of-braces evasion.
1672
1685
  // A braced expression carrying an identifier is left alone: it may already be
@@ -1689,8 +1702,7 @@ function _textLiteralOccurrences(codes, masks) {
1689
1702
  let text = null;
1690
1703
  if (code[i] === "{") {
1691
1704
  const inner = _bracedSpan(code, i);
1692
- const lit =
1693
- inner && inner.trim().match(/^(["'`])((?:\\.|(?!\1).)*)\1$/);
1705
+ const lit = inner && inner.trim().match(_TEXT_LITERAL_CHILD_RE);
1694
1706
  // A template carrying `${…}` interpolates a value, so it is not a literal.
1695
1707
  if (lit && !(lit[1] === "`" && lit[2].includes("${"))) text = lit[2];
1696
1708
  } else {
@@ -99,6 +99,11 @@ function normaliseThemeComponents(raw) {
99
99
  }
100
100
 
101
101
 
102
+ // Long enough for a hex, an enum value or a font name; short enough that a
103
+ // hand-edited theme_config cannot smuggle a payload into every widget.
104
+ // Named because the Mason plan gate prints it when it rejects a value.
105
+ const MAX_STYLE_STRING_LENGTH = 64;
106
+
102
107
  // Values are assigned onto plain object literals, so `__proto__` would set a
103
108
  // PROTOTYPE rather than a style field. Refused at every level.
104
109
  const UNSAFE_STYLE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
@@ -125,9 +130,9 @@ function coerceWidgetStyleValue(value, depth = 0) {
125
130
  const { maxValueDepth, maxValueEntries } = CONTRACT.themeWidgetStyles;
126
131
  if (typeof value === "string") {
127
132
  const trimmed = value.trim();
128
- // Long enough for a hex, an enum value or a font name; short enough that a
129
- // hand-edited theme_config cannot smuggle a payload into every widget.
130
- return trimmed && trimmed.length <= 64 ? trimmed : undefined;
133
+ return trimmed && trimmed.length <= MAX_STYLE_STRING_LENGTH
134
+ ? trimmed
135
+ : undefined;
131
136
  }
132
137
  if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
133
138
  if (typeof value === "boolean") return value;
@@ -393,4 +398,4 @@ function applyThemeComponentStyle(manifestId, theme, props, styleSchema) {
393
398
  return { ...base, style: { ...themed, ...authored } };
394
399
  }
395
400
 
396
- module.exports = { normaliseThemeComponents, normaliseWidgetStyleFields, normaliseWidgetStyles, applyThemeComponentStyle };
401
+ module.exports = { normaliseThemeComponents, normaliseWidgetStyleFields, normaliseWidgetStyles, applyThemeComponentStyle, styleValueBytes, MAX_STYLE_STRING_LENGTH };
@@ -91,6 +91,11 @@ export function normaliseThemeComponents(raw) {
91
91
  }
92
92
 
93
93
 
94
+ // Long enough for a hex, an enum value or a font name; short enough that a
95
+ // hand-edited theme_config cannot smuggle a payload into every widget.
96
+ // Named because the Mason plan gate prints it when it rejects a value.
97
+ export const MAX_STYLE_STRING_LENGTH = 64;
98
+
94
99
  // Values are assigned onto plain object literals, so `__proto__` would set a
95
100
  // PROTOTYPE rather than a style field. Refused at every level.
96
101
  const UNSAFE_STYLE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
@@ -117,9 +122,9 @@ function coerceWidgetStyleValue(value, depth = 0) {
117
122
  const { maxValueDepth, maxValueEntries } = CONTRACT.themeWidgetStyles;
118
123
  if (typeof value === "string") {
119
124
  const trimmed = value.trim();
120
- // Long enough for a hex, an enum value or a font name; short enough that a
121
- // hand-edited theme_config cannot smuggle a payload into every widget.
122
- return trimmed && trimmed.length <= 64 ? trimmed : undefined;
125
+ return trimmed && trimmed.length <= MAX_STYLE_STRING_LENGTH
126
+ ? trimmed
127
+ : undefined;
123
128
  }
124
129
  if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
125
130
  if (typeof value === "boolean") return value;
@@ -157,7 +162,7 @@ function coerceWidgetStyleValue(value, depth = 0) {
157
162
  }
158
163
 
159
164
  // What the value actually costs the cold-start read and the baked export.
160
- function styleValueBytes(value) {
165
+ export function styleValueBytes(value) {
161
166
  const json = JSON.stringify(value);
162
167
  return typeof json === "string" ? json.length : Infinity;
163
168
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.149.0",
3
+ "version": "0.150.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",
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "scripts": {
51
51
  "build": "node scripts/build.js",
52
- "test": "node --test src/__tests__/contract.test.js src/__tests__/theme-token-refs.test.js src/__tests__/vetted-imports-audit.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-invites.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-payments.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-section-empty.test.js src/__tests__/hooks-screen-title.test.js src/__tests__/hooks-widget-event.test.js src/__tests__/hooks-widget-input.test.js src/__tests__/hooks-identification.test.js src/__tests__/hooks-subscription.test.js src/__tests__/hooks-volatile-query-key.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-translation-api.test.js src/__tests__/linter-page-url.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-flex-basis-percent.test.js src/__tests__/linter-hardcoded-design.test.js src/__tests__/linter-editability.test.js src/__tests__/linter-measured-padding.test.js src/__tests__/linter-payment-error.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/hooks-translate.test.js src/__tests__/devserver.test.js src/__tests__/flatten-entry.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/corner-radius.test.js src/__tests__/theme-components-parity.test.js src/__tests__/navigation-parity.test.js src/__tests__/theme-depth-tokens.test.js src/__tests__/interaction-lift.test.js src/__tests__/toast-host.test.js src/__tests__/overlay-tokens.test.js src/__tests__/hooks-domain-error-mapping.test.js src/__tests__/linter-datastore-error.test.js src/__tests__/linter-write-gating.test.js src/__tests__/hooks-speech-to-text.test.js src/__tests__/hooks-camera.test.js src/__tests__/hooks-barcode.test.js src/__tests__/hooks-image-editor.test.js src/__tests__/hooks-bound-columns.test.js src/__tests__/hooks-stable-query.test.js src/__tests__/hooks-can-write.test.js src/__tests__/widget-route.test.js src/__tests__/linter-html-in-content.test.js src/__tests__/markdown.test.js src/__tests__/markdown-edit.test.js src/__tests__/richtext-tokens.test.js src/__tests__/style-group.test.js"
52
+ "test": "node --test \"src/__tests__/*.test.js\""
53
53
  },
54
54
  "engines": {
55
55
  "node": ">=18"