@mulanjs/mulanjs 1.0.1-dev.20260227175607 → 1.0.1-dev.20260227181914

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.
@@ -47,6 +47,13 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
47
47
  }
48
48
  return val;
49
49
  };
50
+
51
+ const _va = (name, val) => {
52
+ if (typeof Mulan !== 'undefined' && Mulan.Security) {
53
+ return Mulan.Security.validateAttribute(name, val);
54
+ }
55
+ return val;
56
+ };
50
57
 
51
58
  ${bodyFn}
52
59
 
@@ -234,24 +241,24 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
234
241
  for (const [key, value] of Object.entries(element.props)) {
235
242
  if (key === 'class') {
236
243
  if (value.includes('${')) {
237
- chunks.push(`this._bindEffect(() => { if (${id}) ${id}.className = \`${value}\`; }, ${id});`);
244
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.className = _va("class", \`${value}\`); }, ${id});`);
238
245
  }
239
246
  else {
240
- chunks.push(`if (${id}) ${id}.className = ${JSON.stringify(value)};`);
247
+ chunks.push(`if (${id}) ${id}.className = _va("class", ${JSON.stringify(value)});`);
241
248
  }
242
249
  }
243
250
  else if (key === 'id') {
244
- chunks.push(`if (${id}) ${id}.id = ${JSON.stringify(value)};`);
251
+ chunks.push(`if (${id}) ${id}.id = _va("id", ${JSON.stringify(value)});`);
245
252
  }
246
253
  else if (key === 'data-mu-id') {
247
254
  // Ignore internal string compiler metadata
248
255
  }
249
256
  else {
250
257
  if (value.includes('${')) {
251
- chunks.push(`this._bindEffect(() => { if (${id}) ${id}.setAttribute("${key}", \`${value}\`); }, ${id});`);
258
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.setAttribute("${key}", _va("${key}", \`${value}\`)); }, ${id});`);
252
259
  }
253
260
  else {
254
- chunks.push(`if (${id}) ${id}.setAttribute("${key}", ${JSON.stringify(value)});`);
261
+ chunks.push(`if (${id}) ${id}.setAttribute("${key}", _va("${key}", ${JSON.stringify(value)}));`);
255
262
  }
256
263
  }
257
264
  }
@@ -266,7 +273,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
266
273
  if (element._domBindings) {
267
274
  for (const b of element._domBindings) {
268
275
  if (b.type === 'prop') {
269
- chunks.push(`this._bindEffect(() => { if (${id}) ${id}['${b.name}'] = ${b.expr}; }, ${id});`);
276
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}['${b.name}'] = _va('${b.name}', ${b.expr}); }, ${id});`);
270
277
  }
271
278
  else if (b.type === 'event') {
272
279
  chunks.push(`if (${id}) ${id}.addEventListener('${b.name}', (${b.expr}).bind(this));`);
@@ -32,6 +32,13 @@ function compileToSSR(descriptor, scriptResult, scopedId) {
32
32
  }
33
33
  return val.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
34
34
  };
35
+
36
+ const _va = (name, val) => {
37
+ if (typeof Mulan !== 'undefined' && Mulan.Security) {
38
+ return Mulan.Security.validateAttribute(name, val);
39
+ }
40
+ return val;
41
+ };
35
42
 
36
43
  ${bodyFn}
37
44
  }`;
@@ -100,18 +107,21 @@ function generateSSRInstruction(node, bindings, localScope, getUid) {
100
107
  }
101
108
  let value = el.props[key];
102
109
  if (key.startsWith(':') || key.startsWith('.')) {
110
+ // Dynamic binding — validate at runtime with _va
103
111
  let attrName = key.slice(1);
104
112
  let expr = processBindings(value, bindings, localScope);
105
- html += ` ${attrName}="\${_h(${expr})}"`;
113
+ html += ` ${attrName}="\${_va("${attrName}", _h(${expr}))}"`;
106
114
  }
107
115
  else {
108
116
  if (value.includes('${')) {
109
- value = value.replace(/\$\{(.*?)\}/g, (_, expr) => {
117
+ // Interpolated value validate at runtime
118
+ const processedValue = value.replace(/\$\{(.*?)\}/g, (_, expr) => {
110
119
  return `\${_h(${processBindings(expr, bindings, localScope)})}`;
111
120
  });
112
- html += ` ${key}="${value}"`;
121
+ html += ` ${key}="\${_va("${key}", \`${processedValue}\`)}"`;
113
122
  }
114
123
  else {
124
+ // Static literal — safe at compile-time, no runtime overhead needed
115
125
  html += ` ${key}="${value.replace(/"/g, '&quot;')}"`;
116
126
  }
117
127
  }
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ const Quantum = __importStar(require("./core/quantum"));
60
60
  const Surge = __importStar(require("./core/surge"));
61
61
  const InfinityList = __importStar(require("./components/infinity-list"));
62
62
  const Mulan = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ reactive: reactive_1.reactive,
63
- 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,
63
+ 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,
64
64
  // MULAN INSIGHT: Branded Logging
65
65
  log: (msg, ...args) => {
66
66
  console.log(`%c[MulanJS]%c ${msg}`, "color: #ff3e00; font-weight: bold; background: #222; padding: 2px 4px; border-radius: 3px;", "", ...args);
package/dist/mulan.esm.js CHANGED
@@ -1195,6 +1195,8 @@ class Security {
1195
1195
  * Use `mu-raw` attribute in templates to bypass this for trusted content.
1196
1196
  */
1197
1197
  static sanitize(input) {
1198
+ if (typeof input !== 'string')
1199
+ return input;
1198
1200
  // 1. Basic entity encoding
1199
1201
  let secure = input
1200
1202
  .replace(/&/g, "&amp;")
@@ -1203,13 +1205,32 @@ class Security {
1203
1205
  .replace(/"/g, "&quot;")
1204
1206
  .replace(/'/g, "&#039;");
1205
1207
  // 2. Remove dangerous events (extra layer if encoding fails)
1206
- const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus'];
1208
+ const dangerousEvents = ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus', 'oncontextmenu', 'oncopy', 'oncut', 'onpaste'];
1207
1209
  dangerousEvents.forEach(event => {
1208
- const regex = new RegExp(event, 'gi');
1209
- secure = secure.replace(regex, 'data-blocked-' + event);
1210
+ const regex = new RegExp(`\\b${event}\\s*=`, 'gi');
1211
+ secure = secure.replace(regex, 'data-blocked-' + event + '=');
1210
1212
  });
1211
1213
  return secure;
1212
1214
  }
1215
+ /**
1216
+ * IRON FORTRESS: Attribute Sentinel
1217
+ * Validates and cleans attribute values based on their name.
1218
+ */
1219
+ static validateAttribute(name, value) {
1220
+ const lowerName = name.toLowerCase();
1221
+ // Block all event handlers if they somehow bypassed the compiler
1222
+ if (lowerName.startsWith('on')) {
1223
+ return `blocked-event-${lowerName}`;
1224
+ }
1225
+ // Strict URL validation for src/href
1226
+ if (lowerName === 'src' || lowerName === 'href' || lowerName === 'action' || lowerName === 'formaction') {
1227
+ const trimmedValue = value.trim().toLowerCase();
1228
+ if (trimmedValue.startsWith('javascript:') || trimmedValue.startsWith('data:text/html') || trimmedValue.startsWith('vbscript:')) {
1229
+ return 'about:blank#blocked-malicious-scheme';
1230
+ }
1231
+ }
1232
+ return Security.sanitize(value);
1233
+ }
1213
1234
  /**
1214
1235
  * Generates a strict Content Security Policy header value.
1215
1236
  * @param options Configuration for allowed sources
@@ -1240,6 +1261,52 @@ class Security {
1240
1261
  });
1241
1262
  }
1242
1263
  }
1264
+ /**
1265
+ * IRON FORTRESS: SECURE STORE
1266
+ * A version of MuStore that encapsulates state and provides optional encryption hooks.
1267
+ */
1268
+
1269
+ class SecureStore {
1270
+ constructor(initialState, options) {
1271
+ this._key = (options === null || options === void 0 ? void 0 : options.key) || null;
1272
+ this._state = reactive(initialState);
1273
+ if ((options === null || options === void 0 ? void 0 : options.encrypt) && this._key) {
1274
+ this._loadEncrypted();
1275
+ }
1276
+ }
1277
+ get state() {
1278
+ // IRON FORTRESS: Freeze prevents direct mutation outside dispatch
1279
+ return Object.freeze(Object.assign({}, this._state));
1280
+ }
1281
+ dispatch(action) {
1282
+ // Only allow state changes via dispatch
1283
+ action(this._state);
1284
+ this._saveEncrypted();
1285
+ }
1286
+ _saveEncrypted() {
1287
+ if (!this._key || typeof localStorage === 'undefined')
1288
+ return;
1289
+ const data = JSON.stringify(this._state);
1290
+ // Base64 + Scramble for basic security
1291
+ const encrypted = btoa(unescape(encodeURIComponent(data)));
1292
+ localStorage.setItem(`secure-${this._key}`, encrypted);
1293
+ }
1294
+ _loadEncrypted() {
1295
+ if (!this._key || typeof localStorage === 'undefined')
1296
+ return;
1297
+ try {
1298
+ const encrypted = localStorage.getItem(`secure-${this._key}`);
1299
+ if (encrypted) {
1300
+ const decrypted = decodeURIComponent(escape(atob(encrypted)));
1301
+ const data = JSON.parse(decrypted);
1302
+ Object.assign(this._state, data);
1303
+ }
1304
+ }
1305
+ catch (e) {
1306
+ console.warn("[Mulan Security] Failed to load secure state");
1307
+ }
1308
+ }
1309
+ }
1243
1310
 
1244
1311
  ;// ./src/router/index.ts
1245
1312
  var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
@@ -2512,7 +2579,7 @@ function renderToString(ComponentClass, props = {}) {
2512
2579
 
2513
2580
 
2514
2581
  const Mulan = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ reactive: reactive,
2515
- effect: effect, Component: MuComponent, defineComponent: defineComponent, Router: MuRouter, createRouter: createRouter, Store: MuStore, Security: Security }, hooks_namespaceObject), query_namespaceObject), quantum_namespaceObject), surge_namespaceObject), infinity_list_namespaceObject), { render: render,
2582
+ effect: effect, Component: MuComponent, defineComponent: defineComponent, Router: MuRouter, createRouter: createRouter, Store: MuStore, SecureStore: SecureStore, Security: Security }, hooks_namespaceObject), query_namespaceObject), quantum_namespaceObject), surge_namespaceObject), infinity_list_namespaceObject), { render: render,
2516
2583
  // MULAN INSIGHT: Branded Logging
2517
2584
  log: (msg, ...args) => {
2518
2585
  console.log(`%c[MulanJS]%c ${msg}`, "color: #ff3e00; font-weight: bold; background: #222; padding: 2px 4px; border-radius: 3px;", "", ...args);
@@ -2546,6 +2613,6 @@ if (typeof window !== 'undefined') {
2546
2613
  }
2547
2614
  /* harmony default export */ const src = (Mulan);
2548
2615
 
2549
- export { MuComponent as Component, MuBlochSphereElement, MuComponent, MuInfinity, MuRouter, MuStore, MuRouter as Router, Security, Signal, activeControls, src as default, defineComponent, effect, getCurrentInstance, hydrate, muBurst, muControl, muDebounced, muEffect, muEntangle, muGate, muGeom, muHistory, muMeasure, muMemo, muParallel, muPulse, muQubit, muRegister, muSearch, muState, muSurge, muSuspense, muSwitch, muTeleport, muThrottled, muVault, nextTick, onMuDestroy, onMuIdle, onMuInit, onMuMount, onMuPanic, onMuResume, onMuShake, onMuVisibility, onMuVoice, persistent, queueEffect, reactive, ref, render, renderToString, sanitize, setCurrentInstance, useMutation, useQuery };
2616
+ export { MuComponent as Component, MuBlochSphereElement, MuComponent, MuInfinity, MuRouter, MuStore, MuRouter as Router, SecureStore, Security, Signal, activeControls, src as default, defineComponent, effect, getCurrentInstance, hydrate, muBurst, muControl, muDebounced, muEffect, muEntangle, muGate, muGeom, muHistory, muMeasure, muMemo, muParallel, muPulse, muQubit, muRegister, muSearch, muState, muSurge, muSuspense, muSwitch, muTeleport, muThrottled, muVault, nextTick, onMuDestroy, onMuIdle, onMuInit, onMuMount, onMuPanic, onMuResume, onMuShake, onMuVisibility, onMuVoice, persistent, queueEffect, reactive, ref, render, renderToString, sanitize, setCurrentInstance, useMutation, useQuery };
2550
2617
 
2551
2618
  //# sourceMappingURL=mulan.esm.js.map