buninu-linux 0.17.60 → 0.18.62

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 (38) hide show
  1. package/NOTICE.md +46 -46
  2. package/README.md +4 -4
  3. package/README.zh.md +4 -4
  4. package/initramfs/bin/tar +32 -15
  5. package/initramfs/bin/unzip +74 -19
  6. package/initramfs/buninu/CHANGELOG.md +19 -0
  7. package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +21 -0
  8. package/initramfs/buninu/apps/bunmsh/package.json +1 -1
  9. package/initramfs/buninu/apps/bunmsh/src/find.js +135 -56
  10. package/initramfs/buninu/apps/bunmsh/src/main.js +12 -1
  11. package/initramfs/buninu/apps/bunmsh/src/shell.js +8 -2
  12. package/initramfs/buninu/apps/bunmsh/test/find.test.js +103 -0
  13. package/initramfs/buninu/apps/bunmsh/test/interactive-typeahead.test.js +40 -0
  14. package/initramfs/buninu/apps/bunmsh/test/posix-bootstrap.test.js +9 -5
  15. package/initramfs/buninu/apps/bunmsh/test/reference.test.js +2 -5
  16. package/initramfs/buninu/apps/bunmsh/test/shell2.test.js +6 -5
  17. package/initramfs/buninu/apps/bunmsh/test/shell3.test.js +8 -7
  18. package/initramfs/buninu/apps/bunmsh/test/support/terminal.js +10 -0
  19. package/initramfs/buninu/apps/jsgotty/bunfig.toml +3 -0
  20. package/initramfs/buninu/apps/jsgotty/gotty.js +14 -1
  21. package/initramfs/buninu/apps/jsgotty/kitty-flow.js +40 -0
  22. package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
  23. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  24. package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +32 -4
  25. package/initramfs/buninu/apps/jsgotty/tests/kitty-flow-browser.test.js +43 -0
  26. package/initramfs/buninu/apps/jsgotty/tests/kitty-flow.test.js +55 -0
  27. package/initramfs/buninu/apps/jsgotty/tests/manual/fps-collector.mjs +18 -0
  28. package/initramfs/buninu/apps/jsgotty/tests/manual/fps-debug.js +31 -0
  29. package/initramfs/buninu/apps/jsgotty/tests/manual/js-eval-show-fps.js +35 -0
  30. package/initramfs/buninu/apps/jsgotty/tests/manual/kitty-background-debug.js +24 -0
  31. package/initramfs/buninu/package.json +1 -1
  32. package/package.json +1 -1
  33. package/scripts/arch.js +5 -5
  34. package/scripts/arch.sh +5 -5
  35. package/scripts/fetch.js +50 -15
  36. package/scripts/npm-packages.md +11 -11
  37. package/test/archive-bounds.test.js +128 -0
  38. package/test/fetch-npm.test.js +1 -1
package/NOTICE.md CHANGED
@@ -156,83 +156,83 @@ the built image redistributes them.
156
156
  - License: GPL-2.0-only WITH Linux-syscall-note
157
157
  - See: `LICENSES/GPL-2.0.txt` and `LICENSES/Linux-syscall-note.txt`
158
158
 
159
- Linux 6.18.60 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
160
- The default build uses package `linux-virt`, version 6.18.60-r0.
161
- `--linux-lts` and `--real` use package `linux-lts`, version 6.18.60-r0.
159
+ Linux 6.18.62 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
160
+ The default build uses package `linux-virt`, version 6.18.62-r0.
161
+ `--linux-lts` and `--real` use package `linux-lts`, version 6.18.62-r0.
162
162
  Each comes from the x86_64 or aarch64 repository to match `--arch`. All
163
163
  listed modules are the selected package's `.ko.gz` files decompressed without
164
164
  modification.
165
165
 
166
- - Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.60.tar.xz
166
+ - Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.62.tar.xz
167
167
  - Alpine build recipe, configuration and patches (Corresponding Source),
168
- pinned to the commit shipping 6.18.60-r0 (the same for both architectures):
169
- https://gitlab.alpinelinux.org/alpine/aports/-/blob/97a4e7b01d88cdec22ef3a5d30247628dc14d8bc/main/linux-lts/APKBUILD
168
+ pinned to the commit shipping 6.18.62-r0 (the same for both architectures):
169
+ https://gitlab.alpinelinux.org/alpine/aports/-/blob/4835e466ec80d5c31c181fa130a10dc81af60c1c/main/linux-lts/APKBUILD
170
170
  - Packages:
