nodebb-plugin-pdf-secure 1.1.6 → 1.2.0

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.
@@ -2,7 +2,10 @@
2
2
  "permissions": {
3
3
  "allow": [
4
4
  "WebFetch(domain:raw.githubusercontent.com)",
5
- "WebFetch(domain:github.com)"
5
+ "WebFetch(domain:github.com)",
6
+ "Bash(dir:*)",
7
+ "Bash(npm pack:*)",
8
+ "Bash(tar:*)"
6
9
  ]
7
10
  }
8
11
  }
@@ -34,38 +34,14 @@ Controllers.servePdfBinary = async function (req, res) {
34
34
  pdfBuffer = await pdfHandler.getSinglePagePdf(data.file);
35
35
  }
36
36
 
37
- // PARTIAL XOR Encryption: Sadece ilk ve son 8KB'ı şifrele
38
- // Bu, PDF'yi açılamaz hale getirir ama performans etkisi minimal
39
- const encryptionKey = Buffer.from(nonce.slice(0, 8));
40
- const encryptedBuffer = Buffer.from(pdfBuffer); // Copy
41
- const chunkSize = 8192; // 8KB
42
-
43
- // İlk 8KB'ı şifrele (PDF header - CRITICAL)
44
- const headerEnd = Math.min(chunkSize, pdfBuffer.length);
45
- for (let i = 0; i < headerEnd; i++) {
46
- encryptedBuffer[i] = pdfBuffer[i] ^ encryptionKey[i % encryptionKey.length];
47
- }
48
-
49
- // Son 8KB'ı şifrele (PDF xref table - CRITICAL)
50
- const footerStart = Math.max(0, pdfBuffer.length - chunkSize);
51
- for (let i = footerStart; i < pdfBuffer.length; i++) {
52
- encryptedBuffer[i] = pdfBuffer[i] ^ encryptionKey[i % encryptionKey.length];
53
- }
54
-
55
37
  res.set({
56
38
  'Content-Type': 'application/octet-stream',
57
- 'Cache-Control': 'no-store, no-cache, must-revalidate, private, max-age=0',
58
- 'Pragma': 'no-cache',
59
- 'Expires': '0',
39
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, private',
60
40
  'X-Content-Type-Options': 'nosniff',
61
- 'X-Download-Options': 'noopen',
62
- 'X-Frame-Options': 'SAMEORIGIN',
63
- 'Content-Security-Policy': "default-src 'none'",
64
41
  'Content-Disposition': 'inline',
65
- 'X-PDF-Encrypted': '1', // İşaretleyici
66
42
  });
67
43
 
