pulse-js-framework 1.11.3 → 1.11.4

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.
Files changed (48) hide show
  1. package/cli/analyze.js +21 -8
  2. package/cli/build.js +83 -56
  3. package/cli/dev.js +108 -94
  4. package/cli/docs-test.js +52 -33
  5. package/cli/index.js +81 -51
  6. package/cli/mobile.js +92 -40
  7. package/cli/release.js +64 -46
  8. package/cli/scaffold.js +14 -13
  9. package/compiler/lexer.js +55 -54
  10. package/compiler/parser/core.js +1 -0
  11. package/compiler/parser/state.js +6 -12
  12. package/compiler/parser/style.js +17 -20
  13. package/compiler/parser/view.js +1 -3
  14. package/compiler/preprocessor.js +124 -262
  15. package/compiler/sourcemap.js +10 -4
  16. package/compiler/transformer/expressions.js +122 -106
  17. package/compiler/transformer/index.js +2 -4
  18. package/compiler/transformer/style.js +74 -7
  19. package/compiler/transformer/view.js +86 -36
  20. package/loader/esbuild-plugin-server-components.js +209 -0
  21. package/loader/esbuild-plugin.js +41 -93
  22. package/loader/parcel-plugin.js +37 -97
  23. package/loader/rollup-plugin-server-components.js +30 -169
  24. package/loader/rollup-plugin.js +27 -78
  25. package/loader/shared.js +362 -0
  26. package/loader/swc-plugin.js +65 -82
  27. package/loader/vite-plugin-server-components.js +30 -171
  28. package/loader/vite-plugin.js +25 -10
  29. package/loader/webpack-loader-server-components.js +21 -134
  30. package/loader/webpack-loader.js +25 -80
  31. package/package.json +52 -12
  32. package/runtime/dom-selector.js +2 -1
  33. package/runtime/form.js +4 -3
  34. package/runtime/http.js +6 -1
  35. package/runtime/logger.js +44 -24
  36. package/runtime/router/utils.js +14 -7
  37. package/runtime/security.js +13 -1
  38. package/runtime/server-components/actions-server.js +23 -19
  39. package/runtime/server-components/error-sanitizer.js +18 -18
  40. package/runtime/server-components/security.js +41 -24
  41. package/runtime/ssr-preload.js +5 -3
  42. package/runtime/testing.js +759 -0
  43. package/runtime/utils.js +3 -2
  44. package/server/utils.js +15 -9
  45. package/sw/index.js +2 -0
  46. package/types/loaders.d.ts +1043 -0
  47. package/compiler/parser/_extract.js +0 -393
  48. package/loader/README.md +0 -509
@@ -26,24 +26,24 @@ const log = loggers.dom;
26
26
  * Covers secrets, connection strings, file paths, env vars.
27
27
  */