171
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.60-r0.apk
172
- SHA-256 `3ff658cf5aada14687238527f48526cef7a75ac8c673fc597f3a65791c86a25e`
173
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.60-r0.apk
174
- SHA-256 `f205a980e1d683414f5557d26366bee1e52cf128613aa74ef371b3f11b019085`
175
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.60-r0.apk
176
- SHA-256 `b931f14a93634469cc8930afe5d6837361325456b620b94e970bb981d841c99c`
177
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.60-r0.apk
178
- SHA-256 `4d398f1d401597da5e92fa7c3c1af93c4a9988b3747222c0173bfe3ff4cfe4a2`
171
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.62-r0.apk
172
+ SHA-256 `e121b547d9b86194a641718b52ae1c3e605085c59f31525b1649a59b3d4db0c7`
173
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.62-r0.apk
174
+ SHA-256 `2e03d0d13959a369764d8d24d51844634d1fd6973a523f4e49166a300c782a06`
175
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.62-r0.apk
176
+ SHA-256 `3a9a0f16adaabee0e4e05e373682249e2a99ab000faebf6aaaf682ae575c661d`
177
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.62-r0.apk
178
+ SHA-256 `29d6cbf6f432667d5d17080037b3cfd4d28d71c3b0ecb4ad7e53438abf9f24e7`
179
179
 
180
180
  SHA-256 of files in the default x86_64 `linux-virt` image:
181
181
 
182
182
  ```text
183
- kernel/x86_64/vmlinuz-virt 1e8ac7aebfab3f0fcb443eae94ad320efbbda51be91b928c32ca1740b44ebe9d
184
- failover.ko 38da7006965e9595c08ebf062bd681bf991d1b1d4356363e9af9b16731781992
185
- net_failover.ko a753488889d8b5b0289542ffcf734c7802da2bc6fa7605feab96381d085880e3
186
- virtio_net.ko a369f7726343f67f1ec3f12c42024fa873d988ff3922efb0c75eb53d79c5d280
183
+ kernel/x86_64/vmlinuz-virt fb646e37ddfe23cb9cbe1127eb641895adaaa4e6062d66eee11f9b2240d952c7
184
+ failover.ko 7417b9cbde8a6b751a9d5d434c3334f80025637fba4b5812fca826426de2f5f4
185
+ net_failover.ko 9948da8f68df63dc07567f2f8f208fb1d3c917f54766d17e8a95533bd9d5224e
186
+ virtio_net.ko 84f6faa9484b46f0903e1de80c68a4327340bb9f736ae465e5fde4b12e902caa
187
187
  ```
188
188
 
189
189
  SHA-256 of files in an x86_64 `--linux-lts` image:
190
190
 
191
191
  ```text
192
- kernel/x86_64/vmlinuz-lts 4b7fd8c62d98e10fec5cad572a2e5e4446ab8c83db06015521824556514581fc
193
- failover.ko 8c2dba793b09615556234ea62455173c47c012a08f5f147a24e2138a7123b8e3
194
- net_failover.ko 595eb1fc7cf568ecb06b1364b075dc4d818fe9582243625d38295699df54f92b
195
- virtio_net.ko 35836c6a68aa6aaaafbe9c8bdd796c45a3cb8b2cfe943d09a5dbcc62e1039646
196
- virtio_ring.ko 791e3c91f6abab031e1585a869e054f29e0093b072793f4ef0468ba61c31b82b
197
- virtio.ko 11ea2958363dce81c4df8f325bdd8aba417205c2cbc802429b61abbaa34096d2
198
- virtio_pci_legacy_dev.ko 515994aafbd6a6cb3543daa1fa0791135640212afede68e63f353877182f865c
199
- virtio_pci_modern_dev.ko 7fda960c51fa394d1e1a7686060a1919985ff173af3b74c50a160d0a0c8160a2
200
- virtio_pci.ko 1dcb0fad10f95e3fb2b7704efb4ab3090fd0fc43e4961606372fe56b1e0cb016
192
+ kernel/x86_64/vmlinuz-lts 3504e27e6802231fed95f12e46998e73bfe1653a8ca682d448cdc559bbfc6e3e
193
+ failover.ko 386a443826c168bb796168d3a9916b01980e69f310c4485001168e5cbf9bf245
194
+ net_failover.ko 16d9fff3e30b17d0a8875154dd0ef837f2518390c49cf8683d83613c503d05cf
195
+ virtio_net.ko 1b384bd50f94e451c52b8475f7dd7c5eb5386e79b79217e91c8f4ef04f68a1eb
196
+ virtio_ring.ko e13bb8a3ca15f8dec60c12481cd258170ea973934a1747c8dcf42a04ba0f5423
197
+ virtio.ko 403c140a1063a0e4189339582db77315d7896d136c8b677a8cc4947bd6dfa235
198
+ virtio_pci_legacy_dev.ko a3554a7cacbbe417b3c4f0f3bf6a5ed556776ee30337375bf99623c30e6c9943
199
+ virtio_pci_modern_dev.ko 3314bb829a4404b979dc09f342b133b9af3d4c184e4fc61beaa566b49b0407dd
200
+ virtio_pci.ko 5163698dd42cf2a221f58982b1fbcc58d915ec78e9adfca44f09efd3493e71a1
201
201
  ```
202
202
 
203
203
  `--real` additionally includes the following modules from that same
204
204
  x86_64 `linux-lts` package for physical xHCI controllers and USB HID keyboards:
205
205
 
