@rsvelte/vite-plugin-svelte-native 0.1.2 → 0.1.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 (2) hide show
  1. package/envelope.js +71 -2
  2. package/package.json +6 -6
package/envelope.js CHANGED
@@ -25,6 +25,27 @@ const WARN_HAS_START = 1 << 1;
25
25
  const WARN_HAS_END = 1 << 2;
26
26
  const WARN_HAS_FRAME = 1 << 3;
27
27
 
28
+ /**
29
+ * Validate that the byte window `[off, off + len)` lies fully inside a
30
+ * buffer of `bufLen` bytes. The encoder writes offsets/lengths as u32s
31
+ * the decoder otherwise trusts blindly — `buf.toString` / `subarray`
32
+ * silently *clamp* an out-of-range window, so a malformed envelope would
33
+ * decode to truncated data instead of failing loudly. Throw instead (M-012).
34
+ *
35
+ * @param {number} bufLen
36
+ * @param {number} off
37
+ * @param {number} len
38
+ * @param {string} label
39
+ */
40
+ function assertWindow(bufLen, off, len, label) {
41
+ if (off < 0 || len < 0 || off + len > bufLen) {
42
+ throw new Error(
43
+ `[rsvelte] envelope ${label} out of bounds ` +
44
+ `(offset ${off} + length ${len} exceeds buffer of ${bufLen} bytes)`,
45
+ );
46
+ }
47
+ }
48
+
28
49
  /**
29
50
  * Decode an envelope produced by `compileEnvelope` / `compileModuleEnvelope`.
30
51
  *
@@ -71,10 +92,24 @@ function decodeEnvelope(buf) {
71
92
  const cssMapLen = view.getUint32(44, true);
72
93
  const warningsOff = view.getUint32(48, true);
73
94
  const warningsCount = view.getUint32(52, true);
95
+ const warningsLen = view.getUint32(56, true);
74
96
 
75
97
  const hasCss = (flags & FLAG_HAS_CSS) !== 0;
76
98
  const runes = (flags & FLAG_RUNES) !== 0;
77
99
 
100
+ // Validate every field window up front so a malformed envelope throws
101
+ // at decode time rather than silently truncating on first lazy read.
102
+ // A zero offset marks an absent optional field (the header alone fills
103
+ // the first HEADER_LEN bytes, so real data never starts at offset 0).
104
+ const len = buf.byteLength;
105
+ assertWindow(len, jsCodeOff, jsCodeLen, 'js.code');
106
+ if (jsMapOff !== 0) assertWindow(len, jsMapOff, jsMapLen, 'js.map');
107
+ if (hasCss) {
108
+ assertWindow(len, cssCodeOff, cssCodeLen, 'css.code');
109
+ if (cssMapOff !== 0) assertWindow(len, cssMapOff, cssMapLen, 'css.map');
110
+ }
111
+ if (warningsCount > 0) assertWindow(len, warningsOff, warningsLen, 'warnings');
112
+
78
113
  // `buf.toString('utf8', start, end)` is the V8 fast path for
79
114
  // Buffer→string and is consistently faster than wrapping in a
80
115
  // TextDecoder; fall back to TextDecoder for plain Uint8Array.
@@ -109,7 +144,7 @@ function decodeEnvelope(buf) {
109
144
  configurable: true,
110
145
  get() {
111
146
  if (warningsCache === null) {
112
- warningsCache = decodeWarnings(buf, view, warningsOff, warningsCount, slice);
147
+ warningsCache = decodeWarnings(buf, view, warningsOff, warningsLen, warningsCount, slice);
113
148
  }
114
149
  return warningsCache;
115
150
  },
@@ -186,29 +221,50 @@ function makeCodeMapObject(buf, slice, codeOff, codeLen, mapOff, mapLen) {
186
221
  return obj;
187
222
  }
188
223
 
189
- function decodeWarnings(buf, view, off, count, slice) {
224
+ function decodeWarnings(buf, view, off, regionLen, count, slice) {
225
+ // Every record field is bounds-checked against the warnings region
226
+ // (already validated to lie inside the buffer by the caller) so a
227
+ // corrupt internal length throws rather than reading adjacent bytes
228
+ // or overrunning the buffer.
229
+ const end = off + regionLen;
230
+ const need = (p, n, what) => {
231
+ if (p < off || p + n > end) {
232
+ throw new Error(
233
+ `[rsvelte] truncated warning ${what} at offset ${p} ` +
234
+ `(needs ${n} bytes, region ends at ${end})`,
235
+ );
236
+ }
237
+ };
190
238
  const out = new Array(count);
191
239
  let p = off;
192
240
  for (let i = 0; i < count; i++) {
241
+ need(p, 4, 'code length');
193
242
  const codeLen = view.getUint32(p, true);
194
243
  p += 4;
244
+ need(p, codeLen, 'code');
195
245
  const code = slice(p, codeLen);
196
246
  p += codeLen;
247
+ need(p, 4, 'message length');
197
248
  const msgLen = view.getUint32(p, true);
198
249
  p += 4;
250
+ need(p, msgLen, 'message');
199
251
  const message = slice(p, msgLen);
200
252
  p += msgLen;
253
+ need(p, 1, 'flags');
201
254
  const flags = view.getUint8(p);
202
255
  p += 1;
203
256
 
204
257
  const w = { code, message };
205
258
  if (flags & WARN_HAS_FILENAME) {
259
+ need(p, 4, 'filename length');
206
260
  const len = view.getUint32(p, true);
207
261
  p += 4;
262
+ need(p, len, 'filename');
208
263
  w.filename = slice(p, len);
209
264
  p += len;
210
265
  }
211
266
  if (flags & WARN_HAS_START) {
267
+ need(p, 12, 'start position');
212
268
  w.start = {
213
269
  line: view.getUint32(p, true),
214
270
  column: view.getUint32(p + 4, true),
@@ -217,6 +273,7 @@ function decodeWarnings(buf, view, off, count, slice) {
217
273
  p += 12;
218
274
  }
219
275
  if (flags & WARN_HAS_END) {
276
+ need(p, 12, 'end position');
220
277
  w.end = {
221
278
  line: view.getUint32(p, true),
222
279
  column: view.getUint32(p + 4, true),
@@ -228,8 +285,10 @@ function decodeWarnings(buf, view, off, count, slice) {
228
285
  w.position = [w.start.character, w.end.character];
229
286
  }
230
287
  if (flags & WARN_HAS_FRAME) {
288
+ need(p, 4, 'frame length');
231
289
  const len = view.getUint32(p, true);
232
290
  p += 4;
291
+ need(p, len, 'frame');
233
292
  w.frame = slice(p, len);
234
293
  p += len;
235
294
  }
@@ -298,12 +357,22 @@ function decodeBatch(buf) {
298
357
  }
299
358
  const count = view.getUint32(12, true);
300
359
 
360
+ // The entry table must fit inside the buffer before we index into it.
361
+ const tableEnd = BATCH_HEADER_LEN + count * BATCH_ENTRY_LEN;
362
+ if (tableEnd > buf.byteLength) {
363
+ throw new Error(
364
+ `[rsvelte] batch entry table (${count} entries) exceeds buffer ` +
365
+ `(needs ${tableEnd} bytes, buffer is ${buf.byteLength})`,
366
+ );
367
+ }
368
+
301
369
  const out = new Array(count);
302
370
  for (let i = 0; i < count; i++) {
303
371
  const entryOff = BATCH_HEADER_LEN + i * BATCH_ENTRY_LEN;
304
372
  const status = view.getUint32(entryOff, true);
305
373
  const payloadOff = view.getUint32(entryOff + 4, true);
306
374
  const payloadLen = view.getUint32(entryOff + 8, true);
375
+ assertWindow(buf.byteLength, payloadOff, payloadLen, `batch entry ${i}`);
307
376
  // Subarray gives a zero-copy view into the same underlying
308
377
  // ArrayBuffer; `decodeEnvelope` walks it without further allocation.
309
378
  const slice = buf.subarray
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsvelte/vite-plugin-svelte-native",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "NAPI bindings to the rsvelte compiler — used by the @rsvelte vite-plugin-svelte shim",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,11 +27,11 @@
27
27
  "index.d.ts"
28
28
  ],
29
29
  "optionalDependencies": {
30
- "@rsvelte/vite-plugin-svelte-native-darwin-arm64": "^0.1.2",
31
- "@rsvelte/vite-plugin-svelte-native-darwin-x64": "^0.1.2",
32
- "@rsvelte/vite-plugin-svelte-native-linux-arm64-gnu": "^0.1.2",
33
- "@rsvelte/vite-plugin-svelte-native-linux-x64-gnu": "^0.1.2",
34
- "@rsvelte/vite-plugin-svelte-native-win32-x64-msvc": "^0.1.2"
30
+ "@rsvelte/vite-plugin-svelte-native-darwin-arm64": "^0.1.4",
31
+ "@rsvelte/vite-plugin-svelte-native-darwin-x64": "^0.1.4",
32
+ "@rsvelte/vite-plugin-svelte-native-linux-arm64-gnu": "^0.1.4",
33
+ "@rsvelte/vite-plugin-svelte-native-linux-x64-gnu": "^0.1.4",
34
+ "@rsvelte/vite-plugin-svelte-native-win32-x64-msvc": "^0.1.4"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"