@trebor/buildhtml 1.0.3 → 1.0.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 (3) hide show
  1. package/README.md +71 -10
  2. package/index.js +16 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -478,7 +478,7 @@ const html = doc.render();
478
478
 
479
479
  ```javascript
480
480
  // Render with JSON embedded in HTML
481
- const html = doc.renderJSON({ encrypt: true });
481
+ const html = doc.renderJSON({ obfuscate: true });
482
482
 
483
483
  // In browser:
484
484
  console.log(window.__SCULPTOR_DATA__);
@@ -489,31 +489,92 @@ console.log(window.__SCULPTOR_DATA__);
489
489
 
490
490
  ## Security
491
491
 
492
- ### XSS Protection
493
-
494
- All text is automatically escaped:
492
+ ### Built-in Security Features
495
493
 
494
+ **Automatic XSS Protection**
496
495
  ```javascript
497
496
  doc.create('div').text('<script>alert("XSS")</script>');
498
497
  // Output: &lt;script&gt;alert("XSS")&lt;/script&gt;
499
498
  ```
500
499
 
501
- ### CSS Injection Protection
502
-
503
- Dangerous CSS characters are sanitized:
504
-
500
+ **CSS Injection Prevention**
505
501
  ```javascript
506
502
  el.css({ background: 'red; } body { display: none; }' });
507
503
  // Sanitized automatically
508
504
  ```
509
505
 
510
- ### CSP Nonce Support
511
-
506
+ **CSP Nonce Support**
512
507
  ```javascript
513
508
  const doc = new Document({ nonce: 'abc123' });
514
509
  // All inline scripts/styles get nonce attribute