28
28
  const SENSITIVE_MESSAGE_PATTERNS = [
29
- // Connection strings
30
- /postgres:\/\/[^@]+@[^/]+/gi, // PostgreSQL
31
- /mongodb(\+srv)?:\/\/[^@]+@[^/]+/gi, // MongoDB
32
- /mysql:\/\/[^@]+@[^/]+/gi, // MySQL
33
- /redis:\/\/[^@]+@[^/]+/gi, // Redis
29
+ // Connection strings - bound quantifiers to prevent ReDoS on strings without '@'
30
+ /postgres:\/\/[^@\s]{1,256}@[^/\s]{1,256}/gi, // PostgreSQL
31
+ /mongodb(?:\+srv)?:\/\/[^@\s]{1,256}@[^/\s]{1,256}/gi, // MongoDB
32
+ /mysql:\/\/[^@\s]{1,256}@[^/\s]{1,256}/gi, // MySQL
33
+ /redis:\/\/[^@\s]{1,256}@[^/\s]{1,256}/gi, // Redis
34
34
 
35
- // API keys and tokens (common formats)
36
- /[a-zA-Z0-9_-]{20,}/g, // Generic long tokens
35
+ // API keys and tokens (common formats) - bounded
36
+ /[a-zA-Z0-9_-]{20,200}/g, // Generic long tokens
37
37
 
38
- // Environment variable values (KEY=value patterns)
39
- /\b[A-Z_]+=[^\s]+/g,
38
+ // Environment variable values (KEY=value patterns) - bounded
39
+ /\b[A-Z_]{1,100}=[^\s]{1,500}/g,
40
40
 
41
- // File paths (Unix and Windows)
42
- /\/[a-zA-Z0-9_\-./]+\.(js|ts|json|env|config)/gi,
43
- /[A-Z]:\\[^"\s]+\.(js|ts|json|env|config)/gi,
41
+ // File paths (Unix and Windows) - non-overlapping segments, bounded
42
+ /\/[a-zA-Z0-9_-]{1,100}(?:\/[a-zA-Z0-9_-]{1,100}){0,20}\.[a-z]{1,6}/gi,
43
+ /[A-Z]:\\[a-zA-Z0-9_-]{1,100}(?:\\[a-zA-Z0-9_-]{1,100}){0,20}\.[a-z]{1,6}/gi,
44
44
 
45
- // Email addresses
46
- /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
45
+ // Email addresses - bounded segments to prevent ReDoS
46
+ /\b[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,253}\.[A-Za-z]{2,20}\b/g,
47
47
 
48
48
  // IP addresses
49
49
  /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g
@@ -152,11 +152,11 @@ export function sanitizeStackTrace(stack) {
152
152
  // First, strip absolute paths to relative paths
153
153
  let sanitizedLine = line;
154
154
 
155
- // Replace absolute paths with relative (Unix)
156
- sanitizedLine = sanitizedLine.replace(/\/[a-zA-Z0-9_\-./]+\/([a-zA-Z0-9_\-./]+\.(js|ts))/g, '$1');
155
+ // Replace absolute paths with relative (Unix) - bounded segments to avoid ReDoS
156
+ sanitizedLine = sanitizedLine.replace(/\/[a-zA-Z0-9_-]{1,100}(?:\/[a-zA-Z0-9_-]{1,100}){0,30}\/([a-zA-Z0-9_.-]{1,100}\.(js|ts))/g, '$1');
157
157
 
158
- // Replace absolute paths with relative (Windows)
159
- sanitizedLine = sanitizedLine.replace(/[A-Z]:\\[^"]+\\([a-zA-Z0-9_\-./\\]+\.(js|ts))/g, '$1');
158
+ // Replace absolute paths with relative (Windows) - bounded segments to avoid ReDoS
159
+ sanitizedLine = sanitizedLine.replace(/[A-Z]:\\[a-zA-Z0-9_-]{1,100}(?:\\[a-zA-Z0-9_-]{1,100}){0,30}\\([a-zA-Z0-9_.-]{1,100}\.(js|ts))/g, '$1');
160
160
 
161
161
  // NOW filter out internal/sensitive paths (after path conversion)
162
162
  let shouldFilter = false;
@@ -66,10 +66,15 @@ const SECRET_PATTERNS = [
66
66
  */
67
67
  const XSS_PATTERNS = {
68
68
  scriptTag: /<script[\s>]/i,
69
- eventHandler: /<[^>]+on\w+\s*=/i,
69
+ // Use non-overlapping groups: match tag name chars then specifically 'on' + handler name
70
+ // Avoids ReDoS by limiting [^>] to {1,1000} and using non-greedy match
71
+ eventHandler: /<[^>]{1,1000}?\bon\w+\s*=/i,
70
72
  javascriptProtocol: /javascript:/i,
71
73
  vbscriptProtocol: /vbscript:/i,
72
- dataHtmlProtocol: /data:text\/html/i
74
+ // Match dangerous data: URIs - block data: with executable MIME types and SVG
75
+ dataProtocol: /data:\s*(?:text\/(?:html|javascript|xml)|image\/svg\+xml|application\/(?:javascript|x-javascript|ecmascript|xhtml\+xml|xml))/i,
76
+ // Catch-all for data: URIs not in allowlist (used in URL validation)
77
+ dataAny: /^\s*data\s*:/i
73
78
  };
74
79
 
75
80
  // ============================================================================
@@ -221,39 +226,51 @@ export function sanitizePropsForXSS(props, componentId) {
221
226
  let sanitized = value;
222
227
  let modified = false;
223
228
 
224
- // Check for script tags
229
+ // Check for script tags - use loop to handle incomplete multi-character sanitization
225
230
  if (XSS_PATTERNS.scriptTag.test(sanitized)) {
226
231
  log.warn(`PSC: Script tag detected in prop '${path}' for '${componentId}'`);
227
- sanitized = sanitized.replace(/<script[\s>]/gi, '&lt;script');
232
+ // Loop until all <script patterns are removed (handles nested/overlapping)
233
+ let prev;
234
+ do {
235
+ prev = sanitized;
236
+ sanitized = sanitized.replace(/<script[\s>]/gi, '&lt;script ');
237
+ } while (sanitized !== prev);
228
238
  modified = true;
229
239
  }
230
240
 
231
241
  // Check for event handlers in HTML-like content
232
242
  if (XSS_PATTERNS.eventHandler.test(sanitized)) {
233
243
  log.warn(`PSC: Event handler detected in prop '${path}' for '${componentId}'`);
234
- sanitized = sanitized.replace(/on\w+\s*=/gi, 'data-blocked-event=');
244
+ sanitized = sanitized.replace(/\bon\w{1,50}\s*=/gi, 'data-blocked-event=');
235
245
  modified = true;
236
246
  }
237
247
 
238
- // Check for javascript: protocol
239
- if (XSS_PATTERNS.javascriptProtocol.test(sanitized)) {
240
- log.warn(`PSC: javascript: protocol detected in prop '${path}' for '${componentId}'`);
241
- sanitized = sanitized.replace(/javascript:/gi, 'blocked:');
242
- modified = true;
243
- }
244
-
245
- // Check for vbscript: protocol
246
- if (XSS_PATTERNS.vbscriptProtocol.test(sanitized)) {
247
- log.warn(`PSC: vbscript: protocol detected in prop '${path}' for '${componentId}'`);
248
- sanitized = sanitized.replace(/vbscript:/gi, 'blocked:');
249
- modified = true;
250
- }
251
-
252
- // Check for data:text/html (can execute scripts)
253
- if (XSS_PATTERNS.dataHtmlProtocol.test(sanitized)) {
254
- log.warn(`PSC: data:text/html protocol detected in prop '${path}' for '${componentId}'`);
255
- sanitized = sanitized.replace(/data:text\/html/gi, 'data:text/plain');
256
- modified = true;
248
+ // Check for dangerous URL protocols: javascript:, vbscript:, data:
249
+ // All three are checked together to ensure complete URL scheme validation.
250
+ {
251
+ const DANGEROUS_PROTOCOLS = /(?:javascript|vbscript|data)\s*:/i;
252
+ if (DANGEROUS_PROTOCOLS.test(sanitized)) {
253
+ // Block javascript: and vbscript: unconditionally
254
+ if (/javascript\s*:/i.test(sanitized)) {
255
+ log.warn(`PSC: javascript: protocol detected in prop '${path}' for '${componentId}'`);
256
+ sanitized = sanitized.replace(/javascript\s*:/gi, 'blocked:');
257
+ modified = true;
258
+ }
259
+ if (/vbscript\s*:/i.test(sanitized)) {
260
+ log.warn(`PSC: vbscript: protocol detected in prop '${path}' for '${componentId}'`);
261
+ sanitized = sanitized.replace(/vbscript\s*:/gi, 'blocked:');
262
+ modified = true;
263
+ }
264
+ // Block data: URIs except safe image/text types - neutralize to data:text/plain
265
+ if (/data\s*:/i.test(sanitized)) {
266
+ const SAFE_DATA = /data:\s*(?:text\/plain|image\/(?:png|jpe?g|gif|webp|bmp|ico))(?:[;,]|$)/i;
267
+ if (!SAFE_DATA.test(sanitized)) {
268
+ log.warn(`PSC: blocked data: URI in prop '${path}' for '${componentId}'`);
269
+ sanitized = sanitized.replace(/data\s*:[^\s)"']{0,500}/gi, 'data:text/plain');
270
+ modified = true;
271
+ }
272
+ }
273
+ }
257
274
  }
258
275
 
259
276
  return sanitized;
@@ -183,9 +183,11 @@ export function hintsToHTML(hints) {
183
183
  if (!hints || hints.length === 0) return '';
184
184
 
185
185
  return hints.map(hint => {
186
- const parts = [`rel="${hint.rel}"`, `href="${escapeHTML(hint.href)}"`];
187
- if (hint.as) parts.push(`as="${hint.as}"`);
188
- if (hint.type) parts.push(`type="${hint.type}"`);
186
+ // Escape all attribute values for safe use in HTML (& < > and double quotes)
187
+ const escapeAttr = (v) => escapeHTML(v).replace(/"/g, '&quot;');
188
+ const parts = [`rel="${escapeAttr(hint.rel)}"`, `href="${escapeAttr(hint.href)}"`];
189
+ if (hint.as) parts.push(`as="${escapeAttr(hint.as)}"`);
190
+ if (hint.type) parts.push(`type="${escapeAttr(hint.type)}"`);
189
191
  if (hint.crossorigin) parts.push('crossorigin');
190
192
  return `<link ${parts.join(' ')}>`;
191
193
  }).join('\n');