@constela/start 1.3.3 → 1.3.5

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
@@ -227,6 +227,22 @@ title: Getting Started
227
227
  </Callout>
228
228
  ```
229
229
 
230
+ ### Security
231
+
232
+ MDX attribute expressions are validated at compile time. Dangerous patterns like `require()`, `eval()`, or `window` in actual code will throw explicit errors:
233
+
234
+ ```mdx
235
+ <!-- Error: MDX attribute contains disallowed pattern: require -->
236
+ <Button data={require("module")} />
237
+ ```
238
+
239
+ However, these words are allowed inside string literals:
240
+
241
+ ```mdx
242
+ <!-- OK: "require" is inside a string literal -->
243
+ <PropsTable items={[{ description: "operations that require one" }]} />
244
+ ```
245
+
230
246
  ## Configuration
231
247
 
232
248
  Create `constela.config.json`:
@@ -351,16 +351,56 @@ var DISALLOWED_PATTERNS = [
351
351
  /\bconstructor\b/,
352
352
  /\bprototype\b/
353
353
  ];
354
+ function extractCodeOutsideStrings(value) {
355
+ let result = "";
356
+ let i = 0;
357
+ while (i < value.length) {
358
+ const char = value[i];
359
+ if (char === '"' || char === "'" || char === "`") {
360
+ const quote = char;
361
+ i++;
362
+ while (i < value.length) {
363
+ if (value[i] === "\\" && i + 1 < value.length) {
364
+ i += 2;
365
+ } else if (value[i] === quote) {
366
+ i++;
367
+ break;
368
+ } else {
369
+ i++;
370
+ }
371
+ }
372
+ } else {
373
+ result += char;
374
+ i++;
375
+ }
376
+ }
377
+ return result;
378
+ }
354
379
  function isSafeLiteral(value) {
355
- return !DISALLOWED_PATTERNS.some((pattern) => pattern.test(value));
380
+ const codeOnly = extractCodeOutsideStrings(value);
381
+ for (const pattern of DISALLOWED_PATTERNS) {
382
+ if (pattern.test(codeOnly)) {
383
+ const patternStr = pattern.source;
384
+ const match = patternStr.match(/\\b\(?([\w|]+)\)?\\b/);
385
+ const matchedName = match?.[1] ?? patternStr;
386
+ return { safe: false, matchedPattern: matchedName };
387
+ }
388
+ }
389
+ return { safe: true };
356
390
  }
357
- function safeEvalLiteral(value) {
391
+ function safeEvalLiteral(value, attributeName) {
358
392
  try {
359
393
  return JSON.parse(value);
360
394
  } catch {
361
395
  }
362
- if (!isSafeLiteral(value)) {
363
- return null;
396
+ const safetyCheck = isSafeLiteral(value);
397
+ if (!safetyCheck.safe) {
398
+ const truncatedValue = value.length > 100 ? value.slice(0, 100) + "..." : value;
399
+ throw new Error(
400
+ `MDX attribute contains disallowed pattern: ${safetyCheck.matchedPattern}
401
+ Attribute: ${attributeName ?? "unknown"}
402
+ Value: "${truncatedValue}"`
403
+ );
364
404
  }
365
405
  try {
366
406
  const fn = new Function(`return (${value});`);
@@ -369,6 +409,17 @@ function safeEvalLiteral(value) {
369
409
  return null;
370
410
  }
371
411
  }
412
+ function checkExpressionSecurity(exprValue, attributeName) {
413
+ const safetyCheck = isSafeLiteral(exprValue);
414
+ if (!safetyCheck.safe) {
415
+ const truncatedValue = exprValue.length > 100 ? exprValue.slice(0, 100) + "..." : exprValue;
416
+ throw new Error(
417
+ `MDX attribute contains disallowed pattern: ${safetyCheck.matchedPattern}
418
+ Attribute: ${attributeName}
419
+ Value: "${truncatedValue}"`
420
+ );
421
+ }
422
+ }
372
423
  function parseAttributeValue(attr) {
373
424
  if (attr.value === null) {
374
425
  return lit(true);
@@ -383,8 +434,9 @@ function parseAttributeValue(attr) {
383
434
  if (exprValue === "null") return lit(null);
384
435
  const num = Number(exprValue);
385
436
  if (!Number.isNaN(num)) return lit(num);
437
+ checkExpressionSecurity(exprValue, attr.name);
386
438
  if (exprValue.startsWith("[") || exprValue.startsWith("{")) {
387
- const parsed = safeEvalLiteral(exprValue);
439
+ const parsed = safeEvalLiteral(exprValue, attr.name);
388
440
  if (parsed !== null && parsed !== void 0) {
389
441
  return lit(parsed);
390
442
  }
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  hyperlink,
5
5
  loadConfig,
6
6
  resolveConfig
7
- } from "../chunk-2P567CIH.js";
7
+ } from "../chunk-QEXDD2F2.js";
8
8
  import "../chunk-CYMS3K6V.js";
9
9
 
10
10
  // src/cli/index.ts
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  transformCsv,
25
25
  transformMdx,
26
26
  transformYaml
27
- } from "./chunk-2P567CIH.js";
27
+ } from "./chunk-QEXDD2F2.js";
28
28
  import {
29
29
  generateHydrationScript,
30
30
  renderPage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/start",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Meta-framework for Constela applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,11 +43,11 @@
43
43
  "postcss": "^8.5.0",
44
44
  "@tailwindcss/postcss": "^4.0.0",
45
45
  "tailwindcss": "^4.0.0",
46
- "@constela/core": "0.9.1",
47
- "@constela/router": "10.0.0",
48
- "@constela/compiler": "0.9.1",
49
- "@constela/runtime": "0.12.2",
50
- "@constela/server": "5.0.1"
46
+ "@constela/core": "0.10.0",
47
+ "@constela/compiler": "0.10.0",
48
+ "@constela/router": "11.0.0",
49
+ "@constela/runtime": "0.13.0",
50
+ "@constela/server": "6.0.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/mdast": "^4.0.4",