515
510
  ```
516
511
 
512
+ ### Enhanced Security Features (NEW!)
513
+
514
+ **Security Headers Middleware**
515
+ ```javascript
516
+ const { securityHeaders } = require('@trebor/buildhtml');
517
+
518
+ app.use(securityHeaders({
519
+ enableCSP: true,
520
+ enableHSTS: true,
521
+ enableXFrameOptions: true,
522
+ cspNonce: (req) => req.nonce
523
+ }));
524
+ ```
525
+
526
+ **Input Sanitization**
527
+ ```javascript
528
+ const { sanitize } = require('@trebor/buildhtml');
529
+
530
+ // Text sanitization
531
+ const safe = sanitize.text('<script>bad</script>');
532
+
533
+ // URL sanitization (blocks javascript:, data:, vbscript:)
534
+ const url = sanitize.url('javascript:alert(1)'); // Returns ''
535
+
536
+ // Email validation
537
+ const email = sanitize.email('user@example.com');
538
+
539
+ // Filename sanitization (prevents path traversal)
540
+ const filename = sanitize.filename('../../../etc/passwd');
541
+ ```
542
+
543
+ **CSRF Protection**
544
+ ```javascript
545
+ const { csrf } = require('@trebor/buildhtml');
546
+
547
+ app.use(csrf.middleware());
548
+
549
+ app.post('/submit', (req, res) => {
550
+ // CSRF token automatically validated
551
+ // Request blocked if invalid
552
+ });
553
+ ```
554
+
555
+ **Rate Limiting**
556
+ ```javascript
557
+ const { createRateLimiter } = require('@trebor/buildhtml');
558
+
559
+ app.use('/api', createRateLimiter({
560
+ windowMs: 15 * 60 * 1000, // 15 minutes
561
+ maxRequests: 100
562
+ }));
563
+ ```
564
+
565
+ **CSP Header Generation**
566
+ ```javascript
567
+ const { generateCSP } = require('@trebor/buildhtml');
568
+
569
+ const csp = generateCSP({
570
+ scriptSrc: ["'self'", "'nonce-abc123'"],
571
+ styleSrc: ["'self'"],
572
+ imgSrc: ["'self'", 'data:', 'https:']
573
+ });
574
+ ```
575
+
576
+ 📖 **See [SECURITY.md](./SECURITY.md) for complete security documentation**
577
+
517
578
  ---
518
579
 
519
580
  ## Advanced Features
package/index.js CHANGED
@@ -688,25 +688,20 @@ class LRUCache {
688
688
  const responseCache = new LRUCache(CONFIG.cacheLimit);
689
689
  const inFlightCache = new Map();
690
690
 
691
- /* ---------------- ENCRYPTION ---------------- */
691
+ /* ---------------- OBFUSCATION (NOT ENCRYPTION) ---------------- */
692
692
  // Simple Base64 obfuscation for JSON data
693
- // NOTE: This is obfuscation, NOT cryptographic security!
694
- function generateEncryptionKey() {
695
- // Not needed for Base64, but kept for API compatibility
696
- return 1;
697
- }
698
-
699
- function encryptString(str, key) {
693
+ // WARNING: This is obfuscation ONLY, NOT cryptographic security!
694
+ // It only reduces payload size and makes data less human-readable.
695
+ function obfuscateString(str) {
700
696
  // Simple Base64 is fast and sufficient for obfuscation.
701
- // Reversing large strings is too expensive for the client.
702
697
  if (typeof Buffer !== 'undefined') {
703
698
  return Buffer.from(str, 'utf-8').toString('base64');
704
699
  }
705
700
  return btoa(unescape(encodeURIComponent(str)));
706
701
  }
707
702
 
708
- function getDecryptScript() {
709
- // Optimized decoder: Just decode Base64 (O(1) complexity relative to string ops)
703
+ function getDeobfuscateScript() {
704
+ // Optimized decoder: Just decode Base64
710
705
  return `var _d=function(e){return decodeURIComponent(escape(atob(e)));};`;
711
706
  }
712
707
 
@@ -962,23 +957,13 @@ class Document {
962
957
  doc.head.globalStyles = json.globalStyles || [];
963
958
  doc.head.classStyles = json.classStyles || {};
964
959
 
965
- // Restore global state
960
+ // Restore global state (data only)
966
961
  doc._globalState = json.globalState || {};
967
962
 
968
- // Restore oncreate callbacks
969
- if (json.oncreateCallbacks && Array.isArray(json.oncreateCallbacks)) {
970
- for (const fnStr of json.oncreateCallbacks) {
971
- try {
972
- // eslint-disable-next-line no-eval
973
- const fn = eval(`(${fnStr})`);
974
- doc._oncreateCallbacks.push(fn);
975
- } catch (err) {
976
- if (CONFIG.mode === 'dev') {
977
- console.error('Failed to restore oncreate callback:', err);
978
- }
979
- }
980
- }
981
- }
963
+ // WARNING: Functions cannot be restored from JSON for security reasons.
964
+ // oncreate callbacks, computed functions, and event handlers are NOT restored.
965
+ // This method is intended for server-side data restoration only.
966
+ // For client-side hydration with functions, use renderJSON() instead.
982
967
 
983
968
  // Restore body
984
969
  const deserializeElement = (node) => {
@@ -1009,35 +994,9 @@ class Document {
1009
994
  el._stateBindings = node.stateBindings;
1010
995
  }
1011
996
 
1012
- if (node.computed) {
1013
- try {
1014
- // eslint-disable-next-line no-eval
1015
- el._computed = eval(`(${node.computed})`);
1016
- } catch (err) {
1017
- if (CONFIG.mode === 'dev') {
1018
- console.error('Failed to restore computed function:', err);
1019
- }
1020
- }
1021
- }
1022
-
1023
- if (node.events && Array.isArray(node.events)) {
1024
- for (const evt of node.events) {
1025
- try {
1026
- // eslint-disable-next-line no-eval
1027
- const fn = eval(`(${evt.fn})`);
1028
- el.events.push({
1029
- event: evt.event,
1030
- id: evt.id,
1031
- targetId: evt.targetId,
1032
- fn: fn
1033
- });
1034
- } catch (err) {
1035
- if (CONFIG.mode === 'dev') {
1036
- console.error('Failed to restore event handler:', err);
1037
- }
1038
- }
1039
- }
1040
- }
997
+ // NOTE: Functions (computed, events, oncreate) are NOT restored from JSON
998
+ // for security reasons. This method is for data restoration only.
999
+ // For full client-side hydration with functions, use renderJSON() instead.
1041
1000
 
1042
1001
  el.hydrate = node.hydrate || false;
1043
1002
 
@@ -1206,8 +1165,8 @@ class Document {
1206
1165
 
1207
1166
  // Inject JSON Data (Fast Decode)
1208
1167
  if (obfuscate) {
1209
- const obfuscated = encryptString(jsonStr, true);
1210
- parts.push(getDecryptScript(), `window.${jsonVarName}=JSON.parse(_d("${obfuscated}"));`);
1168
+ const obfuscated = obfuscateString(jsonStr);
1169
+ parts.push(getDeobfuscateScript(), `window.${jsonVarName}=JSON.parse(_d("${obfuscated}"));`);
1211
1170
  } else {
1212
1171
  parts.push(`window.${jsonVarName}=${jsonStr};`);
1213
1172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebor/buildhtml",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Zero-dependency, ultra-fast HTML builder for server-side rendering (SSR).",
5
5
  "main": "index.js",
6
6
  "files": [