|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Prove the supply-chain cooldown in bunfig.toml actually rejects a fresh release. |
| 3 | +# |
| 4 | +# Requested by Dima Ryskin (Wix secplatform) as the review for #597: "i think the |
| 5 | +# best 'review' would be a test. Can we stack another PR on top of that and check |
| 6 | +# if installing a fresh-package is rejected? I used |
| 7 | +# npmjs.com/package/electron-nightly for always 'fresh' versions". |
| 8 | +# |
| 9 | +# This MUST run without the Wix embargo gateway. With the gateway in front, |
| 10 | +# embargo would refuse the fresh version itself and the run would prove nothing |
| 11 | +# about Bun's guardrail — which is what the publish workflows actually rely on. |
| 12 | +# |
| 13 | +# Three cases, because "it errored" is not the only pass and not the only failure: |
| 14 | +# control a long-stable package still installs, so a red result means the |
| 15 | +# cooldown fired rather than the probe being broken |
| 16 | +# floating `bun add <pkg>` must not land a version inside the cooldown — |
| 17 | +# either refused, or silently resolved to an older one |
| 18 | +# exact pin `bun add <pkg>@<fresh-version>` must be refused. This is the |
| 19 | +# bypass path that matters: a PR pinning an exact fresh version. |
| 20 | +# |
| 21 | +# Linux/GNU only (runs on ubuntu-latest). Age arithmetic is done in Node to avoid |
| 22 | +# date(1) portability problems. |
| 23 | +set -uo pipefail |
| 24 | + |
| 25 | +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" |
| 26 | +FRESH_PKG="electron-nightly" # publishes nightly, so its latest is always fresh |
| 27 | +CONTROL_PKG="lodash" # unchanged for years; must install |
| 28 | + |
| 29 | +# Read the policy from bunfig.toml rather than hardcoding it, so this test cannot |
| 30 | +# drift away from the setting it is supposed to be verifying. |
| 31 | +COOLDOWN_SECONDS="$(sed -nE 's/^[[:space:]]*minimumReleaseAge[[:space:]]*=[[:space:]]*([0-9]+).*/\1/p' "$REPO_ROOT/bunfig.toml" | head -1)" |
| 32 | +if [ -z "$COOLDOWN_SECONDS" ]; then |
| 33 | + echo "FAIL: no minimumReleaseAge found in bunfig.toml — nothing to verify" |
| 34 | + exit 1 |
| 35 | +fi |
| 36 | +echo "Cooldown under test: ${COOLDOWN_SECONDS}s ($((COOLDOWN_SECONDS / 86400)) days)" |
| 37 | +echo "Bun: $(bun --version)" |
| 38 | +echo |
| 39 | + |
| 40 | +WORK="$(mktemp -d)" |
| 41 | +trap 'rm -rf "$WORK"' EXIT |
| 42 | +cp "$REPO_ROOT/bunfig.toml" "$WORK/bunfig.toml" |
| 43 | +cd "$WORK" |
| 44 | +printf '{ "name": "cooldown-probe", "private": true, "version": "0.0.0" }\n' >package.json |
| 45 | + |
| 46 | +failures=0 |
| 47 | + |
| 48 | +# Age in seconds of a specific published version, per the registry's own metadata. |
| 49 | +published_age_seconds() { |
| 50 | + curl -sS "https://registry.npmjs.org/$1" | node -e " |
| 51 | + let raw = ''; |
| 52 | + process.stdin.on('data', (d) => (raw += d)).on('end', () => { |
| 53 | + const when = JSON.parse(raw).time?.['$2']; |
| 54 | + if (!when) { console.error('no publish time for $1@$2'); process.exit(1); } |
| 55 | + console.log(Math.floor((Date.now() - Date.parse(when)) / 1000)); |
| 56 | + }); |
| 57 | + " |
| 58 | +} |
| 59 | + |
| 60 | +echo "── control: bun add $CONTROL_PKG ──────────────────────────────" |
| 61 | +if bun add "$CONTROL_PKG" >control.log 2>&1; then |
| 62 | + echo "PASS control package installed, so the probe environment works" |
| 63 | +else |
| 64 | + echo "FAIL control package could not install — the probe is broken, not the cooldown" |
| 65 | + sed 's/^/ /' control.log |
| 66 | + failures=$((failures + 1)) |
| 67 | +fi |
| 68 | +echo |
| 69 | + |
| 70 | +echo "── floating: bun add $FRESH_PKG ───────────────────────────────" |
| 71 | +if bun add "$FRESH_PKG" >floating.log 2>&1; then |
| 72 | + resolved="$(node -p "require('$WORK/node_modules/$FRESH_PKG/package.json').version")" |
| 73 | + age="$(published_age_seconds "$FRESH_PKG" "$resolved")" || age="" |
| 74 | + if [ -z "$age" ]; then |
| 75 | + echo "FAIL installed $resolved but could not determine its publish time" |
| 76 | + failures=$((failures + 1)) |
| 77 | + elif [ "$age" -ge "$COOLDOWN_SECONDS" ]; then |
| 78 | + echo "PASS resolved $resolved, published ${age}s ago (>= cooldown)" |
| 79 | + echo " fresh versions were filtered out of resolution" |
| 80 | + else |
| 81 | + echo "FAIL installed $resolved, published only ${age}s ago — inside the cooldown" |
| 82 | + failures=$((failures + 1)) |
| 83 | + fi |
| 84 | +else |
| 85 | + echo "PASS bun refused to install $FRESH_PKG" |
| 86 | + sed 's/^/ /' floating.log | tail -5 |
| 87 | +fi |
| 88 | +echo |
| 89 | + |
| 90 | +echo "── exact pin: bun add $FRESH_PKG@<newest> ─────────────────────" |
| 91 | +newest="$(curl -sS "https://registry.npmjs.org/$FRESH_PKG" | node -e " |
| 92 | + let raw = ''; |
| 93 | + process.stdin.on('data', (d) => (raw += d)).on('end', () => { |
| 94 | + const doc = JSON.parse(raw); |
| 95 | + console.log(doc['dist-tags'].nightly ?? doc['dist-tags'].latest); |
| 96 | + }); |
| 97 | +")" |
| 98 | +newest_age="$(published_age_seconds "$FRESH_PKG" "$newest")" || newest_age="" |
| 99 | +echo "Newest published: $newest (${newest_age:-unknown}s old)" |
| 100 | + |
| 101 | +if [ -n "$newest_age" ] && [ "$newest_age" -ge "$COOLDOWN_SECONDS" ]; then |
| 102 | + echo "SKIP newest version is already older than the cooldown; nothing fresh to reject" |
| 103 | + echo " (unexpected for $FRESH_PKG — check it is still publishing nightly)" |
| 104 | +elif bun add "$FRESH_PKG@$newest" >pinned.log 2>&1; then |
| 105 | + echo "FAIL an exact pin bypassed the cooldown and installed $newest" |
| 106 | + echo " a PR pinning a fresh version would defeat the guardrail" |
| 107 | + failures=$((failures + 1)) |
| 108 | +else |
| 109 | + echo "PASS bun refused the exact fresh pin $newest" |
| 110 | + sed 's/^/ /' pinned.log | tail -5 |
| 111 | +fi |
| 112 | +echo |
| 113 | + |
| 114 | +if [ "$failures" -gt 0 ]; then |
| 115 | + echo "RESULT: $failures check(s) failed — the cooldown does not hold" |
| 116 | + exit 1 |
| 117 | +fi |
| 118 | +echo "RESULT: cooldown holds — fresh releases cannot enter the dependency tree" |
0 commit comments