206
206
  ```text
207
- usb-common.ko 1ac8dea6b468dd4428329003f5161e2184dbec9d2af671736581b8dccbed2aa9
208
- usbcore.ko 1121a6019673800300056a587fbf0d71de252a3c7f1aafe534111bf4e34f3a75
209
- xhci-hcd.ko 523a5610eee78a342e07305dbad2b7b0a422eff6f0efe7cc4d10bc03185d678a
210
- xhci-pci.ko 6ae655f06532ec56b3f93e1909e10bce0acf7f5166825461152c706817b5b926
211
- xhci-pci-renesas.ko 59be69cceeb3ab918b4bd070da23bd91d9bf81f9627d0c57abc13fd3e2ebd997
212
- hid.ko e7cf5abe7e29b50ea9fae671b5d0a42d0cf4c7415e30b3e127d77d00ea37cd7d
213
- hid-generic.ko c9082124336d616a489784a117670f83075c0e4bdf0253ffd2fbddf093452efc
214
- usbhid.ko a764883e8831a14d500328af3723d4a5dbb20d3ddf92f9ec7d4136392b6dd539
207
+ usb-common.ko e441b7fa844c3f745e996d270df9c0c193ebffaed86da2af09c2808e5f5c6e51
208
+ usbcore.ko 242fa924fa31c3f09e5539f7814d71349d8393920957a1646ce693520ea709f0
209
+ xhci-hcd.ko 10258014a4e2b82d446731a9816caa305cd6ec01568b1ae83692e6ce72ae76eb
210
+ xhci-pci.ko b8ecc6e8abdd74609ffd26e04bf9511ad2281118319bbe550ce7a32a2148c735
211
+ xhci-pci-renesas.ko 1e24d082466dfd29dc97eb3ac0a45e78290cb3c708da6d46760f281499f6040d
212
+ hid.ko 550ffcb449bbc7e586f594b9bf737260c7be962885a310914f272d20ab903d8a
213
+ hid-generic.ko 26b65cd98dbe3df707458bb2e88cc4d7b106676740bc9775fe932dfe66884672
214
+ usbhid.ko c71f82a76f7cdf16d04ac01270ab959c7e01db3ef55d7e72a447e2be27820220
215
215
  ```
216
216
 
217
217
  SHA-256 of files in the default aarch64 `linux-virt` image:
218
218
 
219
219
  ```text
220
- kernel/aarch64/vmlinuz-virt cd05fed9e617b5df93dc62ece9b78c422fd94e9b432a2f7c092714b139d5eef4
221
- failover.ko 3326352b8cbb89fb1b1281588ec03318175ad8151253da97a50b2180e4e24358
222
- net_failover.ko 7b0b21b6992453ca6b525f816982b7d2b2aec7cdac66ee6a7a57a3506fbef5ce
223
- virtio_net.ko 5e66ddb9e20b5401808a792e245f9c6165654da7fb6b6274df8b33260500d8d2
220
+ kernel/aarch64/vmlinuz-virt 019381933096984ffe4d559969db30b59e223fcfd3a0f349cc7b2b7fd0b9f2bb
221
+ failover.ko 6eb84d8bb29606c40f645101929058d8c1a2226b81f86e1d477ce5292677b1f9
222
+ net_failover.ko 3c4800183b9aadb57b4d7406e17a7dc62dc1955f69eedd52194f0d575fd85e43
223
+ virtio_net.ko 2421ef2c32dd39a9d9d17e20849eb235e0e98d0698f9f3b239839c5367cad3b2
224
224
  ```
225
225
 
226
226
  SHA-256 of files in an aarch64 `--linux-lts` image:
227
227
 
228
228
  ```text
229
- kernel/aarch64/vmlinuz-lts 5ad2bbfaaaf6323f1ae3eef4ca392047413cb397a0d5421ec0f0f450b0ada35e
230
- failover.ko dcd9538f66ecb0ab2aa7d2da8262ab499197899b858eafe73ea322a4d81d587d
231
- net_failover.ko 3570053bf89cdf54fd7188af34150ff2019405cc4018f8dcf02a369f8de71ed8
232
- virtio_net.ko 1254fc3018a319b59cafca3f68632ad739e4e8529b5684062c278a21c85c2784
233
- virtio_pci_legacy_dev.ko e57ab0e3f977104fa9127755af28d450dd459689b99a230066e0f9c43710df04
234
- virtio_pci_modern_dev.ko 1b7b21794f360313a6d7f08d6257892c7932ffcc397eb1dbfb6d128f472d7bef
235
- virtio_pci.ko b7529e676e5237a1b25b2675320526084a6f78c512863e6da6f8ad6aa1e927fb
229
+ kernel/aarch64/vmlinuz-lts a411fa2f9234162c393c63c697b482c2a63702a4d06951c0e85a0ca5ad97cd28
230
+ failover.ko 9c7a6e582e0e2d2eb911d0181ade7cdc28b9cfbbcb4331e87ce9ce18005e93c5
231
+ net_failover.ko 4cfe47683cdf4ced4400069c52890595c418d81bfa3cc44f6e523278823c5326
232
+ virtio_net.ko 9eda63780001f09d30c917feea11b2d319a5656aaed61bce48b2bdbef5826af2
233
+ virtio_pci_legacy_dev.ko ae3ee1f0fec521295b3fe078a271864c484a1177a7099e85930dab9d0d0cc9d5
234
+ virtio_pci_modern_dev.ko f5d23038fd725d9ca5cef5f1d0c982490be9350a203fe8bc0a82c7939630a789
235
+ virtio_pci.ko e02dc0d67bcfd28030db4f7af6b42cb6ea3118f6527ebeab59f379c4d27ff913
236
236
  ```
