nodebb-plugin-pdf-secure 1.2.0 → 1.2.1

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.
@@ -5,6 +5,28 @@ const pdfHandler = require('./pdf-handler');
5
5
 
6
6
  const Controllers = module.exports;
7
7
 
8
+ // XOR key for partial encryption (rotates through this pattern)
9
+ const XOR_KEY = [0x5A, 0x3C, 0x7E, 0x1F, 0x9D, 0xB2, 0x4A, 0xE8];
10
+
11
+ // Partial XOR - encrypts first 10KB and every 50th byte after that
12
+ function partialXorEncode(buffer) {
13
+ const data = Buffer.from(buffer);
14
+ const keyLen = XOR_KEY.length;
15
+
16
+ // Encrypt first 10KB fully
17
+ const fullEncryptLen = Math.min(10240, data.length);
18
+ for (let i = 0; i < fullEncryptLen; i++) {
19
+ data[i] = data[i] ^ XOR_KEY[i % keyLen];
20
+ }
21
+
22
+ // Encrypt every 50th byte after that
23
+ for (let i = fullEncryptLen; i < data.length; i += 50) {
24
+ data[i] = data[i] ^ XOR_KEY[i % keyLen];
25
+ }
26
+
27
+ return data;
28
+ }
29
+
8
30
  Controllers.renderAdminPage = function (req, res) {
9
31
  res.render('admin/plugins/pdf-secure', {
10
32
  title: 'PDF Secure Viewer',
@@ -34,14 +56,19 @@ Controllers.servePdfBinary = async function (req, res) {
34
56
  pdfBuffer = await pdfHandler.getSinglePagePdf(data.file);
35
57
  }
36
58
 
59
+ // Apply partial XOR encryption
60
+ const encodedBuffer = partialXorEncode(pdfBuffer);
61
+
37
62
  res.set({
38
63
  'Content-Type': 'application/octet-stream',
39
64
  'Cache-Control': 'no-store, no-cache, must-revalidate, private',
40
65
  'X-Content-Type-Options': 'nosniff',
41
66
  'Content-Disposition': 'inline',
67
+ // Send XOR key as header for client decoding
68
+ 'X-PDF-Key': Buffer.from(XOR_KEY).toString('base64'),
42
69
  });
43
70
 
44
- return res.send(pdfBuffer);
71
+ return res.send(encodedBuffer);
45
72
  } catch (err) {
46
73
  if (err.message === 'File not found') {
47
74
  return res.status(404).json({ error: 'PDF not found' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Secure PDF viewer plugin for NodeBB - prevents downloading, enables canvas-only rendering with Premium group support",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -1420,6 +1420,26 @@
1420
1420
  generateThumbnails();
1421
1421
  }
1422
1422
 
1423
+ // Partial XOR decoder - must match backend encoding
1424
+ function partialXorDecode(encodedData, keyBase64) {
1425
+ const key = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
1426
+ const data = new Uint8Array(encodedData);
1427
+ const keyLen = key.length;
1428
+
1429
+ // Decrypt first 10KB fully
1430
+ const fullDecryptLen = Math.min(10240, data.length);
1431
+ for (let i = 0; i < fullDecryptLen; i++) {
1432
+ data[i] = data[i] ^ key[i % keyLen];
1433
+ }
1434
+
1435
+ // Decrypt every 50th byte after that
1436
+ for (let i = fullDecryptLen; i < data.length; i += 50) {
1437
+ data[i] = data[i] ^ key[i % keyLen];
1438
+ }
1439
+
1440
+ return data.buffer;
1441
+ }
1442
+
1423
1443
  // Auto-load PDF if config is present (injected by NodeBB plugin)
1424
1444
  async function autoLoadSecurePDF() {
1425
1445
  if (!window.PDF_SECURE_CONFIG || !window.PDF_SECURE_CONFIG.filename) {
@@ -1457,7 +1477,7 @@
1457
1477
  const nonceData = await nonceRes.json();
1458
1478
  const nonce = nonceData.response.nonce;
1459
1479
 
1460
- // Step 2: Fetch PDF binary
1480
+ // Step 2: Fetch encrypted PDF binary
1461
1481
  const pdfUrl = config.relativePath + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
1462
1482
  const pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
1463
1483
 
@@ -1465,12 +1485,33 @@
1465
1485
  throw new Error('PDF yüklenemedi (' + pdfRes.status + ')');
1466
1486
  }
1467
1487
 
1468
- const pdfBuffer = await pdfRes.arrayBuffer();
1469
- console.log('[PDF-Secure] PDF loaded:', pdfBuffer.byteLength, 'bytes');
1488
+ // Get XOR key from header
1489
+ const xorKey = pdfRes.headers.get('X-PDF-Key');
1490
+ const encodedBuffer = await pdfRes.arrayBuffer();
1470
1491
 
1471
- // Load into viewer
1492
+ console.log('[PDF-Secure] Encrypted data received:', encodedBuffer.byteLength, 'bytes');
1493
+
1494
+ // Step 3: Decode XOR encrypted data
1495
+ let pdfBuffer;
1496
+ if (xorKey) {
1497
+ console.log('[PDF-Secure] Decoding XOR encrypted data...');
1498
+ pdfBuffer = partialXorDecode(encodedBuffer, xorKey);
1499
+ } else {
1500
+ // Fallback for backward compatibility
1501
+ pdfBuffer = encodedBuffer;
1502
+ }
1503
+
1504
+ console.log('[PDF-Secure] PDF decoded successfully');
1505
+
1506
+ // Step 4: Load into viewer
1472
1507
  await loadPDFFromBuffer(pdfBuffer);
1473
1508
 
1509
+ // Step 5: Security - clear references to prevent extraction
1510
+ // Nullify the buffer after loading
1511
+ pdfBuffer = null;
1512
+
1513
+ console.log('[PDF-Secure] Buffer references cleared');
1514
+
1474
1515
  } catch (err) {
1475
1516
  console.error('[PDF-Secure] Auto-load error:', err);
1476
1517
  if (dropzone) {