@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.
- package/README.md +71 -10
- package/index.js +16 -57
- 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({
|
|
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
|
-
###
|
|
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: <script>alert("XSS")</script>
|
|
499
498
|
```
|
|
500
499
|
|
|
501
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
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
|
|
709
|
-
// Optimized decoder: Just decode Base64
|
|
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
|
-
//
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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 =
|
|
1210
|
-
parts.push(
|
|
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
|
}
|