237
237
 
238
238
  ### linuxx64.efi.stub and linuxaa64.efi.stub
package/README.md CHANGED
@@ -1415,7 +1415,7 @@ initramfs/ init.js, commands, userspace: the same for every archi
1415
1415
  native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
1416
1416
  ```
1417
1417
 
1418
- The `--real` inputs are Alpine v3.24 `linux-lts-6.18.60-r0`, musl `1.2.6-r2`,
1418
+ The `--real` inputs are Alpine v3.24 `linux-lts-6.18.62-r0`, musl `1.2.6-r2`,
1419
1419
  `systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
1420
1420
  `linux-x64-musl-baseline`. The aarch64 guest uses the same versions from
1421
1421
  Alpine's aarch64 repository and Bun's `linux-aarch64-musl`. There is no BusyBox
@@ -1577,7 +1577,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
1577
1577
  and, with the currently pinned kernel, embeds this complete command line:
1578
1578
 
1579
1579
  ```text
1580
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.60-0-lts rdinit=/bin/bun -- -e import('/init.js')
1580
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.62-0-lts rdinit=/bin/bun -- -e import('/init.js')
1581
1581
  ```
1582
1582
 
1583
1583
  Serial kernel logging is retained, while the final console makes
@@ -1601,7 +1601,7 @@ the same way). Build without `--real`; this selects Alpine `linux-virt`, omits t
1601
1601
  module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
1602
1602
 
1603
1603
  ```text
1604
- console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.60-0-virt rdinit=/bin/bun -- -e import('/init.js')
1604
+ console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.62-0-virt rdinit=/bin/bun -- -e import('/init.js')
1605
1605
  ```
1606
1606
 
1607
1607
  Build and boot it with:
@@ -1724,7 +1724,7 @@ The default `--real` image embeds this kernel command line (shown for the
1724
1724
  currently pinned LTS release):
1725
1725
 
1726
1726
  ```text
1727
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.60-0-lts rdinit=/bin/bun -- -e import('/init.js')
1727
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.62-0-lts rdinit=/bin/bun -- -e import('/init.js')
1728
1728
  ```
1729
1729
 
1730
1730
  The physical display (`tty0`) is the final console and therefore owns PID 1's
package/README.zh.md CHANGED
@@ -1415,7 +1415,7 @@ initramfs/ init.js, commands, userspace: the same for every archi
1415
1415
  native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
1416
1416
  ```
1417
1417
 
1418
- `--real` 的輸入為 Alpine v3.24 `linux-lts-6.18.60-r0`、musl
1418
+ `--real` 的輸入為 Alpine v3.24 `linux-lts-6.18.62-r0`、musl
1419
1419
  `1.2.6-r2`、`systemd-efistub-260.2-r0`、`libstdc++`/`libgcc`
1420
1420
  `15.2.0-r5`,以及 Bun 1.4.2 `linux-x64-musl-baseline`。aarch64
1421
1421
  客體使用 Alpine aarch64 儲存庫的相同版本與 Bun 的 `linux-aarch64-musl`。
@@ -1577,7 +1577,7 @@ MiB 對齊,尾端保留 34 個磁區
1577
1577
  包含 xHCI 與 USB HID 模組鏈,目前鎖定核心使用以下完整命令列:
1578
1578
 
1579
1579
  ```text
1580
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.60-0-lts rdinit=/bin/bun -- -e import('/init.js')
1580
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.62-0-lts rdinit=/bin/bun -- -e import('/init.js')
1581
1581
  ```
1582
1582
 
1583
1583
  保留序列核心日誌,最後指定的主控台則讓
@@ -1601,7 +1601,7 @@ pkg install qemu-system-x86-64
1601
1601
  機模組集,並將 `ttyS0` 放最後,讓序列標準輸入輸出擁有 `/dev/console`:
1602
1602
 
1603
1603
  ```text
1604
- console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.60-0-virt rdinit=/bin/bun -- -e import('/init.js')
1604
+ console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.62-0-virt rdinit=/bin/bun -- -e import('/init.js')
1605
1605
  ```
1606
1606
 
1607
1607
  使用以下命令建置並開機:
@@ -1724,7 +1724,7 @@ Bun 腳本。`--help` 使用 `Bun.markdown.ansi`
1724
1724
  鎖定的 LTS 版本):
1725
1725
 
1726
1726
  ```text
