peer-term 1.3.0 → 1.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +72 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peer-term",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "license": "MIT",
5
5
  "description": "Share your terminal instantly using a 6-digit code. Encrypted. No config.",
6
6
  "repository": {
package/src/index.js CHANGED
@@ -99,6 +99,10 @@ const RELAY_URLS = argv.relay
99
99
  const HEARTBEAT_INTERVAL_MS = 5000;
100
100
  const MAX_MISSED_PINGS = 2;
101
101
 
102
+ const UPLOAD_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB max
103
+ const UPLOAD_MAX_CHUNKS_PER_FILE = Math.ceil(UPLOAD_MAX_FILE_SIZE / 16384);
104
+ const UPLOAD_MAX_CONCURRENT = 3;
105
+
102
106
  // ─── Path Resolution ─────────────────────────────────────────────────────────
103
107
 
104
108
  function expandTilde(inputPath) {
@@ -605,6 +609,17 @@ class Session {
605
609
  return;
606
610
  }
607
611
 
612
+ // Check if decrypted message is a file transfer message (file-start, file-chunk, file-end)
613
+ try {
614
+ const parsedMsg = JSON.parse(plaintext);
615
+ if (parsedMsg && typeof parsedMsg.type === 'string' && parsedMsg.type.startsWith('file-')) {
616
+ this._handleFileMessage(parsedMsg);
617
+ return;
618
+ }
619
+ } catch {
620
+ // Not JSON — normal keystroke data, fall through
621
+ }
622
+
608
623
  // Normal keystroke — drop if read-only
609
624
  if (this.readOnly) return;
610
625
  this.ptyProcess.write(plaintext);
@@ -834,15 +849,32 @@ class Session {
834
849
  switch (msg.type) {
835
850
  case 'file-start': {
836
851
  // Validate required fields
837
- if (!msg.id || !msg.name || !msg.size || !msg.totalChunks) {
852
+ if (!msg.id || !msg.name || msg.size === undefined || msg.totalChunks === undefined) {
838
853
  this._sendFileError(msg.id, 'Invalid file-start message');
839
854
  return;
840
855
  }
856
+
857
+ if (msg.size > UPLOAD_MAX_FILE_SIZE) {
858
+ this._sendFileError(msg.id, `File exceeds maximum size of ${UPLOAD_MAX_FILE_SIZE / 1024 / 1024}MB`);
859
+ return;
860
+ }
861
+
862
+ if (msg.totalChunks > UPLOAD_MAX_CHUNKS_PER_FILE) {
863
+ this._sendFileError(msg.id, 'File requires too many chunks');
864
+ return;
865
+ }
866
+
867
+ if (Object.keys(this._incomingFiles).length >= UPLOAD_MAX_CONCURRENT) {
868
+ this._sendFileError(msg.id, 'Too many concurrent uploads');
869
+ return;
870
+ }
871
+
841
872
  this.log(`File upload started: "${msg.name}" (${msg.size} bytes, ${msg.totalChunks} chunks)`);
842
873
  this._incomingFiles[msg.id] = {
843
874
  name: msg.name,
844
875
  size: msg.size,
845
876
  totalChunks: msg.totalChunks,
877
+ receivedSize: 0,
846
878
  chunks: new Map(),
847
879
  };
848
880
  break;
@@ -853,8 +885,23 @@ class Session {
853
885
  this._sendFileError(msg.id, 'Unknown transfer ID');
854
886
  return;
855
887
  }
856
- // data is base64-encoded raw bytes
857
- transfer.chunks.set(msg.index, Buffer.from(msg.data, 'base64'));
888
+
889
+ const chunkData = Buffer.from(msg.data, 'base64');
890
+
891
+ if (transfer.chunks.size >= transfer.totalChunks) {
892
+ this._sendFileError(msg.id, 'Too many chunks received');
893
+ delete this._incomingFiles[msg.id];
894
+ return;
895
+ }
896
+
897
+ if (transfer.receivedSize + chunkData.length > transfer.size) {
898
+ this._sendFileError(msg.id, 'Received size exceeds declared size');
899
+ delete this._incomingFiles[msg.id];
900
+ return;
901
+ }
902
+
903
+ transfer.chunks.set(msg.index, chunkData);
904
+ transfer.receivedSize += chunkData.length;
858
905
  this.logDebug(`File chunk ${msg.index + 1}/${transfer.totalChunks} received for "${transfer.name}"`);
859
906
  break;
860
907
  }
@@ -896,7 +943,17 @@ class Session {
896
943
  // Determine save path: ~/peerterm-uploads/<filename>
897
944
  const homeDir = os.homedir();
898
945
  const uploadDir = path.join(homeDir, 'peerterm-uploads');
899
- const savePath = path.join(uploadDir, transfer.name);
946
+ // Sanitize filename to prevent path traversal (e.g., "../../../etc/passwd")
947
+ const baseName = path.basename(transfer.name);
948
+ if (!baseName || baseName === '.' || baseName === '..') {
949
+ this._sendFileError(id, 'Invalid filename');
950
+ delete this._incomingFiles[id];
951
+ return;
952
+ }
953
+ const savePath = path.join(uploadDir, baseName);
954
+
955
+
956
+
900
957
 
901
958
  try {
902
959
  await fs.promises.mkdir(uploadDir, { recursive: true });
@@ -1272,6 +1329,17 @@ class Session {
1272
1329
  return;
1273
1330
  }
1274
1331
 
1332
+ // Check if decrypted message is a file transfer message
1333
+ try {
1334
+ const parsedMsg = JSON.parse(plaintext);
1335
+ if (parsedMsg && typeof parsedMsg.type === 'string' && parsedMsg.type.startsWith('file-')) {
1336
+ this._handleFileMessage(parsedMsg);
1337
+ return;
1338
+ }
1339
+ } catch {
1340
+ // Not JSON — normal keystroke data, fall through
1341
+ }
1342
+
1275
1343
  if (this.readOnly) return;
1276
1344
  this.ptyProcess.write(plaintext);
1277
1345
  } catch (err) {