Repository navigation
Expand file tree
/
Copy pathbacktest-bun.py
More file actions
1420 lines (1289 loc) · 79.7 KB
/
Copy pathbacktest-bun.py
File metadata and controls
1420 lines (1289 loc) · 79.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""Native Bun / public Socket patch compatibility, with no service doubles.
Every (bun release, project shape, mode) cell builds an isolated project with a
REAL Bun binary, runs the production CLI against the public free minimist@1.2.2
patch, and checks the outcome against `expected_outcome` — an oracle of the
DOCUMENTED boundaries, never the CLI's own refusal codes — so a CLI regression
that refuses a supported configuration (or "supports" a refused one) fails the
cell instead of being recorded as an unsupported PASS.
Shapes (project layouts; `--shapes`):
direct / dev / optional / peer the dependency section minimist lives in
alias {"alias": "npm:minimist@1.2.2"}
transitive mkdirp@0.5.3 + overrides.minimist=1.2.2
two-versions / production a second minimist (npm:minimist@1.2.8) as a
dep / devDep (production installs --production)
workspace / workspace-nested a member declares minimist (nested: the root
pins 1.2.8 beside it)
workspace-root the root declares minimist, the member left-pad
text-workspace the workspace project with a REAL version-0
text lock (--save-text-lockfile, 1.1.39-1.1.45)
workspace-get-uuid / -search `get <uuid>` / `get <purl>` on the workspace
crlf / crlf-lock CRLF package.json / a CRLF-converted bun.lock
space-unicode project path with a space and a non-ASCII char
custom-registry bun's full-URL registry slot injected into the
registry tuple (a non-default registry)
text text-lock opt-in on 1.1.39-1.1.45
legacy-lockb bun.lockb written by Bun 1.1.38, patched natively
and installed by the matrix release
isolated / hoisted bunfig [install] linker (>= 1.3.0)
lockfile-only node_modules removed before the CLI runs
get-uuid / get-search `get <uuid>` / `get <purl>` instead of `scan`
hosted-then-vendored hosted redirect, then the vendored takeover
vendored-then-hosted vendored, then the hosted takeover
already-vendored-workspace wire a plain project (the cell's mode), add a
workspace member, `bun install`, re-run the same
mode (a clean no-op: already_vendored / one hosted
`applied` event), then `repair` rebuilds a deleted artifact
preexisting-manifest a foreign .socket/manifest.json record must
survive a refused vendored run
Boundaries the oracle encodes (measured against real releases):
<= 1.1.38 binary bun.lockb only; 1.1.39-1.1.45 write it by default
1.1.39 first text lock (lockfileVersion 0, --save-text-lockfile)
1.2.0 text default, lockfileVersion 1; 1.4.0: lockfileVersion 2
version-0 workspace lock hosted refuses (redirect_bun_workspace_unsupported)
pre-v2 workspace lock vendored refuses (vendor_bun_workspace_unsupported)
bun.lockb native binary inventory and package-record rewrites
1.3.10 URL/local tarball sha512 enforced (registry tuples are
enforced on every text-lock release)
0.8.1 / 1.0.0 peers not installed, overrides ignored (upstream)
Manifest-less VEX (every supported cell, after the installs): a fresh checkout
of the committed state with .socket/manifest.json deleted and a real frozen
`bun install` must be attested by standalone `vex` against the public patch
API — the patched purl under the record's uuid (vendored: the vendor
ledger's record; hosted: v5 writes NO redirect ledger, so the public API's
`/patch/view/<uuid>` record) with the `(redirected)` / `(vendored)` marker and
exactly the record's vulnerability ids (+ CVE aliases), also through the
embedded `apply --vex`; with the vendor ledger deleted too (lockfile discovery
+ the API); `--offline` with no ledger must omit it as `record_unavailable`
(exit 1); and with the lock put back to the registry version (vendor ledger,
artifacts and the patched install kept) it must NOT be attested, also under
`--no-verify` (`vendor_unwired`; hosted: nothing names the patch any more —
`manifest_not_found`, exit 2).
Every cell records the CLI exit codes (main, repeat, rollback, conversion),
the exact refusal-code set, the repeat-run envelope semantics, digest
enforcement, and after rollback the lockfile presence rules and byte identity.
A hosted rollback restores the upstream entry of the project's registry
(#992: the full tarball URL for a non-default registry, bun's `""` for npmjs —
the custom-registry fixture configures none, so its injected slot comes back as
`""`); a hosted bun.lockb is binary, so its rollback refuses with the
`git checkout -- bun.lockb` remedy and the cell applies that remedy. Rollback
runs over a patched install; the reinstall is a plain `bun install` over the
kept node_modules, and where Bun keeps the patched copy (#764) the rollback must
have advised `bun install --force`, which the cell then runs.
Provenance: `--cli-revision` is the branch-resolvable commit the row is about
(PR head, or the pushed commit); `--cli-build-sha` (or the CLI_BUILD_SHA
environment variable) is the commit the binary was actually built from —
`refs/pull/N/merge` on a pull request — recorded as `cliBuildSha` (null when
neither is given). A `--versions/--shapes/--modes` narrowing that leaves no
applicable cell is reported, writes a single `{"noCells": true}` summary row
and exits 0 — the default shape list always holds `direct`, which applies to
every release and mode, so an un-narrowed run can never go vacuous.
"""
import argparse
import base64
import concurrent.futures
from datetime import datetime, timezone
import hashlib
import http.client
import json
import os
from pathlib import Path
import platform
import re
import shutil
import subprocess
import time
import urllib.error
import urllib.request
import zipfile
VERSIONS = ['0.8.1', '1.0.0', '1.0.36', '1.1.0', '1.1.38', '1.1.39', '1.1.43', '1.1.45',
'1.2.0', '1.2.23', '1.3.0', '1.3.9', '1.3.10', '1.3.14', '1.4.0', '1.4.2']
SHAPES = ['direct', 'dev', 'optional', 'alias', 'transitive', 'two-versions',
'workspace', 'workspace-nested', 'workspace-root', 'text-workspace',
'workspace-get-uuid', 'workspace-get-search', 'peer', 'crlf', 'crlf-lock',
'space-unicode', 'custom-registry', 'text', 'legacy-lockb', 'isolated', 'hoisted',
'lockfile-only', 'production', 'get-uuid', 'get-search',
'hosted-then-vendored', 'vendored-then-hosted', 'already-vendored-workspace',
'preexisting-manifest']
# Vendored mode is manifest-free (the vendor ledger embeds the record), so the
# former `vendored-detached` leg collapsed into `vendored`: same footprint.
MODES = ['hosted', 'vendored']
PURL = 'pkg:npm/minimist@1.2.2'
UUID = '642d7f02-ebc1-4ab0-99e2-07f5dd8463cb'
# The registry slot bun writes for a non-default registry: the full tarball URL.
REGISTRY_SLOT = 'https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz'
LOCAL_TUPLE_SPEC = f'minimist@.socket/vendor/npm/{UUID}/minimist-1.2.2.tgz'
HOSTED_TUPLE_PREFIX = 'minimist@https://patch.socket.dev/'
# A record for ANOTHER purl seeded into .socket/manifest.json by the
# `preexisting-manifest` shape; a refused vendored run must leave it intact.
OTHER_PURL = 'pkg:npm/left-pad@1.3.0'
# Release boundaries, measured against real binaries (docs/testing/bun-compatibility.md).
LEGACY_BUN = '1.1.38' # last binary-only release; legacy-lockb baseline
TEXT_LOCK_FROM = (1, 1, 39) # --save-text-lockfile (lockfileVersion 0)
TEXT_DEFAULT_FROM = (1, 2, 0) # bun.lock by default (lockfileVersion 1)
LOCK_V2_FROM = (1, 4, 0) # fresh locks are lockfileVersion 2
LINKER_FROM = (1, 3, 0) # bunfig [install] linker
TARBALL_INTEGRITY_ENFORCED_FROM = (1, 3, 10) # URL/local tarball sha512 verified
NO_PEER_OR_OVERRIDE = ('0.8.1', '1.0.0') # peers not installed, overrides ignored
# Bun 1.3.0's isolated linker leaves `node_modules/.bun/node_modules/<name>`
# (the hidden hoist link) on a superseded store entry, even under
# `bun install --force`; 1.3.1 repoints it. Measured on 1.3.0-1.3.14.
STALE_HIDDEN_HOIST = ('1.3.0',)
# Bun's hoisted linker keeps the installed copy when a lock entry moves back to
# the registry copy of the same name@version, so `rollback` / `vendor --revert`
# name `bun install --force` whenever node_modules may still hold it (#764).
# The rollback step follows that advice (see `rollbackReinstallAdvised`).
BUN_REINSTALL_ADVISORIES = {'vendor_bun_reinstall_required', 'redirect_bun_reinstall_required'}
# Advisory codes a SUPPORTED run may carry; everything else is a refusal.
# Deliberately NOT here: `*_bun_default_trust_lost` (#371 — minimist is not on
# Bun's default-trusted list), `*_non_registry_entry_skipped` (#497 — every
# fixture resolves minimist from the registry) and
# `vendor_bun_lockb_duplicate_records` (#861 — no fixture carries a nested
# duplicate record). On these fixtures each would be a misclassification, so it
# must fail the cell rather than be waved through.
INFORMATIONAL = {
'vendor_prebuilt_downloaded', 'vendor_prebuilt_unavailable', 'vendor_prebuilt_pending',
'vendor_fetched_missing', 'reinstall_required',
'vendor_takeover_reverted_redirect', 'redirect_takeover_reverted_vendored',
*BUN_REINSTALL_ADVISORIES,
}
# Codes that mean the rewriter or the takeover broke on a supported configuration.
REGRESSION_CODES = {
'redirect_bun_entry_not_found', 'redirect_revert_failed',
}
WORKSPACE_SHAPES = {'workspace', 'workspace-nested', 'workspace-root', 'text-workspace',
'workspace-get-uuid', 'workspace-get-search', 'preexisting-manifest'}
GET_SHAPES = {'get-uuid', 'get-search', 'workspace-get-uuid', 'workspace-get-search'}
TEXT_OPT_IN_SHAPES = {'text', 'text-workspace'}
CONVERSION_SHAPES = {'hosted-then-vendored', 'vendored-then-hosted', 'already-vendored-workspace'}
def ver(version):
return tuple(map(int, version.split('.')))
def save(path, data):
path.write_text(json.dumps(data, indent=2) + '\n', encoding='utf-8')
# The CLI's own report of a transport failure: a request error, or a 5xx from
# the patch API (e.g. "API request failed with status 503: upstream connect
# error or disconnect/reset before headers").
CLI_TRANSPORT_FAILURE = re.compile(r'error sending request for url \(|API request failed with status 5\d\d\b')
# bun's own report of a failed fetch (e.g. the hosted tarball from
# patch.socket.dev): `error: ConnectionRefused downloading tarball <spec>`,
# or `error: GET <url> - 503` for a server error.
BUN_TRANSPORT_FAILURE = re.compile(
r'^error: (?:Connection\w+|FailedToOpenSocket|Timeout|TLSHandshakeTimeout) downloading '
r'|^error: GET \S+ - 5\d\d\b', re.M)
# The harness's own fetches (published record, hosted tarball digest) that
# still fail after their in-place retries surface as the cell's `error`:
# a 5xx / 429 (`HTTP Error 503: Service Unavailable`) or no response at all
# (`<urlopen error [Errno 104] Connection reset by peer>`).
HARNESS_TRANSPORT_FAILURE = re.compile(
r'\bHTTP Error (?:5\d\d|429)\b|^<urlopen error |^\[(?:Win)?Errno \d+\] Connection reset')
def bun_transport_failures(output):
"""The lines of a bun install's output that report a transport failure."""
return [line for line in output.splitlines() if BUN_TRANSPORT_FAILURE.match(line)]
def has_transport_failure(value):
"""Only explicit request transport errors (a request error or a patch API
5xx, from the CLI, from bun's fetch or from the harness's own fetch)
qualify for a fresh-cell retry."""
if isinstance(value, dict):
return any(has_transport_failure(item) for item in value.values())
if isinstance(value, list):
return any(has_transport_failure(item) for item in value)
return isinstance(value, str) and bool(CLI_TRANSPORT_FAILURE.search(value)
or BUN_TRANSPORT_FAILURE.search(value)
or HARNESS_TRANSPORT_FAILURE.search(value))
def retry_network_cell(run_case, job, root, attempts=3):
"""Keep failed evidence and retry transient service failures from a clean tree."""
name = '-'.join(job)
case = root / 'captures' / name
history = []
for attempt in range(1, attempts + 1):
row = run_case(job)
if row['passed'] or not has_transport_failure(row) or attempt == attempts:
if history:
row['networkRetryAttempts'] = history
save(case / 'result.json', row)
return row
evidence = root / 'attempts' / name / str(attempt)
evidence.mkdir(parents=True, exist_ok=True)
for source in case.iterdir():
if source.is_file() and source.suffix in ('.log', '.json'):
shutil.copy2(source, evidence / source.name)
elif source.name == 'tree':
shutil.copytree(source, evidence / source.name, dirs_exist_ok=True)
history.append(dict(attempt=attempt, evidence=evidence.relative_to(root).as_posix(),
failedChecks=[key for key, passed in row.get('checks', {}).items() if not passed]))
# Remove the failed project's package-manager caches too. A retry is
# another cold proof, never a continuation from partially written state.
shutil.rmtree(case)
print(f'{name}: request transport failed; retrying fresh cell ({attempt}/{attempts})', flush=True)
time.sleep(5 * attempt)
def run(command, cwd, env, log, required=True, timeout=180, tolerate_timeout=False):
"""(exit code, combined output). A hang is an error — except for the
digest-tamper installs (`tolerate_timeout`), where Bun 1.3.9 and 1.3.10
print the integrity error and then never exit on a workspace project:
those return (None, output-so-far) so the caller can still judge the
rejection (the error was reported and nothing was installed)."""
try:
result = subprocess.run([str(x) for x in command], cwd=cwd, env=env,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=timeout)
except subprocess.TimeoutExpired as error:
log.write_bytes(error.stdout or b'')
if tolerate_timeout:
return None, (error.stdout or b'').decode(errors='replace')
raise
log.write_bytes(result.stdout)
if required and result.returncode:
raise RuntimeError(f'{command}: {result.stdout.decode(errors="replace")[-4000:]}')
return result.returncode, result.stdout.decode(errors='replace')
def git_hash(data):
return hashlib.sha256(f'blob {len(data)}\0'.encode() + data).hexdigest()
def sha256(data):
return hashlib.sha256(data).hexdigest()
class DownloadError(RuntimeError):
"""A release asset arrived but failed verification (truncated / tampered)."""
USER_AGENT = 'SocketPatchCLI-backtest/1.0'
RETRYABLE = (urllib.error.URLError, TimeoutError, ConnectionError, http.client.HTTPException)
def fetch(url, dest, attempts=5):
"""Download `url` to `dest` with backoff. 5xx / 429 / connection errors /
stalls retry; any other HTTP error is final."""
for attempt in range(1, attempts + 1):
try:
request = urllib.request.Request(url, headers={'User-Agent': USER_AGENT})
with urllib.request.urlopen(request, timeout=60) as response, open(dest, 'wb') as out:
shutil.copyfileobj(response, out)
return
except RETRYABLE as error:
final = isinstance(error, urllib.error.HTTPError) and error.code < 500 and error.code != 429
if final or attempt == attempts:
raise
print(f'download {url} attempt {attempt} failed: {error}; retrying', flush=True)
time.sleep(10 * attempt)
def listed_sha256(sums, name):
"""The SHA-256 SHASUMS256.txt lists for `name`; fail closed when absent."""
for line in sums.read_text(encoding='utf-8').splitlines():
parts = line.strip().split()
if len(parts) == 2 and parts[1] == name:
return parts[0]
raise RuntimeError(f'{name} is not listed in {sums}')
def bun_asset():
system = platform.system().lower()
arch = 'aarch64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64'
if system == 'windows':
arch = 'x64'
return system, f'bun-{system}-{arch}'
def install_tool(root, version):
"""Return the Bun binary for `version` under `root/<version>/<asset>/`,
downloading the release zip (retried, verified against the release's own
SHASUMS256.txt, fail closed) when it is not already there — the CI
pre-download step leaves the same layout plus SHASUMS256.txt behind."""
system, asset = bun_asset()
directory = root / version
binary = directory / asset / ('bun.exe' if system == 'windows' else 'bun')
if not binary.exists():
directory.mkdir(parents=True, exist_ok=True)
base = f'https://github.com/oven-sh/bun/releases/download/bun-v{version}'
archive = directory / f'{asset}.zip'
sums = directory / 'SHASUMS256.txt'
for attempt in range(1, 6):
try:
# Bun 0.1.x predates published checksum manifests. Those
# immutable release assets have reviewed SHA-256 pins in this
# repository; all later releases use upstream SHASUMS256.txt.
historical = json.loads(Path(__file__).with_name('bun-historical-shas.json').read_text())
pins = {key.split('/', 1)[1]: digest for key, digest in historical.items()
if key.startswith(version + '/')}
if pins:
sums.write_text(''.join(f'{digest} {name}\n' for name, digest in pins.items()))
else:
fetch(f'{base}/SHASUMS256.txt', sums)
fetch(f'{base}/{asset}.zip', archive)
expected = listed_sha256(sums, f'{asset}.zip')
actual = sha256(archive.read_bytes())
if actual != expected:
raise DownloadError(f'{asset}.zip sha256 {actual} != SHASUMS256.txt {expected}')
with zipfile.ZipFile(archive) as zipped:
zipped.extractall(directory)
break
except (zipfile.BadZipFile, DownloadError, *RETRYABLE) as error:
final = isinstance(error, urllib.error.HTTPError) and error.code < 500 and error.code != 429
if final or attempt == 5:
raise
print(f'bun {version} attempt {attempt} failed: {error}; retrying', flush=True)
time.sleep(10 * attempt)
finally:
archive.unlink(missing_ok=True)
binary.chmod(0o755)
actual = subprocess.check_output([binary, '--version'], text=True).strip()
if actual != version:
raise RuntimeError(f'Expected Bun {version}, got {actual}')
return binary
def archive_sha256(root, version):
"""The verified release-zip SHA-256 when SHASUMS256.txt sits beside the
tool (written by install_tool or the CI pre-download step)."""
sums = root / version / 'SHASUMS256.txt'
try:
return listed_sha256(sums, f'{bun_asset()[1]}.zip') if sums.exists() else None
except RuntimeError:
return None
def base_shape(shape):
"""The project layout a shape starts from."""
if shape in ('text-workspace', 'workspace-get-uuid', 'workspace-get-search',
'preexisting-manifest'):
return 'workspace'
if shape in ('crlf-lock', 'legacy-lockb', 'custom-registry', *CONVERSION_SHAPES):
return 'direct'
return shape
def project_files(shape):
layout = base_shape(shape)
manifest = dict(name='bun-patch-backtest', version='1.0.0', private=True,
dependencies={'minimist': '1.2.2'})
files = {}
if layout in ('dev', 'optional', 'peer'):
key = {'dev': 'devDependencies', 'optional': 'optionalDependencies',
'peer': 'peerDependencies'}[layout]
manifest[key] = manifest.pop('dependencies')
elif layout == 'alias':
manifest['dependencies'] = {'alias': 'npm:minimist@1.2.2'}
elif layout == 'transitive':
manifest['dependencies'] = {'mkdirp': '0.5.3'}
manifest['overrides'] = {'minimist': '1.2.2'}
elif layout == 'two-versions':
manifest['dependencies']['other'] = 'npm:minimist@1.2.8'
elif layout == 'production':
manifest['devDependencies'] = {'other': 'npm:minimist@1.2.8'}
elif layout.startswith('workspace'):
manifest['workspaces'] = ['packages/*']
manifest['dependencies'] = {'consumer': 'workspace:*'}
member = {'minimist': '1.2.2'}
if layout == 'workspace-nested':
manifest['dependencies']['minimist'] = '1.2.8'
elif layout == 'workspace-root':
# The root declares the patched package; the member something else.
manifest['dependencies']['minimist'] = '1.2.2'
member = {'left-pad': '1.3.0'}
files['packages/consumer/package.json'] = json.dumps(dict(
name='consumer', version='1.0.0', dependencies=member)) + '\n'
elif layout in ('isolated', 'hoisted'):
files['bunfig.toml'] = f'[install]\nlinker = "{layout}"\n'
files['package.json'] = json.dumps(manifest, indent=2) + '\n'
return {name: (data.replace('\n', '\r\n') if shape == 'crlf' else data).encode()
for name, data in files.items()}
def add_workspace_member(project):
"""Turn a plain project into a workspace with one member (left-pad) in
place; returns the files it wrote/rewrote."""
manifest = json.loads((project / 'package.json').read_text(encoding='utf-8'))
manifest['workspaces'] = ['packages/*']
manifest['dependencies']['consumer'] = 'workspace:*'
files = {'package.json': (json.dumps(manifest, indent=2) + '\n').encode(),
'packages/consumer/package.json': (json.dumps(dict(
name='consumer', version='1.0.0',
dependencies={'left-pad': '1.3.0'})) + '\n').encode()}
for name, data in files.items():
(project / name).parent.mkdir(parents=True, exist_ok=True)
(project / name).write_bytes(data)
return files
def seed_manifest(project):
"""Write a .socket/manifest.json holding one record for a purl the project
does not install (the schema the CLI writes: uuid, exportedAt, files,
vulnerabilities, description, license, tier) plus its after-blob under
.socket/blobs/<afterHash>, so the record is locally satisfied exactly like
a committed one — without the blob the vendor step aborts the whole run
with `no_local_source` before it ever reaches the bun refusal."""
after = b'// seeded by backtest-bun.py\n'
manifest = {'patches': {OTHER_PURL: {
'uuid': '00000000-0000-4000-8000-000000000000',
'exportedAt': 'Mon, 05 Jan 2026 17:03:26 GMT',
'files': {'package/index.js': {'beforeHash': git_hash(b'// pristine\n'),
'afterHash': git_hash(after)}},
'vulnerabilities': {}, 'description': 'seeded by backtest-bun.py',
'license': 'MIT', 'tier': 'free'}}}
(project / '.socket/blobs').mkdir(parents=True)
(project / '.socket/blobs' / git_hash(after)).write_bytes(after)
save(project / '.socket/manifest.json', manifest)
return manifest
def get_verb(shape):
if shape in ('get-uuid', 'workspace-get-uuid'):
return ['get', UUID]
if shape in ('get-search', 'workspace-get-search'):
return ['get', PURL]
return ['scan']
def cell_applies(version, shape, mode):
"""Which (version, shape, mode) cells exist: shapes need the Bun feature
they exercise, and the two-mode shapes run only where both modes are
supported (their `mode` is the vendored flavor)."""
v = ver(version)
if shape in ('isolated', 'hoisted') and v < LINKER_FROM:
return False
if shape in TEXT_OPT_IN_SHAPES and v < TEXT_LOCK_FROM:
return False
if shape == 'text-workspace' and v >= TEXT_DEFAULT_FROM:
return False # the text lock is the default there: identical to `workspace`
if shape == 'legacy-lockb' and v < TEXT_LOCK_FROM:
return False # the matrix release would be the legacy writer itself
if shape in ('crlf-lock', 'custom-registry') and v < TEXT_DEFAULT_FROM:
return False # both need a default text bun.lock to edit
if shape == 'already-vendored-workspace' and v < TEXT_DEFAULT_FROM:
return False # this shape explicitly inspects text re-save syntax
if shape in ('hosted-then-vendored', 'vendored-then-hosted') and mode == 'hosted':
return False # their `mode` is the vendored flavor of the conversion
if shape == 'preexisting-manifest' and (mode == 'hosted' or v < TEXT_DEFAULT_FROM or v >= LOCK_V2_FROM):
return False # always a refused pre-v2 text-workspace vendored run
return True
def expected_outcome(version, shape, mode):
"""The DOCUMENTED outcome of one cell — never derived from the CLI's output.
supported: whether the patch must land; codes: the EXACT refusal-code set
(after removing INFORMATIONAL); exit: 'zero' (supported, hosted refusals,
upstream limitations) or 'nonzero' (vendored / get refusals);
limitation: the row annotation for an unsupported cell; rerun: the main
command is a documented no-op re-run (already_vendored) rather than a
first application."""
v = ver(version)
hosted = mode == 'hosted'
scan = shape not in GET_SHAPES
workspace = shape in WORKSPACE_SHAPES
text_lock = (v >= TEXT_DEFAULT_FROM or (shape in TEXT_OPT_IN_SHAPES and v >= TEXT_LOCK_FROM)) \
and shape != 'legacy-lockb'
# lockfileVersion of a freshly written text lock.
lock_version = 2 if v >= LOCK_V2_FROM else 1 if v >= TEXT_DEFAULT_FROM else 0
def outcome(supported, codes=(), exit='zero', limitation=None, rerun=False):
return dict(supported=supported, codes=set(codes), exit=exit,
limitation=limitation, rerun=rerun)
if version in NO_PEER_OR_OVERRIDE and shape in ('peer', 'transitive'):
return outcome(False, limitation='This Bun release does not install the requested '
'peer or honor the transitive override')
if shape == 'already-vendored-workspace':
return outcome(True, rerun=True)
if not text_lock:
return outcome(True)
if workspace:
if hosted and lock_version == 0:
return outcome(False, {'redirect_bun_workspace_unsupported'},
limitation='Version-0 workspace locks cannot carry hosted tarballs')
if not hosted and lock_version < 2:
return outcome(False, {'vendor_bun_workspace_unsupported'}, 'nonzero',
limitation='Pre-v2 workspace locks may be installed by Bun < 1.4, '
'which resolves local tarballs relative to the member')
return outcome(True)
def store_entry(path):
"""The isolated-linker store entry (`node_modules/.bun/<key>`) holding
`path`, or None outside one (`.bun/node_modules` is Bun's hoist dir)."""
parts = path.parts
for i in range(len(parts) - 2):
if parts[i] == 'node_modules' and parts[i + 1] == '.bun' and parts[i + 2] != 'node_modules':
return Path(*parts[:i + 3])
return None
def hidden_hoist(directory):
"""Whether `directory` is Bun's hidden hoist dir (`node_modules/.bun/node_modules`)."""
parts = directory.parts
return parts[-3:] == ('node_modules', '.bun', 'node_modules')
def linked_store_entries(project, skip_hidden_hoist=False):
"""Every isolated store entry some symlink outside it resolves into. Bun's
isolated linker leaves the entry of a superseded resolution (the patched
`minimist@https+++patch.socket.dev+…`) on disk, unlinked, after the lock
moves back to the registry; nothing can `require` it (the #599 orphans).
`skip_hidden_hoist` ignores the links in Bun's hidden hoist dir (see
STALE_HIDDEN_HOIST)."""
live = set()
for directory, subdirs, files in os.walk(project):
if '.socket' in Path(directory).relative_to(project).parts:
subdirs[:] = []
continue
if skip_hidden_hoist and hidden_hoist(Path(directory)):
continue
for name in [*subdirs, *files]:
link = Path(directory) / name
# Bun links with junctions on Windows (Path.is_junction: 3.12+).
if not (link.is_symlink() or getattr(link, 'is_junction', lambda: False)()):
continue
entry = store_entry(link.resolve())
if entry is not None and store_entry(Path(directory).resolve() / name) != entry:
live.add(entry)
return live
def installed_targets(project, skip_hidden_hoist=False):
"""The installed minimist@1.2.2 copies node can load: every copy outside
an isolated store, and the store copies something links to."""
targets = []
live = None
for manifest in project.rglob('package.json'):
if 'node_modules' not in manifest.parts or '.socket' in manifest.parts:
continue
entry = store_entry(manifest)
if entry is not None:
live = linked_store_entries(project, skip_hidden_hoist) if live is None else live
if entry.resolve() not in live:
continue
data = json.loads(manifest.read_text(encoding='utf-8'))
if data.get('name') == 'minimist' and data.get('version') == '1.2.2':
targets.append(manifest.parent)
return targets
def oracle(project, record, side, skip_hidden_hoist=False):
targets = installed_targets(project, skip_hidden_hoist)
checks = {}
for target in targets:
for filename, hashes in record['files'].items():
file = target / filename.removeprefix('package/')
expected = hashes.get(side + 'Hash')
checks[str(file.relative_to(project))] = (
git_hash(file.read_bytes()) == expected if expected and file.is_file()
else expected is None and not file.exists())
return bool(targets) and bool(checks) and all(checks.values()), checks
def parse_envelope(output):
return json.loads(output[output.index('{'):])
def mode_events(envelope, mode, *actions):
"""The v5 envelope's events of one leg (`details.mode` hosted / vendored),
narrowed to `actions` when given."""
return [e for e in envelope.get('events') or []
if (e.get('details') or {}).get('mode') == mode
and (not actions or e.get('action') in actions)]
def envelope_codes(envelope):
"""(codes about the minimist patch or carrying no purl, codes about other
purls) — every channel the v5 envelope reports on: top-level warnings
(`{code, detail}`), every event's `errorCode`, and `error.code`."""
mine, others = [], []
def take(purl, code):
if code:
(mine if purl in (None, PURL) else others).append(code)
for w in envelope.get('warnings') or []:
take(None, w.get('code') if isinstance(w, dict) else w)
for e in envelope.get('events') or []:
take(e.get('purl'), e.get('errorCode'))
if isinstance(envelope.get('error'), dict):
take(None, envelope['error'].get('code'))
return mine, others
def applied_count(envelope, mode):
"""Pins (hosted) / vendorings (vendored) the run wrote: the leg's
`applied` events (`verified` on a dry run). A hosted re-run over an
already-pinned lock still reports `applied` for the confirmed pin."""
return len(mode_events(envelope, mode, 'applied', 'verified'))
def downloaded_count(envelope):
return (envelope.get('summary') or {}).get('downloaded', 0)
def rerun_clean(code, envelope, mode):
"""The documented no-op re-run: hosted re-confirms the wiring (one hosted
`applied` event, no warnings beyond advisories); vendored skips exactly
one already_vendored purl with nothing applied or failed."""
if code != 0 or envelope.get('status') != 'success':
return False
codes, _ = envelope_codes(envelope)
if mode == 'hosted':
return (applied_count(envelope, mode) == 1
and not set(codes) - INFORMATIONAL)
events = mode_events(envelope, mode)
return (applied_count(envelope, mode) == 0
and sum(e.get('action') == 'skipped' and e.get('errorCode') == 'already_vendored'
for e in events) == 1
and not any(e.get('action') == 'failed' for e in envelope.get('events') or [])
and not set(codes) - INFORMATIONAL - {'already_vendored'})
PUBLIC_PATCH_API = 'https://patches-api.socket.dev'
_PUBLISHED_RECORDS = {}
def published_record(uuid):
"""The public proxy's `/patch/view/<uuid>` record (cached per run)."""
if uuid not in _PUBLISHED_RECORDS:
url = f'{PUBLIC_PATCH_API}/patch/view/{uuid}'
for attempt in range(1, 6):
try:
request = urllib.request.Request(url, headers={'Accept': 'application/json', 'User-Agent': USER_AGENT})
with urllib.request.urlopen(request, timeout=60) as response:
_PUBLISHED_RECORDS[uuid] = json.loads(response.read().decode('utf-8'))
break
except RETRYABLE:
if attempt == 5:
raise
time.sleep(10 * attempt)
return _PUBLISHED_RECORDS[uuid]
def hosted_wired(project):
"""Whether a bun lock of `project` pins the hosted patch UUID."""
return any(UUID.encode() in (project / name).read_bytes()
for name in ('bun.lock', 'bun.lockb') if (project / name).is_file())
def ledger_record(project, mode):
"""The patch record for PURL the mode's state carries (None when absent).
Vendored: the vendor ledger's entry `record` (vendored mode never writes
`.socket/manifest.json`). Hosted: v5 writes no ledger — the lock pin is
the state — so the record is the public API's for the pinned uuid."""
if mode == 'hosted':
if (project / '.socket/vendor/redirect-state.json').exists():
raise RuntimeError('hosted mode wrote the pre-v5 redirect ledger')
return published_record(UUID) if hosted_wired(project) else None
path = project / '.socket/vendor/state.json'
if not path.is_file():
return None
state = json.loads(path.read_text(encoding='utf-8'))
return state.get('entries', {}).get(PURL, {}).get('record')
def load_json(path):
return json.loads(path.read_text(encoding='utf-8')) if path.is_file() else None
def wired_fragments(project, mode, before=None, after=None):
"""The {original, new} bun.lock line pair for PURL: the vendored ledger's
`bun_lock_package` wiring, or — hosted, which keeps no ledger in v5 — the
text-lock line pinning the hosted UUID in `after` (the lock the CLI wrote)
and the line with the same key in `before` (the registry lock)."""
if mode == 'hosted':
new = next(line for line in after.decode('utf-8').split('\n') if UUID in line)
key = new[:new.index(': [') + 3]
original = next(line for line in before.decode('utf-8').split('\n') if line.startswith(key))
return {'original': original.rstrip('\r'), 'new': new.rstrip('\r')}
wiring = load_json(project / '.socket/vendor/state.json')['entries'][PURL]['wiring']
return next(w for w in wiring if w['file'] in ('bun.lock', 'bun.lockb'))
def hosted_lockb_digest(lockb):
"""The raw sha512 the hosted bun.lockb pins for the patched tarball: the
digest of the hosted artifact its URL names (downloaded — the binary lock
stores the integrity as raw bytes, and v5 keeps no ledger to read it from)."""
match = re.search(rb'https://patch\.socket\.dev/[\x21-\x7e]+?\.tgz', lockb)
if match is None:
raise RuntimeError('the hosted bun.lockb names no patch.socket.dev tarball')
url = match.group(0).decode('ascii')
request = urllib.request.Request(url, headers={'User-Agent': USER_AGENT})
with urllib.request.urlopen(request, timeout=60) as response:
return hashlib.sha512(response.read()).digest()
def wired_line(text, recorded_new):
"""The live bun.lock line carrying the recorded wiring `recorded_new`:
byte-identical, or the digest-less 2-tuple Bun < 1.3.10 re-saves it as
(same `"key": ["spec", {meta}` head, no trailing `"sha512-…"`). None when
neither spelling is present."""
if recorded_new in text:
return recorded_new
head = recorded_new[:recorded_new.rfind(', "sha512-')]
for line in text.splitlines():
if line.startswith(head) and line[len(head):] in (']', '],'):
return line
return None
def crlf_only(data):
return all(line.endswith(b'\r\n') for line in data.splitlines(keepends=True) if line.strip())
def tamper_digests(lock, marker):
"""Replace the sha512 on every tuple line carrying `marker`."""
return b''.join(
re.sub(rb'sha512-[A-Za-z0-9+/=]+(?="\])', b'sha512-' + b'A' * 86 + b'==', line)
if marker in line else line for line in lock.splitlines(keepends=True))
VEX_PRODUCT = 'pkg:npm/bun-patch-backtest@1.0.0'
VEX_OUTPUT = 'out.vex.json'
LEDGERS = ('.socket/vendor/state.json', '.socket/vendor/redirect-state.json')
def manifestless_checkout(project, dest):
"""Copy the committable state of `project` into `dest`: everything but
node_modules, the manifest, the apply lock and earlier VEX output."""
skip = {'.socket/manifest.json', '.socket/apply.lock', VEX_OUTPUT}
if dest.exists():
shutil.rmtree(dest)
for source in project.rglob('*'):
rel = source.relative_to(project)
if 'node_modules' in rel.parts or rel.as_posix() in skip or not source.is_file():
continue
(dest / rel).parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, dest / rel)
dest.mkdir(parents=True, exist_ok=True)
return dest
def vex_statements(doc, purl):
"""The OpenVEX statements whose subcomponent is `purl` (or `purl?…`)."""
def about(statement):
return any(sub.get('@id') == purl or str(sub.get('@id', '')).split('?')[0] == purl
for product in statement.get('products', [])
for sub in product.get('subcomponents', []))
return [st for st in (doc or {}).get('statements', []) if about(st)]
def vex_attested(doc, purl, uuid, marker, vulns):
"""True when `doc` attests `purl` for EXACTLY the vulnerability ids of
`vulns` ({id: [cve aliases]}), each not_affected, carrying every alias
and the `Patched via Socket patch <uuid> (<marker>)` impact part."""
statements = vex_statements(doc, purl)
if not vulns or sorted(st['vulnerability']['name'] for st in statements) != sorted(vulns):
return False
part = f'Patched via Socket patch {uuid} ({marker})'
for st in statements:
aliases = set(st['vulnerability'].get('aliases', []))
if (st.get('status') != 'not_affected'
or not set(vulns[st['vulnerability']['name']]) <= aliases
or part not in str(st.get('impact_statement', '')).split('; ')):
return False
return True
def vex_skip_reason(envelope, purl):
"""The `errorCode` of the standalone vex envelope's skipped event for
`purl` (None when it was verified or never mentioned)."""
canonical = purl.replace('%40', '@')
events = [e for e in (envelope or {}).get('events', [])
if str(e.get('purl', '')).split('?')[0] in (purl, canonical)]
if any(e.get('action') == 'verified' for e in events):
return None
return next((e.get('errorCode') for e in events if e.get('action') == 'skipped'), None)
def remove_node_modules(project):
for modules in sorted(project.rglob('node_modules'), key=lambda p: len(p.parts)):
if modules.exists() and not modules.is_symlink():
shutil.rmtree(modules)
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('--cli', type=Path, required=True)
parser.add_argument('--cli-revision', required=True,
help='the branch-resolvable commit the rows are about (cliRevision)')
parser.add_argument('--cli-build-sha', default=os.environ.get('CLI_BUILD_SHA') or None,
help='the commit the CLI binary was built from (cliBuildSha; on a pull '
'request the refs/pull/N/merge commit, which diverges from the head '
'once main advances) — defaults to $CLI_BUILD_SHA, else null')
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--tools', type=Path)
parser.add_argument('--versions', nargs='+', default=VERSIONS)
parser.add_argument('--shapes', nargs='+', default=SHAPES, choices=SHAPES)
parser.add_argument('--modes', nargs='+', default=MODES, choices=MODES)
parser.add_argument('--jobs', type=int, default=4)
args = parser.parse_args()
root = args.output.resolve()
root.mkdir(parents=True, exist_ok=True)
cli = root / ('socket-patch.exe' if platform.system() == 'Windows' else 'socket-patch')
shutil.copy2(args.cli.resolve(), cli)
toolroot = (args.tools or root / 'tools').resolve()
provenance = dict(capturedAt=datetime.now(timezone.utc).isoformat(),
os=platform.system().lower(), platform=platform.platform(),
cliRevision=args.cli_revision, cliBuildSha=args.cli_build_sha,
cliSha256=sha256(cli.read_bytes()))
jobs = [(v, s, m) for v in args.versions for s in args.shapes for m in args.modes
if cell_applies(v, s, m)]
if not jobs:
# Only an explicit --versions/--shapes/--modes narrowing gets here: the
# default shape list holds `direct`, which applies to every release and
# mode. Not a failure — but the summary must say that nothing ran, so a
# consumer can never mistake it for a green matrix.
reason = ('no (version, shape, mode) cell applies to the requested narrowing: '
f'versions={" ".join(args.versions)} shapes={" ".join(args.shapes)} '
f'modes={" ".join(args.modes)}')
save(root / 'summary.json', [dict(noCells=True, bun=' '.join(args.versions), shape='*',
mode='*', passed=False, error=reason, **provenance)])
print(f'::notice::{reason}', flush=True)
return 0
# The legacy-lockb shape baselines every project with the last binary-only
# release, whatever the matrix version — needed only where such a cell
# applies (the shape is gated to distinct, newer binary readers).
legacy_needed = any(shape == 'legacy-lockb' for _, shape, _ in jobs)
needed = list(dict.fromkeys([*args.versions, *([LEGACY_BUN] if legacy_needed else [])]))
try:
tools = {v: install_tool(toolroot, v) for v in needed}
except Exception as error: # noqa: BLE001 — the artifact must explain the run
rows = [dict(bun=v, shape='*', mode='*', passed=False,
error=f'tool install failed: {error}', **provenance) for v in args.versions]
save(root / 'summary.json', rows)
print('tool install failed:', error, flush=True)
return 1
tool_sha = {v: dict(bunSha256=sha256(path.read_bytes()),
bunArchiveSha256=archive_sha256(toolroot, v)) for v, path in tools.items()}
base_env = {k: v for k, v in os.environ.items()
if not k.startswith(('SOCKET_', 'BUN_', 'npm_config_', 'NPM_CONFIG_'))}
base_env.update(SOCKET_NO_CONFIG='1', SOCKET_NO_UPDATE_CHECK='1', NO_COLOR='1')
def backtest(job):
version, shape, mode = job
expected = expected_outcome(version, shape, mode)
case = root / 'captures' / f'{version}-{shape}-{mode}'
case.mkdir(parents=True, exist_ok=True)
project = case / ('project space café' if shape == 'space-unicode' else 'project')
if project.exists():
shutil.rmtree(project)
project.mkdir()
row = dict(bun=version, shape=shape, mode=mode, passed=False, **provenance,
**tool_sha[version], supported=expected['supported'],
expected=dict(supported=expected['supported'],
refusals=sorted(expected['codes']), exit=expected['exit']))
checks, exit_codes = {}, {}
row['checks'] = checks
row['exitCodes'] = exit_codes
# The two-mode shapes: `pre_mode` is the mode applied first, `main_mode`
# the mode of the command the row is about (its ledger, its envelope).
main_mode = 'hosted' if shape == 'vendored-then-hosted' else mode
pre_mode = 'hosted' if shape == 'hosted-then-vendored' else mode
try:
bun = tools[version]
def env_for(binary, cache):
temporary = case / 'tool-tmp'
temporary.mkdir(exist_ok=True)
return dict(base_env, PATH=str(binary.parent) + os.pathsep + base_env['PATH'],
BUN_INSTALL_CACHE_DIR=str(case / cache),
BUN_INSTALL=str(case / 'bun-home'),
TMPDIR=str(temporary), TMP=str(temporary), TEMP=str(temporary),
BUN_TMPDIR=str(temporary))
env = env_for(bun, 'cache')
def cli_command(verb, run_mode):
return [cli, *verb, '--mode', run_mode,
'--cwd', project, '--json', '--yes', '--no-telemetry']
def install(binary, label, flags=(), cache=None):
remove_node_modules(project)
return run([binary, 'install', '--ignore-scripts', *flags], project,
env_for(binary, cache or 'cache-' + label), case / (label + '.log'), False)
files = project_files(shape)
for name, data in files.items():
(project / name).parent.mkdir(parents=True, exist_ok=True)
(project / name).write_bytes(data)
baseline_bun = tools[LEGACY_BUN] if shape == 'legacy-lockb' else bun
install_args = ['install', '--ignore-scripts']
if shape in TEXT_OPT_IN_SHAPES:
install_args += ['--save-text-lockfile']
# The legacy writer never shares a cache with the matrix release.
run([baseline_bun, *install_args], project,
env_for(baseline_bun, 'cache-legacy' if shape == 'legacy-lockb' else 'cache'),
case / 'baseline.log')
lock = project / ('bun.lock' if (project / 'bun.lock').exists() else 'bun.lockb')
if shape == 'crlf-lock':
lock.write_bytes(lock.read_bytes().replace(b'\r\n', b'\n').replace(b'\n', b'\r\n'))
code, _ = install(bun, 'crlf-accepted', ['--frozen-lockfile'])
checks['crlfLockAccepted'] = code == 0 and crlf_only(lock.read_bytes())
if shape == 'custom-registry':
# bun writes "" for its default registry, so `.npmrc` alone never
# fills the slot: inject the full-URL form bun emits for any other
# registry and prove bun installs from it before the CLI runs.
# Bytes, not text mode: Windows text I/O would turn the lock's LF
# into CRLF, and the restore (which keeps the lock's line endings)
# would then never match the LF pre-injection bytes.
pre_injection = lock.read_bytes()
text = pre_injection.decode('utf-8')
injected = text.replace('["minimist@1.2.2", ""', f'["minimist@1.2.2", "{REGISTRY_SLOT}"')
checks['registrySlotInjected'] = injected != text
lock.write_bytes(injected.encode('utf-8'))
code, _ = install(bun, 'registry-accepted', ['--frozen-lockfile'])
checks['registrySlotAccepted'] = code == 0 and lock.read_bytes() == injected.encode('utf-8')
original = {name: (project / name).read_bytes()
for name in [*files, 'bun.lock', 'bun.lockb'] if (project / name).exists()}
row['originalSha256'] = {n: sha256(b) for n, b in original.items()}
original_binary_dump = None
binary_schema_upgraded = False
if lock.name == 'bun.lockb' and lock.is_file():
_, original_binary_dump = run([bun, lock.name], project, env,
case / 'original-binary-dump.log')
if shape in TEXT_OPT_IN_SHAPES:
checks['textLockWritten'] = 'bun.lock' in original and 'bun.lockb' not in original
if shape == 'legacy-lockb':
checks['legacyLockbBaseline'] = 'bun.lockb' in original and 'bun.lock' not in original
installed = bool(installed_targets(project))
if version in NO_PEER_OR_OVERRIDE and shape in ('peer', 'transitive'):
checks['upstreamNotInstalled'] = not installed
else:
checks['installedBefore'] = installed
if 'bun.lock' in original:
# Registry 4-tuples are digest-verified on every text-lock
# release: the baseline every rewrite is compared against.
scratch = case / 'registry-tamper'
if scratch.exists():
shutil.rmtree(scratch)
shutil.copytree(project, scratch, ignore=shutil.ignore_patterns('node_modules', '.socket'))
tampered = tamper_digests(original['bun.lock'], b'minimist@1.2.2"')
(scratch / 'bun.lock').write_bytes(tampered)
code, output = run([bun, 'install', '--ignore-scripts', '--frozen-lockfile'], scratch,
env_for(bun, 'cache-registry-tamper'), case / 'registry-tamper.log',
False, timeout=60, tolerate_timeout=True)
checks['registryDigestEnforced'] = (
tampered != original['bun.lock'] and code != 0
and ('integrity' in output.lower() or 'checksum' in output.lower()))
if code is None:
row.setdefault('notes', []).append(
'bun printed the registry integrity error but never exited (killed after 60 s)')
shutil.rmtree(scratch, ignore_errors=True)
if shape == 'lockfile-only':
shutil.rmtree(project / 'node_modules')
seeded = seed_manifest(project) if shape == 'preexisting-manifest' else None
if shape in CONVERSION_SHAPES:
# First mode (or the first vendoring): must land and install.
before_conversion = lock.read_bytes()