1727
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.60-0-lts rdinit=/bin/bun -- -e import('/init.js')
1727
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.62-0-lts rdinit=/bin/bun -- -e import('/init.js')
1728
1728
  ```
1729
1729
 
1730
1730
  實體螢幕(`tty0`)是最後指定的主控台,因此擁有 PID 1 的
package/initramfs/bin/tar CHANGED
@@ -142,7 +142,7 @@ const octal = (block, offset, length) => {
142
142
  };
143
143
 
144
144
  // The header checksum is computed with its own field read as spaces. A
145
- // mismatch means this is not a tar header, so scanning stops there.
145
+ // mismatch rejects the archive instead of reporting a partial listing.
146
146
  const checksumMatches = (block) => {
147
147
  let sum = 0;
148
148
  for (let index = 0; index < BLOCK; index++) {
@@ -151,16 +151,19 @@ const checksumMatches = (block) => {
151
151
  return sum === octal(block, 148, 8);
152
152
  };
153
153
 
154
+ // Each record's length counts bytes, so the records are cut from the bytes and decoded one by one;
155
+ // cutting a decoded string by that length breaks on any multi-byte name.
154
156
  const paxRecords = (data) => {
155
- const records = {};
157
+ const records = Object.create(null);
156
158
  let offset = 0;
157
- const content = decoder.decode(data);
158
- while (offset < content.length) {
159
- const space = content.indexOf(" ", offset);
160
- if (space === -1) break;
161
- const length = Number.parseInt(content.slice(offset, space), 10);
162
- if (!Number.isFinite(length) || length <= 0) break;
163
- const record = content.slice(space + 1, offset + length).replace(/\n$/, "");
159
+ while (offset < data.length) {
160
+ const space = data.indexOf(0x20, offset);
161
+ if (space === -1) throw new Error("invalid pax record length");
162
+ const digits = decoder.decode(data.subarray(offset, space));
163
+ const length = /^[0-9]+$/.test(digits) ? Number(digits) : NaN;
164
+ if (!Number.isSafeInteger(length) || length <= space - offset + 1 || length > data.length - offset
165
+ || data[offset + length - 1] !== 10) throw new Error("invalid pax record length");
166
+ const record = decoder.decode(data.subarray(space + 1, offset + length)).replace(/\n$/, "");
164
167
  const equals = record.indexOf("=");
165
168
  if (equals > 0) records[record.slice(0, equals)] = record.slice(equals + 1);
166
169
  offset += length;
@@ -174,13 +177,14 @@ const entryKinds = {
174
177
  };
175
178
 
176
179
  // Every member of the archive, in order, as { name, kind, target, size, mode,
177
- // mtime }. Returns null when the bytes do not parse as tar.
180
+ // mtime }. Reject malformed input before listing or extracting any members.
178
181
  const scanEntries = (bytes) => {
179
182
  const entries = [];
180
183
  let offset = 0;
181
184
  let longName = null;
182
185
  let longTarget = null;
183
186
  let extended = {};
187
+ let global = {};
184
188
  let ended = false;
185
189
  entries.concatenated = false;
186
190
  while (offset + BLOCK <= bytes.length) {
@@ -191,13 +195,24 @@ const scanEntries = (bytes) => {
191
195
  ended = true;
192
196
  longName = longTarget = null;
193
197
  extended = {};
198
+ global = {};
194
199
  continue;
195
200
  }
196
- if (!checksumMatches(block)) return entries.length ? entries : null;
201
+ if (!checksumMatches(block)) throw new Error("invalid tar header checksum");
197
202
  if (ended) { entries.concatenated = true; ended = false; }
198
203
  const flag = String.fromCharCode(block[156] || 0x30);
199
- const size = extended.size !== undefined ? Number(extended.size) : octal(block, 124, 12);
204
+ // Extension records use their own header size, never the preceding pax size.
205
+ const metadata = ["L", "K", "x", "X", "g"].includes(flag);
206
+ const attrs = { ...global, ...extended };
207
+ const digits = text(block, 124, 12).trim();
208
+ if (!(block[124] & 0x80) && digits && !/^[0-7]+$/.test(digits))
209
+ throw new Error("invalid tar member size");
210
+ if (!metadata && attrs.size !== undefined && !/^[0-9]+$/.test(attrs.size))
211
+ throw new Error("invalid pax member size");
212
+ const size = !metadata && attrs.size !== undefined ? Number(attrs.size) : octal(block, 124, 12);
200
213
  const dataBlocks = Math.ceil(size / BLOCK) * BLOCK;
214
+ if (!Number.isSafeInteger(size) || size < 0 || !Number.isSafeInteger(dataBlocks)
215
+ || dataBlocks > bytes.length - offset) throw new Error("invalid or truncated tar member size");
201
216
  const data = bytes.subarray(offset, offset + size);
202
217
  if (flag === "L" || flag === "K") {
203
218
  const value = decoder.decode(data).replace(/\0+$/, "");
@@ -211,13 +226,14 @@ const scanEntries = (bytes) => {
211
226
  continue;
212
227
  }
213
228
  if (flag === "g") {
229
+ global = { ...global, ...paxRecords(data) };
214
230
  offset += dataBlocks;
215
231
  continue;
216
232
  }
217
- const prefix = text(block, 345, 155);
233
+ const prefix = text(block, 257, 5) === "ustar" ? text(block, 345, 155) : "";
218
234
  const stored = text(block, 0, 100);
219
- const name = extended.path ?? longName ?? (prefix ? `${prefix}/${stored}` : stored);
220
- const target = extended.linkpath ?? longTarget ?? text(block, 157, 100);
235
+ const name = attrs.path ?? longName ?? (prefix ? `${prefix}/${stored}` : stored);
236
+ const target = attrs.linkpath ?? longTarget ?? text(block, 157, 100);
221
237
  entries.push({
222
238
  name: name.replace(/^\.\//, ""),
223
239
  archiveName: name,
@@ -233,6 +249,7 @@ const scanEntries = (bytes) => {
233
249
  extended = {};
234
250
  offset += dataBlocks;
235
251
  }
252
+ if (offset !== bytes.length) throw new Error("truncated tar header");
236
253
  return entries;
237
254
  };
238
255
 
@@ -7,7 +7,7 @@
7
7
  // MEMBER operands may be wildcards. Unix modes and symlinks are restored.
8
8
  // Runs under node as well as bun.
9
9
 
10
- import { chmodSync, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readSync, rmSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
10
+ import { constants, chmodSync, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readSync, rmSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
11
11
  import { basename, dirname, join, normalize, resolve } from "node:path";
12
12
  import { inflateRawSync } from "node:zlib";
13
13
  import { crc32 } from "../lib/crc32.js";
@@ -61,11 +61,23 @@ if (operands.length === 0) usage();
61
61
  const [archive, ...patterns] = operands;
62
62
 
63
63
  const fd = (() => {
64
- try { return openSync(archive, "r"); } catch (error) { return fail(`cannot open ${archive}: ${error.message}`, 9); }
64
+ try { return openSync(archive, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0)); } catch (error) { return fail(`cannot open ${archive}: ${error.message}`, 9); }
65
65
  })();
66
- const size = fstatSync(fd).size;
66
+ const info = fstatSync(fd);
67
+ if (!info.isFile()) fail(`${archive}: not a regular file`, 3);
68
+ const size = info.size;
69
+ const validSize = (value) => Number.isSafeInteger(value) && value >= 0;
70
+ const uint64 = (buffer, offset) => {
71
+ const value = buffer.readBigUInt64LE(offset);
72
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) fail(`${archive}: ZIP64 value exceeds safe integer range`, 3);
73
+ return Number(value);
74
+ };
67
75
  const read = (position, length) => {
68
- const buffer = Buffer.alloc(length);
76
+ if (!validSize(position) || !validSize(length) || position > size || length > size - position)
77
+ fail(`${archive}: invalid or truncated archive range`, 3);
78
+ let buffer;
79
+ try { buffer = Buffer.alloc(length); }
80
+ catch { fail(`${archive}: cannot allocate member buffer`, 3); }
69
81
  let done = 0;
70
82
  while (done < length) {
71
83
  const count = readSync(fd, buffer, done, length - done, position + done);
@@ -80,28 +92,59 @@ const tailLength = Math.min(size, 65557);
80
92
  const tail = read(size - tailLength, tailLength);
81
93
  let eocd = -1;
82
94
  for (let offset = tailLength - 22; offset >= 0; offset--) {
83
- if (tail.readUInt32LE(offset) === 0x06054b50) { eocd = offset; break; }
95
+ if (tail.readUInt32LE(offset) === 0x06054b50 && offset + 22 + tail.readUInt16LE(offset + 20) === tail.length) { eocd = offset; break; }
84
96
  }
85
97
  if (eocd < 0) fail(`${archive}: not a zip archive (no end of central directory)`, 9);
98
+ if (tail.readUInt16LE(eocd + 4) !== 0 || tail.readUInt16LE(eocd + 6) !== 0
99
+ || tail.readUInt16LE(eocd + 8) !== tail.readUInt16LE(eocd + 10))
100
+ fail(`${archive}: split archives are not supported`, 3);
86
101
  let entryCount = tail.readUInt16LE(eocd + 10);
87
102
  let directorySize = tail.readUInt32LE(eocd + 12);
88
103
  let directoryOffset = tail.readUInt32LE(eocd + 16);
104
+ let directoryLimit = size - tailLength + eocd;
89
105
  // zip64: the locator sits just before the end of central directory record.
90
106
  if (eocd >= 20 && tail.readUInt32LE(eocd - 20) === 0x07064b50) {
91
- const record = read(Number(tail.readBigUInt64LE(eocd - 20 + 8)), 56);
107
+ if (tail.readUInt32LE(eocd - 16) !== 0 || tail.readUInt32LE(eocd - 4) !== 1)
108
+ fail(`${archive}: split ZIP64 archives are not supported`, 3);
109
+ const recordOffset = uint64(tail, eocd - 20 + 8);
110
+ if (recordOffset > directoryLimit - 20 - 56) fail(`${archive}: invalid ZIP64 record range`, 3);
111
+ const record = read(recordOffset, 56);
92
112
  if (record.readUInt32LE(0) !== 0x06064b50) fail(`${archive}: bad zip64 end of central directory`, 3);
93
- entryCount = Number(record.readBigUInt64LE(32));
94
- directorySize = Number(record.readBigUInt64LE(40));
95
- directoryOffset = Number(record.readBigUInt64LE(48));
113
+ if (record.readUInt32LE(16) !== 0 || record.readUInt32LE(20) !== 0
114
+ || uint64(record, 24) !== uint64(record, 32) || uint64(record, 4) < 44)
115
+ fail(`${archive}: invalid or split ZIP64 directory`, 3);
116
+ if (uint64(record, 4) > directoryLimit - 20 - recordOffset - 12)
117
+ fail(`${archive}: truncated ZIP64 record`, 3);
118
+ directoryLimit = recordOffset;
119
+ entryCount = uint64(record, 32);
120
+ directorySize = uint64(record, 40);
121
+ directoryOffset = uint64(record, 48);
96
122
  }
97
123
 
124
+ // Info-ZIP writes UTF-8 names without the 0x800 flag, so a name that decodes as UTF-8 is read as
125
+ // UTF-8 and only the rest as the old code page's stand-in, latin1.
126
+ const strictUtf8 = new TextDecoder("utf-8", { fatal: true });
127
+ const memberName = (raw, flags) => {
128
+ if (flags & 0x800) return raw.toString("utf8");
129
+ try { return strictUtf8.decode(raw); } catch { return raw.toString("latin1"); }
130
+ };
131
+
98
132
  const entries = [];
99
- const central = read(directoryOffset, directorySize);
100
- for (let offset = 0, index = 0; index < entryCount; index++) {
133
+ if (!validSize(entryCount) || !validSize(directorySize) || !validSize(directoryOffset)
134
+ || directoryOffset > directoryLimit || directorySize > directoryLimit - directoryOffset
135
+ || entryCount > Math.floor(directorySize / 46)) fail(`${archive}: invalid central directory bounds`, 3);
136
+ let directoryPosition = 0;
137
+ for (let index = 0; index < entryCount; index++) {
138
+ if (directorySize - directoryPosition < 46) fail(`${archive}: truncated central directory`, 3);
139
+ const header = read(directoryOffset + directoryPosition, 46);
140
+ const entryLength = 46 + header.readUInt16LE(28) + header.readUInt16LE(30) + header.readUInt16LE(32);
141
+ if (entryLength > directorySize - directoryPosition) fail(`${archive}: truncated central entry`, 3);
142
+ // Read one bounded entry instead of allocating the untrusted whole-directory size.
143
+ const central = read(directoryOffset + directoryPosition, entryLength), offset = 0;
144
+ if (central.readUInt16LE(34) !== 0) fail(`${archive}: split members are not supported`, 3);
101
145
  if (central.readUInt32LE(offset) !== 0x02014b50) fail(`${archive}: bad central directory entry ${index}`, 3);
102
146
  const nameLength = central.readUInt16LE(offset + 28);
103
147
  const extraLength = central.readUInt16LE(offset + 30);
104
- const commentLength = central.readUInt16LE(offset + 32);
105
148
  const flags = central.readUInt16LE(offset + 8);
106
149
  const entry = {
107
150
  madeBy: central.readUInt16LE(offset + 4) >> 8,
@@ -114,23 +157,33 @@ for (let offset = 0, index = 0; index < entryCount; index++) {
114
157
  size: central.readUInt32LE(offset + 24),
115
158
  external: central.readUInt32LE(offset + 38),
116
159
  localOffset: central.readUInt32LE(offset + 42),
117
- name: central.toString(flags & 0x800 ? "utf8" : "latin1", offset + 46, offset + 46 + nameLength),
160
+ name: memberName(central.subarray(offset + 46, offset + 46 + nameLength), flags),
118
161
  };
119
162
  // zip64 extra field: the 0xFFFFFFFF fields follow, in this order.
120
163
  const extra = central.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength);
121
- for (let position = 0; position + 4 <= extra.length;) {
164
+ const required64 = ["size", "compressedSize", "localOffset"].filter(key => entry[key] === 0xffffffff);
165
+ let found64 = false;
166
+ for (let position = 0; position < extra.length;) {
167
+ if (extra.length - position < 4) fail(`${archive}: truncated extra field`, 3);
122
168
  const id = extra.readUInt16LE(position);
123
169
  const length = extra.readUInt16LE(position + 2);
124
- if (id === 0x0001) {
170
+ if (length > extra.length - position - 4) fail(`${archive}: truncated extra field`, 3);
171
+ if (id === 0x0001 && !found64) {
172
+ found64 = true;
125
173
  let field = position + 4;
126
- for (const key of ["size", "compressedSize", "localOffset"]) {
127
- if (entry[key] === 0xffffffff) { entry[key] = Number(extra.readBigUInt64LE(field)); field += 8; }
174
+ for (const key of required64) {
175
+ if (field + 8 > position + 4 + length) fail(`${archive}: truncated ZIP64 extra field`, 3);
176
+ entry[key] = uint64(extra, field); field += 8;
128
177
  }
129
178
  }
130
179
  position += 4 + length;
131
180
  }
181
+ if (required64.length && !found64) fail(`${archive}: missing ZIP64 extra field`, 3);
182
+ if (entry.localOffset > directoryOffset || directoryOffset - entry.localOffset < 30
183
+ || entry.compressedSize > directoryOffset - entry.localOffset - 30)
184
+ fail(`${archive}: invalid member range`, 3);
132
185
  entries.push(entry);
133
- offset += 46 + nameLength + extraLength + commentLength;
186
+ directoryPosition += entryLength;
134
187
  }
135
188
 
136
189
  // Bun.Glob when running under Bun; elsewhere the wildcards become a RegExp:
@@ -165,10 +218,12 @@ const data = (entry) => {
165
218
  const local = read(entry.localOffset, 30);
166
219
  if (local.readUInt32LE(0) !== 0x04034b50) throw new Error("bad local header");
167
220
  const start = entry.localOffset + 30 + local.readUInt16LE(26) + local.readUInt16LE(28);
221
+ if (!validSize(start) || start > directoryOffset || entry.compressedSize > directoryOffset - start)
222
+ throw new Error("invalid member data range");
168
223
  const raw = read(start, entry.compressedSize);
169
224
  let bytes;
170
225
  if (entry.method === 0) bytes = raw;
171
- else if (entry.method === 8) bytes = inflateRawSync(raw);
226
+ else if (entry.method === 8) bytes = inflateRawSync(raw, { maxOutputLength: Math.max(1, entry.size) });
172
227
  else throw new Error(`compression method ${entry.method} is not supported`);
173
228
  if (bytes.length !== entry.size) throw new Error(`size mismatch (${bytes.length} != ${entry.size})`);
174
229
  if (crc32(bytes) !== entry.crc) throw new Error("CRC error");
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1 - 2026-11-08
4
+
5
+ - Sync jsgotty to 1.4.2-rc0:
6
+ * Add browser Kitty-frame flow control: limit unacknowledged replaceable
7
+ image placements to two, coalesce pending updates for the same placement,
8
+ and pause delivery while the page is hidden. Ordinary terminal output and
9
+ CLI clients keep their existing ordered path.
10
+ * Negotiate the capability per WebSocket and acknowledge image placement
11
+ after decoding, preventing a suspended browser from accumulating stale
12
+ frames that replay when it returns to the foreground.
13
+ * Add automated server/browser flow tests and manual FPS diagnostic helpers.
14
+ - Sync bunmsh to 0.4.2:
15
+ * Extend `find` with grouped expressions, negation, AND/OR precedence,
16
+ short-circuit actions, `-prune` and `-exec` with bounded `+` batches.
17
+ * Stream uncaptured results during traversal while preserving command
18
+ substitution, pipeline capture and redirection behavior.
19
+ * Retain submitted interactive lines received while waiting for terminal
20
+ cursor-position replies, and allow slow terminals up to 400 ms to reply.
21
+
3
22
  ## 0.7.0 - 2026-10-26
4
23
 
5
24
  - Sync jsgotty to 1.4.0:
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable user-visible changes to bunmsh are documented here.
4
4
 
5
+ ## [0.4.2] - 2026-11-08
6
+
7
+ ### Added and fixed
8
+
9
+ - Extend builtin `find` with grouped expressions, negation, AND/OR precedence,
10
+ short-circuit actions and `-prune`. Support `-exec` with `;` and bounded `+`
11
+ batches, and report invalid expressions as command errors.
12
+ - Stream uncaptured `find` results during traversal while preserving command
13
+ substitutions, pipeline captures and stdout/stderr redirection order.
14
+ - Increase the interactive cursor-position query timeout from 60 ms to 400 ms
15
+ for slow terminals when checking output without a trailing newline.
16
+ - Preserve commands submitted while waiting for cursor-position replies instead
17
+ of losing buffered input lines.
18
+
19
+ ### Tests
20
+
21
+ - Isolate builtin assertions from host utilities and descriptor-redirection
22
+ assertions from inherited console-color settings.
23
+ - Expand coverage for `find` expressions, execution, streaming and capture
24
+ isolation, plus immediate, delayed and missing terminal cursor replies.
25
+
5
26
  ## [0.4.1] - 2026-10-13
6
27
 
7
28
  - Fixed awk bypassing linker protection
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunmsh",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Cross-platform command shell for Bun JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",