68
- return res.send(encryptedBuffer);
44
+ return res.send(pdfBuffer);
69
45
  } catch (err) {
70
46
  if (err.message === 'File not found') {
71
47
  return res.status(404).json({ error: 'PDF not found' });
package/library.js CHANGED
@@ -42,11 +42,17 @@ plugin.init = async (params) => {
42
42
  return res.status(400).send('Invalid file');
43
43
  }
44
44
 
45
- // Serve the viewer template with security headers
45
+ // Serve the viewer template with comprehensive security headers
46
46
  res.set({
47
47
  'X-Frame-Options': 'SAMEORIGIN',
48
48
  'X-Content-Type-Options': 'nosniff',
49
- 'Cache-Control': 'no-store, no-cache, must-revalidate, private',
49
+ 'X-XSS-Protection': '1; mode=block',
50
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, private, max-age=0',
51
+ 'Pragma': 'no-cache',
52
+ 'Expires': '0',
53
+ 'Referrer-Policy': 'no-referrer',
54
+ 'Permissions-Policy': 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()',
55
+ 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data: blob:; connect-src 'self'; frame-ancestors 'self'",
50
56
  });
51
57
 
52
58
  // Read and send the viewer HTML
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.1.6",
3
+ "version": "1.2.0",
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": {
@@ -32,7 +32,6 @@ function interceptPdfLinks() {
32
32
 
33
33
  var container = document.createElement('div');
34
34
  container.className = 'pdf-secure-inline';
35
- container.setAttribute('tabindex', '0'); // Focus için gerekli
36
35
  container.innerHTML =
37
36
  '<div class="pdf-secure-inline-header">' +
38
37
  '<i class="fa fa-file-pdf-o"></i> ' +
@@ -41,10 +40,7 @@ function interceptPdfLinks() {
41
40
  '<div class="pdf-secure-inline-body">' +
42
41
  '<div class="pdf-secure-loading">Loading PDF...</div>' +
43
42
  '<div class="pdf-secure-error"></div>' +
44
- '<div class="pdf-secure-canvas-wrapper">' +
45
- '<canvas class="pdf-secure-canvas"></canvas>' +
46
- '<div class="pdf-secure-overlay"></div>' +
47
- '</div>' +
43
+ '<canvas class="pdf-secure-canvas"></canvas>' +
48
44
  '</div>' +
49
45
  '<div class="pdf-secure-inline-footer">' +
50
46
  '<button class="pdf-secure-prev" disabled>&#8249; Prev</button>' +
@@ -110,29 +106,8 @@ async function loadPdf(container, filename) {
110
106
  return;
111
107
  }
112
108
 
113
- var encryptedArrayBuffer = await pdfRes.arrayBuffer();
114
- console.log('[PDF-Secure][Viewer] Step 2 - Encrypted data loaded:', encryptedArrayBuffer.byteLength, 'bytes');
115
-
116
- // PARTIAL XOR Decryption: Sadece ilk ve son 8KB'ı decrypt et
117
- var encryptedBytes = new Uint8Array(encryptedArrayBuffer);
118
- var encryptionKey = new TextEncoder().encode(nonce.slice(0, 8));
119
- var decryptedBytes = new Uint8Array(encryptedBytes); // Copy
120
- var chunkSize = 8192; // 8KB
121
-
122
- // İlk 8KB'ı decrypt et (PDF header)
123
- var headerEnd = Math.min(chunkSize, encryptedBytes.length);
124
- for (var i = 0; i < headerEnd; i++) {
125
- decryptedBytes[i] = encryptedBytes[i] ^ encryptionKey[i % encryptionKey.length];
126
- }
127
-
128
- // Son 8KB'ı decrypt et (PDF xref table)
129
- var footerStart = Math.max(0, encryptedBytes.length - chunkSize);
130
- for (var i = footerStart; i < encryptedBytes.length; i++) {
131
- decryptedBytes[i] = encryptedBytes[i] ^ encryptionKey[i % encryptionKey.length];
132
- }
133
-
134
- var pdfArrayBuffer = decryptedBytes.buffer;
135
- console.log('[PDF-Secure][Viewer] Step 2 - Decrypted PDF:', pdfArrayBuffer.byteLength, 'bytes');
109
+ var pdfArrayBuffer = await pdfRes.arrayBuffer();
110
+ console.log('[PDF-Secure][Viewer] Step 2 - PDF loaded:', pdfArrayBuffer.byteLength, 'bytes');
136
111
 
137
112
  // Step 3: Render PDF
138
113
  console.log('[PDF-Secure][Viewer] Step 3 - Rendering...');
@@ -147,41 +122,6 @@ async function loadPdf(container, filename) {
147
122
  container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
148
123
  container.addEventListener('dragstart', function (e) { e.preventDefault(); });
149
124
  container.addEventListener('selectstart', function (e) { e.preventDefault(); });
150
- container.addEventListener('copy', function (e) { e.preventDefault(); });
151
-
152
- // Keyboard shortcuts engelleme (Ctrl+S, Ctrl+P, Ctrl+C, Print Screen)
153
- container.addEventListener('keydown', function (e) {
154
- // Ctrl/Cmd + S (Save)
155
- if ((e.ctrlKey || e.metaKey) && e.key === 's') {
156
- e.preventDefault();
157
- console.log('[PDF-Secure] Save blocked');
158
- return false;
159
- }
160
- // Ctrl/Cmd + P (Print)
161
- if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
162
- e.preventDefault();
163
- console.log('[PDF-Secure] Print blocked');
164
- return false;
165
- }
166
- // Ctrl/Cmd + C (Copy)
167
- if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
168
- e.preventDefault();
169
- console.log('[PDF-Secure] Copy blocked');
170
- return false;
171
- }
172
- // Print Screen
173
- if (e.key === 'PrintScreen') {
174
- e.preventDefault();
175
- console.log('[PDF-Secure] PrintScreen blocked');
176
- return false;
177
- }
178
- // F12 (DevTools)
179
- if (e.key === 'F12') {
180
- e.preventDefault();
181
- console.log('[PDF-Secure] F12 blocked');
182
- return false;
183
- }
184
- });
185
125
 
186
126
  var ctx = canvas.getContext('2d');
187
127
  var currentPage = 1;
package/static/style.less CHANGED
@@ -136,142 +136,7 @@
136
136
 
137
137
  /* Anti-print */
138
138
  @media print {
139
- .pdf-secure-embed,
140
- .pdf-secure-inline {
139
+ .pdf-secure-embed {
141
140
  display: none !important;
142
141
  }
143
142
  }
144
-
145
- /* Inline PDF Viewer Styles */
146
- .pdf-secure-inline {
147
- margin: 20px 0;
148
- border: 1px solid #ddd;
149
- border-radius: 8px;
150
- overflow: hidden;
151
- background: #f5f5f5;
152
- user-select: none;
153
- -webkit-user-select: none;
154
- -moz-user-select: none;
155
- -ms-user-select: none;
156
- }
157
-
158
- .pdf-secure-inline-header {
159
- display: flex;
160
- align-items: center;
161
- gap: 8px;
162
- padding: 12px 16px;
163
- background: linear-gradient(to bottom, #fff, #f8f8f8);
164
- border-bottom: 1px solid #ddd;
165
- font-weight: 500;
166
- color: #333;
167
- }
168
-
169
- .pdf-secure-inline-header i {
170
- color: #e81224;
171
- font-size: 18px;
172
- }
173
-
174
- .pdf-secure-filename {
175
- flex: 1;
176
- overflow: hidden;
177
- text-overflow: ellipsis;
178
- white-space: nowrap;
179
- }
180
-
181
- .pdf-secure-inline-body {
182
- position: relative;
183
- min-height: 400px;
184
- background: #fff;
185
- display: flex;
186
- align-items: center;
187
- justify-content: center;
188
- }
189
-
190
- .pdf-secure-loading {
191
- position: absolute;
192
- top: 50%;
193
- left: 50%;
194
- transform: translate(-50%, -50%);
195
- color: #666;
196
- font-size: 14px;
197
- }
198
-
199
- .pdf-secure-error {
200
- display: none;
201
- position: absolute;
202
- top: 50%;
203
- left: 50%;
204
- transform: translate(-50%, -50%);
205
- color: #e81224;
206
- font-size: 14px;
207
- text-align: center;
208
- padding: 20px;
209
- }
210
-
211
- .pdf-secure-canvas-wrapper {
212
- position: relative;
213
- display: inline-block;
214
- /* Canvas wrapper'ı güvenlik için kapsayıcı */
215
- }
216
-
217
- .pdf-secure-canvas {
218
- display: none;
219
- max-width: 100%;
220
- height: auto;
221
- /* Canvas üzerinde sağ tık ve sürüklemeyi engelle */
222
- pointer-events: auto;
223
- }
224
-
225
- .pdf-secure-overlay {
226
- position: absolute;
227
- top: 0;
228
- left: 0;
229
- right: 0;
230
- bottom: 0;
231
- background: transparent;
232
- /* Görünmez koruma katmanı - tüm mouse eventlerini yakalar */
233
- pointer-events: auto;
234
- cursor: default;
235
- z-index: 1;
236
- }
237
-
238
- .pdf-secure-inline-footer {
239
- display: none;
240
- align-items: center;
241
- justify-content: center;
242
- gap: 16px;
243
- padding: 12px;
244
- background: #f8f8f8;
245
- border-top: 1px solid #ddd;
246
- }
247
-
248
- .pdf-secure-prev,
249
- .pdf-secure-next {
250
- padding: 6px 12px;
251
- background: #fff;
252
- border: 1px solid #ddd;
253
- border-radius: 4px;
254
- cursor: pointer;
255
- font-size: 14px;
256
- color: #333;
257
- transition: all 0.2s;
258
- }
259
-
260
- .pdf-secure-prev:hover:not(:disabled),
261
- .pdf-secure-next:hover:not(:disabled) {
262
- background: #f0f0f0;
263
- border-color: #999;
264
- }
265
-
266
- .pdf-secure-prev:disabled,
267
- .pdf-secure-next:disabled {
268
- opacity: 0.4;
269
- cursor: not-allowed;
270
- }
271
-
272
- .pdf-secure-page-info {
273
- font-size: 14px;
274
- color: #666;
275
- min-width: 60px;
276
- text-align: center;
277
- }
@@ -3047,7 +3047,178 @@
3047
3047
 
3048
3048
  console.log('PDF Viewer Ready');
3049
3049
  console.log('Keyboard Shortcuts: H=Highlight, P=Pen, E=Eraser, T=Text, R=Shapes, S=Sidebar, M=ReadingMode, Arrows=Navigate');
3050
+
3051
+ // ==========================================
3052
+ // SECURITY FEATURES
3053
+ // ==========================================
3054
+
3055
+ (function initSecurityFeatures() {
3056
+ console.log('[Security] Initializing protection features...');
3057
+
3058
+ // 1. Block dangerous keyboard shortcuts
3059
+ document.addEventListener('keydown', function (e) {
3060
+ // Ctrl+S (Save)
3061
+ if (e.ctrlKey && e.key === 's') {
3062
+ e.preventDefault();
3063
+ console.log('[Security] Ctrl+S blocked');
3064
+ return false;
3065
+ }
3066
+ // Ctrl+P (Print)
3067
+ if (e.ctrlKey && e.key === 'p') {
3068
+ e.preventDefault();
3069
+ console.log('[Security] Ctrl+P blocked');
3070
+ return false;
3071
+ }
3072
+ // Ctrl+Shift+S (Save As)
3073
+ if (e.ctrlKey && e.shiftKey && e.key === 'S') {
3074
+ e.preventDefault();
3075
+ return false;
3076
+ }
3077
+ // F12 (DevTools)
3078
+ if (e.key === 'F12') {
3079
+ e.preventDefault();
3080
+ console.log('[Security] F12 blocked');
3081
+ return false;
3082
+ }
3083
+ // Ctrl+Shift+I (DevTools)
3084
+ if (e.ctrlKey && e.shiftKey && e.key === 'I') {
3085
+ e.preventDefault();
3086
+ return false;
3087
+ }
3088
+ // Ctrl+Shift+J (Console)
3089
+ if (e.ctrlKey && e.shiftKey && e.key === 'J') {
3090
+ e.preventDefault();
3091
+ return false;
3092
+ }
3093
+ // Ctrl+U (View Source)
3094
+ if (e.ctrlKey && e.key === 'u') {
3095
+ e.preventDefault();
3096
+ return false;
3097
+ }
3098
+ // Ctrl+Shift+C (Inspect Element)
3099
+ if (e.ctrlKey && e.shiftKey && e.key === 'C') {
3100
+ e.preventDefault();
3101
+ return false;
3102
+ }
3103
+ }, true);
3104
+
3105
+ // 2. Block context menu (right-click)
3106
+ document.addEventListener('contextmenu', function (e) {
3107
+ // Allow our custom context menu for annotations
3108
+ if (e.target.closest('.annotationLayer') || e.target.closest('.pdfViewer')) {
3109
+ // Keep custom context menu behavior
3110
+ return;
3111
+ }
3112
+ e.preventDefault();
3113
+ return false;
3114
+ }, true);
3115
+
3116
+ // 3. Block copy/cut/paste
3117
+ document.addEventListener('copy', function (e) {
3118
+ e.preventDefault();
3119
+ console.log('[Security] Copy blocked');
3120
+ return false;
3121
+ }, true);
3122
+
3123
+ document.addEventListener('cut', function (e) {
3124
+ e.preventDefault();
3125
+ return false;
3126
+ }, true);
3127
+
3128
+ // 4. Block drag events (prevent dragging content out)
3129
+ document.addEventListener('dragstart', function (e) {
3130
+ e.preventDefault();
3131
+ return false;
3132
+ }, true);
3133
+
3134
+ // 5. DevTools detection (basic)
3135
+ let devToolsOpen = false;
3136
+ const threshold = 160;
3137
+
3138
+ function checkDevTools() {
3139
+ const widthThreshold = window.outerWidth - window.innerWidth > threshold;
3140
+ const heightThreshold = window.outerHeight - window.innerHeight > threshold;
3141
+
3142
+ if (widthThreshold || heightThreshold) {
3143
+ if (!devToolsOpen) {
3144
+ devToolsOpen = true;
3145
+ console.log('[Security] DevTools detected');
3146
+ // Optional: blur content when devtools open
3147
+ document.body.classList.add('devtools-open');
3148
+ }
3149
+ } else {
3150
+ if (devToolsOpen) {
3151
+ devToolsOpen = false;
3152
+ document.body.classList.remove('devtools-open');
3153
+ }
3154
+ }
3155
+ }
3156
+
3157
+ // Check periodically
3158
+ setInterval(checkDevTools, 1000);
3159
+ window.addEventListener('resize', checkDevTools);
3160
+
3161
+ // 6. Block Print via window.print override
3162
+ window.print = function () {
3163
+ console.log('[Security] Print function blocked');
3164
+ alert('Yazdırma bu belgede engellenmiştir.');
3165
+ return false;
3166
+ };
3167
+
3168
+ // 7. Disable beforeprint event
3169
+ window.addEventListener('beforeprint', function (e) {
3170
+ e.preventDefault();
3171
+ document.body.style.display = 'none';
3172
+ });
3173
+
3174
+ window.addEventListener('afterprint', function () {
3175
+ document.body.style.display = '';
3176
+ });
3177
+
3178
+ // 8. Block screenshot keyboard shortcuts
3179
+ document.addEventListener('keyup', function (e) {
3180
+ // PrintScreen key
3181
+ if (e.key === 'PrintScreen') {
3182
+ navigator.clipboard.writeText('');
3183
+ console.log('[Security] PrintScreen clipboard cleared');
3184
+ }
3185
+ }, true);
3186
+
3187
+ // 9. Visibility change detection (tab switching for screenshots)
3188
+ document.addEventListener('visibilitychange', function () {
3189
+ if (document.hidden) {
3190
+ // User switched tabs - could be for screenshot tools
3191
+ console.log('[Security] Tab hidden');
3192
+ }
3193
+ });
3194
+
3195
+ console.log('[Security] All protection features initialized');
3196
+ })();
3050
3197
  </script>
3198
+
3199
+ <!-- Security CSS for DevTools detection -->
3200
+ <style>
3201
+ .devtools-open #viewerContainer,
3202
+ .devtools-open #viewer,
3203
+ .devtools-open .pdfViewer {
3204
+ filter: blur(20px) !important;
3205
+ pointer-events: none !important;
3206
+ }
3207
+
3208
+ .devtools-open::after {
3209
+ content: 'Geliştirici araçları açıkken içerik görüntülenemez.';
3210
+ position: fixed;
3211
+ top: 50%;
3212
+ left: 50%;
3213
+ transform: translate(-50%, -50%);
3214
+ background: rgba(0, 0, 0, 0.9);
3215
+ color: #fff;
3216
+ padding: 30px 50px;
3217
+ border-radius: 10px;
3218
+ font-size: 18px;
3219
+ z-index: 99999;
3220
+ }
3221
+ </style>
3051
3222
  </body>
3052
3223
 
3053
3224
  </html>