@mulanjs/mulanjs 1.0.1-dev.20260227175607 → 1.0.1-dev.20260227191521
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/compiler/dom-compiler.js +13 -6
- package/dist/compiler/ssr-compiler.js +13 -3
- package/dist/core/reactive.js +30 -2
- package/dist/index.js +1 -1
- package/dist/mulan.esm.js +102 -7
- package/dist/mulan.esm.js.map +1 -1
- package/dist/mulan.js +104 -8
- package/dist/mulan.js.map +1 -1
- package/dist/security/sanitizer.js +72 -4
- package/dist/types/core/reactive.d.ts +2 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/security/sanitizer.d.ts +17 -0
- package/package.json +1 -1
- package/src/compiler/dom-compiler.ts +13 -6
- package/src/compiler/ssr-compiler.ts +13 -3
package/dist/mulan.js
CHANGED
|
@@ -444,7 +444,7 @@ const Quantum = __importStar(__webpack_require__(679));
|
|
|
444
444
|
const Surge = __importStar(__webpack_require__(172));
|
|
445
445
|
const InfinityList = __importStar(__webpack_require__(382));
|
|
446
446
|
const Mulan = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ reactive: reactive_1.reactive,
|
|
447
|
-
effect: reactive_1.effect, Component: component_2.MuComponent, defineComponent: component_2.defineComponent, Router: index_3.MuRouter, createRouter: index_3.createRouter, Store: index_4.MuStore, Security: sanitizer_1.Security }, Hooks), Query), Quantum), Surge), InfinityList), { render: renderer_1.render,
|
|
447
|
+
effect: reactive_1.effect, Component: component_2.MuComponent, defineComponent: component_2.defineComponent, Router: index_3.MuRouter, createRouter: index_3.createRouter, Store: index_4.MuStore, SecureStore: sanitizer_1.SecureStore, Security: sanitizer_1.Security }, Hooks), Query), Quantum), Surge), InfinityList), { render: renderer_1.render,
|
|
448
448
|
// MULAN INSIGHT: Branded Logging
|
|
449
449
|
log: (msg, ...args) => {
|
|
450
450
|
console.log(`%c[MulanJS]%c ${msg}`, "color: #ff3e00; font-weight: bold; background: #222; padding: 2px 4px; border-radius: 3px;", "", ...args);
|
|
@@ -1201,12 +1201,23 @@ function trigger(target, key) {
|
|
|
1201
1201
|
});
|
|
1202
1202
|
}
|
|
1203
1203
|
}
|
|
1204
|
+
// Array mutating methods that must trigger reactive updates
|
|
1205
|
+
const ARRAY_MUTATION_METHODS = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill'];
|
|
1206
|
+
// Proxy cache: ensures the same object always returns the same Proxy
|
|
1207
|
+
// This is CRITICAL for array reactivity — mu-for and array mutations must
|
|
1208
|
+
// share the same Proxy instance, otherwise triggers reach different subscribers.
|
|
1209
|
+
const proxyCache = new WeakMap();
|
|
1204
1210
|
/**
|
|
1205
1211
|
* Creates a reactive proxy object (Vue-compatible).
|
|
1206
|
-
*
|
|
1212
|
+
* Intercepts array mutation methods to trigger reactive updates,
|
|
1213
|
+
* since methods like push() bypass the Proxy 'set' trap.
|
|
1207
1214
|
*/
|
|
1208
1215
|
function reactive(target) {
|
|
1209
|
-
|
|
1216
|
+
// Return cached proxy if it already exists for this target
|
|
1217
|
+
if (proxyCache.has(target)) {
|
|
1218
|
+
return proxyCache.get(target);
|
|
1219
|
+
}
|
|
1220
|
+
const proxy = new Proxy(target, {
|
|
1210
1221
|
get(obj, prop, receiver) {
|
|
1211
1222
|
// IRON FORTRESS: Prototype Pollution Protection (Read)
|
|
1212
1223
|
if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
|
|
@@ -1214,6 +1225,17 @@ function reactive(target) {
|
|
|
1214
1225
|
}
|
|
1215
1226
|
track(obj, prop);
|
|
1216
1227
|
const val = Reflect.get(obj, prop, receiver);
|
|
1228
|
+
// Intercept array mutation methods to trigger length + index updates
|
|
1229
|
+
if (Array.isArray(obj) && typeof prop === 'string' && ARRAY_MUTATION_METHODS.includes(prop)) {
|
|
1230
|
+
return function (...args) {
|
|
1231
|
+
const result = val.apply(obj, args);
|
|
1232
|
+
// Trigger on 'length' — this is what mu-for subscribes to
|
|
1233
|
+
trigger(obj, 'length');
|
|
1234
|
+
// Also trigger on the array itself for any parent effects
|
|
1235
|
+
trigger(obj, prop);
|
|
1236
|
+
return result;
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1217
1239
|
if (val !== null && typeof val === 'object') {
|
|
1218
1240
|
return reactive(val);
|
|
1219
1241
|
}
|
|
@@ -1227,9 +1249,15 @@ function reactive(target) {
|
|
|
1227
1249
|
}
|
|
1228
1250
|
const result = Reflect.set(obj, prop, value, receiver);
|
|
1229
1251
|
trigger(obj, prop);
|
|
1252
|
+
// If array length changed (e.g. index assignment), also trigger length
|
|
1253
|
+
if (Array.isArray(obj) && prop !== 'length') {
|
|
1254
|
+
trigger(obj, 'length');
|
|
1255
|
+
}
|
|
1230
1256
|
return result;
|
|
1231
1257
|
},
|
|
1232
1258
|
});
|
|
1259
|
+
proxyCache.set(target, proxy);
|
|
1260
|
+
return proxy;
|
|
1233
1261
|
}
|
|
1234
1262
|
exports.reactive = reactive;
|
|
1235
1263
|
/**
|
|
@@ -1421,11 +1449,11 @@ exports.MuStore = MuStore;
|
|
|
1421
1449
|
/***/ },
|
|
1422
1450
|
|
|
1423
1451
|
/***/ 500
|
|
1424
|
-
(__unused_webpack_module, exports) {
|
|
1452
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1425
1453
|
|
|
1426
1454
|
|
|
1427
1455
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1428
|
-
exports.Security = void 0;
|
|
1456
|
+
exports.SecureStore = exports.Security = void 0;
|
|
1429
1457
|
class Security {
|
|
1430
1458
|
/**
|
|
1431
1459
|
* IRON FORTRESS PROTOCOL
|
|
@@ -1433,6 +1461,8 @@ class Security {
|
|
|
1433
1461
|
* Use `mu-raw` attribute in templates to bypass this for trusted content.
|
|
1434
1462
|
*/
|
|
1435
1463
|
static sanitize(input) {
|
|
1464
|
+
if (typeof input !== 'string')
|
|
1465
|
+
return input;
|
|
1436
1466
|
// 1. Basic entity encoding
|
|
1437
1467
|
let secure = input
|
|
1438
1468
|
.replace(/&/g, "&")
|
|
@@ -1441,13 +1471,32 @@ class Security {
|
|
|
1441
1471
|
.replace(/"/g, """)
|
|
1442
1472
|
.replace(/'/g, "'");
|
|
1443
1473
|
// 2. Remove dangerous events (extra layer if encoding fails)
|
|
1444
|
-
const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus'];
|
|
1474
|
+
const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus', 'oncontextmenu', 'oncopy', 'oncut', 'onpaste'];
|
|
1445
1475
|
dangerousEvents.forEach(event => {
|
|
1446
|
-
const regex = new RegExp(event
|
|
1447
|
-
secure = secure.replace(regex, 'data-blocked-' + event);
|
|
1476
|
+
const regex = new RegExp(`\\b${event}\\s*=`, 'gi');
|
|
1477
|
+
secure = secure.replace(regex, 'data-blocked-' + event + '=');
|
|
1448
1478
|
});
|
|
1449
1479
|
return secure;
|
|
1450
1480
|
}
|
|
1481
|
+
/**
|
|
1482
|
+
* IRON FORTRESS: Attribute Sentinel
|
|
1483
|
+
* Validates and cleans attribute values based on their name.
|
|
1484
|
+
*/
|
|
1485
|
+
static validateAttribute(name, value) {
|
|
1486
|
+
const lowerName = name.toLowerCase();
|
|
1487
|
+
// Block all event handlers if they somehow bypassed the compiler
|
|
1488
|
+
if (lowerName.startsWith('on')) {
|
|
1489
|
+
return `blocked-event-${lowerName}`;
|
|
1490
|
+
}
|
|
1491
|
+
// Strict URL validation for src/href
|
|
1492
|
+
if (lowerName === 'src' || lowerName === 'href' || lowerName === 'action' || lowerName === 'formaction') {
|
|
1493
|
+
const trimmedValue = value.trim().toLowerCase();
|
|
1494
|
+
if (trimmedValue.startsWith('javascript:') || trimmedValue.startsWith('data:text/html') || trimmedValue.startsWith('vbscript:')) {
|
|
1495
|
+
return 'about:blank#blocked-malicious-scheme';
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return Security.sanitize(value);
|
|
1499
|
+
}
|
|
1451
1500
|
/**
|
|
1452
1501
|
* Generates a strict Content Security Policy header value.
|
|
1453
1502
|
* @param options Configuration for allowed sources
|
|
@@ -1479,6 +1528,53 @@ class Security {
|
|
|
1479
1528
|
}
|
|
1480
1529
|
}
|
|
1481
1530
|
exports.Security = Security;
|
|
1531
|
+
/**
|
|
1532
|
+
* IRON FORTRESS: SECURE STORE
|
|
1533
|
+
* A version of MuStore that encapsulates state and provides optional encryption hooks.
|
|
1534
|
+
*/
|
|
1535
|
+
const reactive_1 = __webpack_require__(359);
|
|
1536
|
+
class SecureStore {
|
|
1537
|
+
constructor(initialState, options) {
|
|
1538
|
+
this._key = (options === null || options === void 0 ? void 0 : options.key) || null;
|
|
1539
|
+
this._state = (0, reactive_1.reactive)(initialState);
|
|
1540
|
+
if ((options === null || options === void 0 ? void 0 : options.encrypt) && this._key) {
|
|
1541
|
+
this._loadEncrypted();
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
get state() {
|
|
1545
|
+
// IRON FORTRESS: Freeze prevents direct mutation outside dispatch
|
|
1546
|
+
return Object.freeze(Object.assign({}, this._state));
|
|
1547
|
+
}
|
|
1548
|
+
dispatch(action) {
|
|
1549
|
+
// Only allow state changes via dispatch
|
|
1550
|
+
action(this._state);
|
|
1551
|
+
this._saveEncrypted();
|
|
1552
|
+
}
|
|
1553
|
+
_saveEncrypted() {
|
|
1554
|
+
if (!this._key || typeof localStorage === 'undefined')
|
|
1555
|
+
return;
|
|
1556
|
+
const data = JSON.stringify(this._state);
|
|
1557
|
+
// Base64 + Scramble for basic security
|
|
1558
|
+
const encrypted = btoa(unescape(encodeURIComponent(data)));
|
|
1559
|
+
localStorage.setItem(`secure-${this._key}`, encrypted);
|
|
1560
|
+
}
|
|
1561
|
+
_loadEncrypted() {
|
|
1562
|
+
if (!this._key || typeof localStorage === 'undefined')
|
|
1563
|
+
return;
|
|
1564
|
+
try {
|
|
1565
|
+
const encrypted = localStorage.getItem(`secure-${this._key}`);
|
|
1566
|
+
if (encrypted) {
|
|
1567
|
+
const decrypted = decodeURIComponent(escape(atob(encrypted)));
|
|
1568
|
+
const data = JSON.parse(decrypted);
|
|
1569
|
+
Object.assign(this._state, data);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
catch (e) {
|
|
1573
|
+
console.warn("[Mulan Security] Failed to load secure state");
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
exports.SecureStore = SecureStore;
|
|
1482
1578
|
|
|
1483
1579
|
|
|
1484
1580
|
/***/ },
|