@getforma/core 0.2.0 → 0.3.1
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 +265 -13
- package/dist/{chunk-VKKPX4YU.js → chunk-5H52PKGF.js} +62 -12
- package/dist/chunk-5H52PKGF.js.map +1 -0
- package/dist/{chunk-VP5EOGM5.cjs → chunk-TSQ7AKFT.cjs} +62 -11
- package/dist/chunk-TSQ7AKFT.cjs.map +1 -0
- package/dist/forma-runtime-csp.js +2 -0
- package/dist/forma-runtime.js +2 -0
- package/dist/formajs-runtime-hardened.global.js +1 -1
- package/dist/formajs-runtime-hardened.global.js.map +1 -1
- package/dist/formajs-runtime.global.js +1 -1
- package/dist/formajs-runtime.global.js.map +1 -1
- package/dist/formajs.global.js +1 -1
- package/dist/formajs.global.js.map +1 -1
- package/dist/index.cjs +100 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -27
- package/dist/index.d.ts +43 -27
- package/dist/index.js +57 -27
- package/dist/index.js.map +1 -1
- package/dist/runtime-hardened.cjs +166 -50
- package/dist/runtime-hardened.cjs.map +1 -1
- package/dist/runtime-hardened.d.cts +86 -0
- package/dist/runtime-hardened.d.ts +86 -0
- package/dist/runtime-hardened.js +166 -50
- package/dist/runtime-hardened.js.map +1 -1
- package/dist/runtime.cjs +188 -72
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +167 -51
- package/dist/runtime.js.map +1 -1
- package/dist/ssr/index.cjs +59 -71
- package/dist/ssr/index.cjs.map +1 -1
- package/dist/ssr/index.d.cts +4 -0
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.js +59 -71
- package/dist/ssr/index.js.map +1 -1
- package/package.json +26 -3
- package/dist/chunk-VKKPX4YU.js.map +0 -1
- package/dist/chunk-VP5EOGM5.cjs.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { internalEffect, reconcileList, batch } from './chunk-
|
|
1
|
+
import { internalEffect, reconcileList, batch } from './chunk-5H52PKGF.js';
|
|
2
2
|
import { createValueSignal, createComputed } from './chunk-KX5WRZH7.js';
|
|
3
3
|
|
|
4
4
|
// src/dom/reconcile.ts
|
|
@@ -479,6 +479,7 @@ if (buildUnsafeEvalMode) {
|
|
|
479
479
|
_unsafeEvalMode = buildUnsafeEvalMode;
|
|
480
480
|
if (_unsafeEvalMode === "locked-off") _allowUnsafeEval = false;
|
|
481
481
|
if (_unsafeEvalMode === "locked-on") _allowUnsafeEval = true;
|
|
482
|
+
if (_unsafeEvalMode === "mutable") _allowUnsafeEval = true;
|
|
482
483
|
}
|
|
483
484
|
var runtimeConfig = readRuntimeConfig();
|
|
484
485
|
var configUnsafeMode = runtimeConfig.lockUnsafeEval ? "locked-off" : runtimeConfig.unsafeEvalMode;
|
|
@@ -556,7 +557,6 @@ var RE_STRING_DOUBLE = /^"[^"]*"$/;
|
|
|
556
557
|
var RE_NUMBER = /^-?\d+(\.\d+)?$/;
|
|
557
558
|
var RE_IDENTIFIER = /^[a-zA-Z_$]\w*$/;
|
|
558
559
|
var RE_DOT_ACCESS = /^(\w+)\.(\w+)$/;
|
|
559
|
-
var RE_DEEP_DOT = /^(\w+)\.(\w+)\.(\w+)(?:\.(\w+))?$/;
|
|
560
560
|
var RE_BRACKET = /^(\w+)\[(\d+|'[^']*'|"[^"]*")\]$/;
|
|
561
561
|
var RE_TERNARY = /^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/;
|
|
562
562
|
var RE_NULLISH = /^(.+?)\s*\?\?\s*(.+)$/;
|
|
@@ -567,7 +567,6 @@ var RE_MUL = /^(.+?)\s*([*/%])\s*(.+)$/;
|
|
|
567
567
|
var RE_ADD = /^(.+?)\s*([+-])\s*(.+)$/;
|
|
568
568
|
var RE_TEMPLATE_LIT = /^`([^`]*)`$/;
|
|
569
569
|
var RE_TEMPLATE_INTERP = /\$\{([^}]+)\}/g;
|
|
570
|
-
var RE_METHOD_CALL = /^(\w+)\.(\w+)\((.*)\)$/;
|
|
571
570
|
var RE_GROUP_METHOD_CALL = /^\((.+)\)\.(\w+)\((.*)\)$/;
|
|
572
571
|
var RE_STRIP_BRACES = /^\{|\}$/g;
|
|
573
572
|
var RE_DIGIT_ONLY = /^\d+$/;
|
|
@@ -582,6 +581,7 @@ var RE_COMPUTED = /^(\w+)\s*=\s*(.+)$/;
|
|
|
582
581
|
var RE_FETCH = /^(.+?)(?:→|->)\s*(\S+)(.*)$/;
|
|
583
582
|
var RE_FETCH_METHOD = /^(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/i;
|
|
584
583
|
var RE_STRIP_ITEM_BRACES = /^\{item\.?|\}$/g;
|
|
584
|
+
var RE_EVENT_REF = /\bevent\b|\$event\b/;
|
|
585
585
|
var RE_REFETCH_CALL = /^\$refetch\(\s*['"]([^'"]+)['"]\s*\)$/;
|
|
586
586
|
var TRANSITION_STATE_SYM = /* @__PURE__ */ Symbol.for("forma-transition-state");
|
|
587
587
|
var EXPRESSION_CACHE_MAX = 2048;
|
|
@@ -596,7 +596,40 @@ function cacheExpression(key, factory) {
|
|
|
596
596
|
var scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
597
597
|
var scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
598
598
|
var compiledTemplateCache = /* @__PURE__ */ new Map();
|
|
599
|
-
var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
599
|
+
var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
600
|
+
"constructor",
|
|
601
|
+
"__proto__",
|
|
602
|
+
"prototype",
|
|
603
|
+
"__defineGetter__",
|
|
604
|
+
"__defineSetter__",
|
|
605
|
+
"__lookupGetter__",
|
|
606
|
+
"__lookupSetter__",
|
|
607
|
+
"eval"
|
|
608
|
+
]);
|
|
609
|
+
var BLOCKED_METHOD_REGEXES = (() => {
|
|
610
|
+
const result = [];
|
|
611
|
+
for (const name of UNSAFE_METHOD_NAMES) {
|
|
612
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
613
|
+
result.push({
|
|
614
|
+
name,
|
|
615
|
+
// Match as property access (.name) or bare identifier at start
|
|
616
|
+
dotRe: new RegExp(`(?:^|\\.)${escaped}(?:\\s*\\(|\\s*$|[^\\w$])`, "m"),
|
|
617
|
+
// Match bracket access with single quotes, double quotes, or backticks
|
|
618
|
+
bracketRe: new RegExp(`\\[\\s*(?:'${escaped}'|"${escaped}"|\`` + escaped + `\`)\\s*\\]`)
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
})();
|
|
623
|
+
function findBlockedMethod(expr) {
|
|
624
|
+
let cleaned = expr.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
625
|
+
cleaned = cleaned.replace(/\/\/[^\n]*/g, "");
|
|
626
|
+
cleaned = cleaned.replace(/\s*\.\s*/g, ".");
|
|
627
|
+
for (const { name, dotRe, bracketRe } of BLOCKED_METHOD_REGEXES) {
|
|
628
|
+
if (dotRe.test(cleaned)) return name;
|
|
629
|
+
if (bracketRe.test(cleaned)) return name;
|
|
630
|
+
}
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
600
633
|
var TEXT_BINDING_SYM = /* @__PURE__ */ Symbol.for("forma-text-binding-cache");
|
|
601
634
|
function toTextValue(value) {
|
|
602
635
|
if (value == null) return "";
|
|
@@ -871,6 +904,7 @@ function consumeStatement(raw) {
|
|
|
871
904
|
function parseIfHandler(expr, scope) {
|
|
872
905
|
const input = expr.trim();
|
|
873
906
|
if (!RE_IF_PREFIX.test(input)) return null;
|
|
907
|
+
if (RE_EVENT_REF.test(input)) return null;
|
|
874
908
|
let idx = 2;
|
|
875
909
|
while (idx < input.length && /\s/.test(input[idx])) idx++;
|
|
876
910
|
if (input[idx] !== "(") return null;
|
|
@@ -1272,6 +1306,68 @@ function cloneAttributeTemplates(el, item) {
|
|
|
1272
1306
|
}
|
|
1273
1307
|
}
|
|
1274
1308
|
}
|
|
1309
|
+
function parseChainedAccess(expr, scope) {
|
|
1310
|
+
let pos = 0;
|
|
1311
|
+
const identMatch = expr.match(/^[a-zA-Z_$]\w*/);
|
|
1312
|
+
if (!identMatch) return null;
|
|
1313
|
+
const rootName = identMatch[0];
|
|
1314
|
+
pos = rootName.length;
|
|
1315
|
+
if (pos >= expr.length) return null;
|
|
1316
|
+
if (expr[pos] !== "." && !(expr[pos] === "?" && expr[pos + 1] === ".")) return null;
|
|
1317
|
+
const steps = [];
|
|
1318
|
+
while (pos < expr.length) {
|
|
1319
|
+
let optional = false;
|
|
1320
|
+
if (expr[pos] === "?" && expr[pos + 1] === ".") {
|
|
1321
|
+
optional = true;
|
|
1322
|
+
pos += 2;
|
|
1323
|
+
} else if (expr[pos] === ".") {
|
|
1324
|
+
pos += 1;
|
|
1325
|
+
} else {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
const nameMatch = expr.slice(pos).match(/^\w+/);
|
|
1329
|
+
if (!nameMatch) return null;
|
|
1330
|
+
const name = nameMatch[0];
|
|
1331
|
+
pos += name.length;
|
|
1332
|
+
if (UNSAFE_METHOD_NAMES.has(name)) return () => void 0;
|
|
1333
|
+
if (pos < expr.length && expr[pos] === "(") {
|
|
1334
|
+
const balanced = readBalancedSegment(expr, pos, "(", ")");
|
|
1335
|
+
if (!balanced) return null;
|
|
1336
|
+
const argsRaw = balanced.inner.trim();
|
|
1337
|
+
const argFns = [];
|
|
1338
|
+
for (const arg of splitCallArgs(argsRaw)) {
|
|
1339
|
+
const parsed = parseExpression(arg, scope);
|
|
1340
|
+
if (!parsed) return null;
|
|
1341
|
+
argFns.push(parsed);
|
|
1342
|
+
}
|
|
1343
|
+
steps.push({ type: "call", name, optional, argFns });
|
|
1344
|
+
pos = balanced.end + 1;
|
|
1345
|
+
} else {
|
|
1346
|
+
steps.push({ type: "prop", name, optional });
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (pos !== expr.length) return null;
|
|
1350
|
+
if (steps.length === 0) return null;
|
|
1351
|
+
const rootExpr = rootName === "Math" ? (() => Math) : (() => scope.getters[rootName]?.());
|
|
1352
|
+
return () => {
|
|
1353
|
+
let val = rootExpr();
|
|
1354
|
+
for (const step of steps) {
|
|
1355
|
+
if (val == null) {
|
|
1356
|
+
if (step.optional) return void 0;
|
|
1357
|
+
return void 0;
|
|
1358
|
+
}
|
|
1359
|
+
if (step.type === "prop") {
|
|
1360
|
+
val = val[step.name];
|
|
1361
|
+
} else {
|
|
1362
|
+
const method = val[step.name];
|
|
1363
|
+
if (typeof method !== "function") return void 0;
|
|
1364
|
+
const args = step.argFns.map((fn) => fn());
|
|
1365
|
+
val = method.apply(val, args);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return val;
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1275
1371
|
function parseExpression(expr, scope) {
|
|
1276
1372
|
const cachedFactory = expressionCache.get(expr);
|
|
1277
1373
|
if (cachedFactory) return cachedFactory(scope);
|
|
@@ -1322,24 +1418,9 @@ function parseExpressionUncached(expr, scope) {
|
|
|
1322
1418
|
if (RE_IDENTIFIER.test(expr)) {
|
|
1323
1419
|
return () => scope.getters[expr]?.();
|
|
1324
1420
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
return () => {
|
|
1329
|
-
const obj = scope.getters[p1]?.();
|
|
1330
|
-
return obj?.[p2];
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
|
-
const deepDotMatch = expr.match(RE_DEEP_DOT);
|
|
1334
|
-
if (deepDotMatch) {
|
|
1335
|
-
const p1 = deepDotMatch[1], p2 = deepDotMatch[2], p3 = deepDotMatch[3], p4 = deepDotMatch[4];
|
|
1336
|
-
return () => {
|
|
1337
|
-
let val = scope.getters[p1]?.();
|
|
1338
|
-
val = val?.[p2];
|
|
1339
|
-
val = val?.[p3];
|
|
1340
|
-
if (p4) val = val?.[p4];
|
|
1341
|
-
return val;
|
|
1342
|
-
};
|
|
1421
|
+
{
|
|
1422
|
+
const chainResult = parseChainedAccess(expr, scope);
|
|
1423
|
+
if (chainResult) return chainResult;
|
|
1343
1424
|
}
|
|
1344
1425
|
const groupedCallMatch = expr.match(RE_GROUP_METHOD_CALL);
|
|
1345
1426
|
if (groupedCallMatch) {
|
|
@@ -1363,28 +1444,6 @@ function parseExpressionUncached(expr, scope) {
|
|
|
1363
1444
|
return method.apply(base, args);
|
|
1364
1445
|
};
|
|
1365
1446
|
}
|
|
1366
|
-
const callMatch = expr.match(RE_METHOD_CALL);
|
|
1367
|
-
if (callMatch) {
|
|
1368
|
-
const baseName = callMatch[1];
|
|
1369
|
-
const methodName = callMatch[2];
|
|
1370
|
-
const argsRaw = callMatch[3].trim();
|
|
1371
|
-
if (UNSAFE_METHOD_NAMES.has(methodName)) return () => void 0;
|
|
1372
|
-
const baseExpr = baseName === "Math" ? (() => Math) : parseExpression(baseName, scope);
|
|
1373
|
-
if (!baseExpr) return null;
|
|
1374
|
-
const argFns = [];
|
|
1375
|
-
for (const arg of splitCallArgs(argsRaw)) {
|
|
1376
|
-
const parsed = parseExpression(arg, scope);
|
|
1377
|
-
if (!parsed) return null;
|
|
1378
|
-
argFns.push(parsed);
|
|
1379
|
-
}
|
|
1380
|
-
return () => {
|
|
1381
|
-
const base = baseExpr();
|
|
1382
|
-
const method = base?.[methodName];
|
|
1383
|
-
if (typeof method !== "function") return void 0;
|
|
1384
|
-
const args = argFns.map((fn) => fn());
|
|
1385
|
-
return method.apply(base, args);
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
1447
|
if (expr.startsWith("!")) {
|
|
1389
1448
|
const inner = parseExpression(expr.slice(1).trim(), scope);
|
|
1390
1449
|
if (inner) return () => !inner();
|
|
@@ -1403,6 +1462,29 @@ function parseExpressionUncached(expr, scope) {
|
|
|
1403
1462
|
return () => objExpr()?.[key];
|
|
1404
1463
|
}
|
|
1405
1464
|
}
|
|
1465
|
+
if (expr.startsWith("[")) {
|
|
1466
|
+
const balanced = readBalancedSegment(expr, 0, "[", "]");
|
|
1467
|
+
if (balanced && balanced.end === expr.length - 1) {
|
|
1468
|
+
const inner = balanced.inner.trim();
|
|
1469
|
+
if (inner === "") {
|
|
1470
|
+
return () => [];
|
|
1471
|
+
}
|
|
1472
|
+
const elements = splitCallArgs(inner);
|
|
1473
|
+
const elementFns = [];
|
|
1474
|
+
let allParsed = true;
|
|
1475
|
+
for (const el of elements) {
|
|
1476
|
+
const parsed = parseExpression(el.trim(), scope);
|
|
1477
|
+
if (!parsed) {
|
|
1478
|
+
allParsed = false;
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
elementFns.push(parsed);
|
|
1482
|
+
}
|
|
1483
|
+
if (allParsed) {
|
|
1484
|
+
return () => elementFns.map((fn) => fn());
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1406
1488
|
const ternaryMatch = expr.match(RE_TERNARY);
|
|
1407
1489
|
if (ternaryMatch) {
|
|
1408
1490
|
const cond = parseExpression(ternaryMatch[1].trim(), scope);
|
|
@@ -1532,6 +1614,15 @@ function getScopeCache(cache, scope) {
|
|
|
1532
1614
|
}
|
|
1533
1615
|
return scoped;
|
|
1534
1616
|
}
|
|
1617
|
+
function cspExpressionHint(expr) {
|
|
1618
|
+
if (expr.includes("...")) {
|
|
1619
|
+
return `Unsupported expression in CSP-safe mode: spread syntax detected. Use .concat() instead, or enable unsafe-eval via setUnsafeEval(true).`;
|
|
1620
|
+
}
|
|
1621
|
+
if (expr.includes("=>")) {
|
|
1622
|
+
return `Unsupported expression in CSP-safe mode: arrow function detected. Extract logic to a data-computed attribute, or enable unsafe-eval via setUnsafeEval(true).`;
|
|
1623
|
+
}
|
|
1624
|
+
return `Unsupported expression in CSP-safe mode. Simplify the expression or enable unsafe-eval via setUnsafeEval(true).`;
|
|
1625
|
+
}
|
|
1535
1626
|
function buildEvaluator(expr, scope) {
|
|
1536
1627
|
const cleaned = expr.replace(RE_STRIP_BRACES, "").trim();
|
|
1537
1628
|
const cache = getScopeCache(scopeExpressionCache, scope);
|
|
@@ -1544,11 +1635,17 @@ function buildEvaluator(expr, scope) {
|
|
|
1544
1635
|
}
|
|
1545
1636
|
if (!_allowUnsafeEval) {
|
|
1546
1637
|
dbg("buildEvaluator: blocked unsafe eval fallback for expression:", cleaned);
|
|
1547
|
-
reportDiagnostic("expression-unsupported", cleaned,
|
|
1638
|
+
reportDiagnostic("expression-unsupported", cleaned, cspExpressionHint(cleaned));
|
|
1548
1639
|
const blocked = () => void 0;
|
|
1549
1640
|
cache.set(cleaned, blocked);
|
|
1550
1641
|
return blocked;
|
|
1551
1642
|
}
|
|
1643
|
+
const blockedMethod = findBlockedMethod(cleaned);
|
|
1644
|
+
if (blockedMethod) {
|
|
1645
|
+
const msg = `Blocked unsafe method "${blockedMethod}" in expression`;
|
|
1646
|
+
reportDiagnostic("expression-unsupported", cleaned, msg);
|
|
1647
|
+
throw new Error(`[FormaJS] ${msg}: ${cleaned}`);
|
|
1648
|
+
}
|
|
1552
1649
|
try {
|
|
1553
1650
|
const fn = new Function("__scope", `with(__scope) { return (${cleaned}); }`);
|
|
1554
1651
|
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
@@ -1564,7 +1661,7 @@ function buildEvaluator(expr, scope) {
|
|
|
1564
1661
|
cache.set(cleaned, unsafe);
|
|
1565
1662
|
return unsafe;
|
|
1566
1663
|
} catch {
|
|
1567
|
-
reportDiagnostic("expression-unsupported", cleaned, "Expression
|
|
1664
|
+
reportDiagnostic("expression-unsupported", cleaned, "Expression too complex for CSP-safe mode. Enable unsafe-eval via FormaRuntime.unsafeEval = true, or use the standard (non-hardened) build.");
|
|
1568
1665
|
const failed = () => void 0;
|
|
1569
1666
|
cache.set(cleaned, failed);
|
|
1570
1667
|
return failed;
|
|
@@ -1670,7 +1767,13 @@ function parseHandler(expr, scope) {
|
|
|
1670
1767
|
return null;
|
|
1671
1768
|
}
|
|
1672
1769
|
function buildHandler(expr, scope) {
|
|
1673
|
-
|
|
1770
|
+
let cleaned = expr.trim();
|
|
1771
|
+
if (cleaned.startsWith("{")) {
|
|
1772
|
+
const seg = readBalancedSegment(cleaned, 0, "{", "}");
|
|
1773
|
+
if (seg && seg.end === cleaned.length - 1) {
|
|
1774
|
+
cleaned = seg.inner.trim();
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1674
1777
|
const cache = getScopeCache(scopeHandlerCache, scope);
|
|
1675
1778
|
const cached = cache.get(cleaned);
|
|
1676
1779
|
if (cached) return cached;
|
|
@@ -1682,7 +1785,7 @@ function buildHandler(expr, scope) {
|
|
|
1682
1785
|
}
|
|
1683
1786
|
if (!_allowUnsafeEval) {
|
|
1684
1787
|
dbg("buildHandler: blocked unsafe eval fallback for expression:", cleaned);
|
|
1685
|
-
reportDiagnostic("handler-unsupported", cleaned,
|
|
1788
|
+
reportDiagnostic("handler-unsupported", cleaned, cspExpressionHint(cleaned));
|
|
1686
1789
|
const result = {
|
|
1687
1790
|
handler: () => {
|
|
1688
1791
|
},
|
|
@@ -1691,6 +1794,12 @@ function buildHandler(expr, scope) {
|
|
|
1691
1794
|
cache.set(cleaned, result);
|
|
1692
1795
|
return result;
|
|
1693
1796
|
}
|
|
1797
|
+
const blockedMethod = findBlockedMethod(cleaned);
|
|
1798
|
+
if (blockedMethod) {
|
|
1799
|
+
const msg = `Blocked unsafe method "${blockedMethod}" in handler`;
|
|
1800
|
+
reportDiagnostic("handler-unsupported", cleaned, msg);
|
|
1801
|
+
throw new Error(`[FormaJS] ${msg}: ${cleaned}`);
|
|
1802
|
+
}
|
|
1694
1803
|
try {
|
|
1695
1804
|
const fn = new Function("__scope", "$event", "event", `with(__scope) { ${cleaned} }`);
|
|
1696
1805
|
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
@@ -1718,7 +1827,7 @@ function buildHandler(expr, scope) {
|
|
|
1718
1827
|
cache.set(cleaned, result);
|
|
1719
1828
|
return result;
|
|
1720
1829
|
} catch {
|
|
1721
|
-
reportDiagnostic("handler-unsupported", cleaned, "
|
|
1830
|
+
reportDiagnostic("handler-unsupported", cleaned, "Expression too complex for CSP-safe mode. Enable unsafe-eval via FormaRuntime.unsafeEval = true, or use the standard (non-hardened) build.");
|
|
1722
1831
|
const result = {
|
|
1723
1832
|
handler: () => {
|
|
1724
1833
|
},
|
|
@@ -1728,16 +1837,22 @@ function buildHandler(expr, scope) {
|
|
|
1728
1837
|
return result;
|
|
1729
1838
|
}
|
|
1730
1839
|
}
|
|
1840
|
+
var FORBIDDEN_STATE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1731
1841
|
function parseState(raw) {
|
|
1842
|
+
let parsed;
|
|
1732
1843
|
try {
|
|
1733
|
-
|
|
1844
|
+
parsed = JSON.parse(raw);
|
|
1734
1845
|
} catch {
|
|
1735
1846
|
try {
|
|
1736
|
-
|
|
1847
|
+
parsed = JSON.parse(raw.replace(RE_UNQUOTED_KEYS, '"$1":'));
|
|
1737
1848
|
} catch {
|
|
1738
1849
|
return {};
|
|
1739
1850
|
}
|
|
1740
1851
|
}
|
|
1852
|
+
for (const key of FORBIDDEN_STATE_KEYS) {
|
|
1853
|
+
if (key in parsed) delete parsed[key];
|
|
1854
|
+
}
|
|
1855
|
+
return parsed;
|
|
1741
1856
|
}
|
|
1742
1857
|
function initScope(stateEl) {
|
|
1743
1858
|
const raw = stateEl.getAttribute("data-forma-state") ?? "{}";
|
|
@@ -2407,6 +2522,7 @@ function setUnsafeEvalMode(mode) {
|
|
|
2407
2522
|
_unsafeEvalMode = mode;
|
|
2408
2523
|
if (mode === "locked-off") _allowUnsafeEval = false;
|
|
2409
2524
|
if (mode === "locked-on") _allowUnsafeEval = true;
|
|
2525
|
+
if (mode === "mutable") _allowUnsafeEval = true;
|
|
2410
2526
|
scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
2411
2527
|
scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
2412
2528
|
}
|