From 42cb58b3520ea6c55ba67c145b05012efb2f7896 Mon Sep 17 00:00:00 2001 From: Guy Ofeck Date: Tue, 11 Aug 2026 15:00:02 +0300 Subject: [PATCH 1/4] ci: Bun cooldown for installs, no gateway in publish, OIDC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the interim policy Dima Ryskin set out for OSS repos, so GitHub Actions can be re-enabled for base44/cli at the org level: 1. enforce lock-files on all node builds, so the build never overrides one 2. enforce a package manager that supports a minimal-age directive 3. use embargo in non-publish workflow tasks Bun satisfies point 2 natively, so no package manager migration is needed: bun install --minimum-release-age= Only install packages published at least N seconds ago (security feature) bunfig.toml sets `minimumReleaseAge = 604800` (7 days; Bun takes SECONDS, unlike npm's min-release-age, which takes days). Point 1 was already satisfied by `bun install --frozen-lockfile`, which is what every workflow runs. That choice matters operationally, not just aesthetically: Wix-managed machines block registry.npmjs.org at the network-extension layer, so no developer here can regenerate an npm lockfile without routing through the embargo gateway or an internal mirror. A Bun-native cooldown keeps bun.lock as the single source of truth and needs no lockfile regeneration at all. Point 3 — the publish workflows no longer run the embargo gateway: - Drops the `sudo sed -i /etc/hosts` unpin hack from both. There is no pin to strip now, so the window where the gateway was bypassed is gone entirely. - Safe because those jobs resolve nothing: `--frozen-lockfile` installs bun.lock verbatim, so dropping the gateway does not widen what they can pull. - check_wix_proxy_steps.py enforces the split bidirectionally: non-publish jobs must run the proxy, publish jobs must not. Exemptions are keyed on exact filename and printed on success, so the list cannot quietly grow. Also closes the remaining unprotected registry fetches in the publish path: - Authenticate via npm trusted publishing (OIDC). Drops NODE_AUTH_TOKEN / secrets.NPM_TOKEN from preview-publish; manual-publish never had a credential wired at all, so it could not have published regardless. - Replace `bunx json-bump` with `npm version` and `npm pkg set`. json-bump is declared in no manifest, so bunx fetched it at run time — outside bun.lock and outside the cooldown, since bunx accepts --minimum-release-age without enforcing it (oven-sh/bun#30748). - Remove `npm install -g npm@latest` from both publish workflows. - Least-privilege per-job permissions replace the workflow-level blocks; drops manual-publish's unused packages: write and pull-requests: read. Known Bun gaps, documented in bunfig.toml and docs/AGENTS.md rather than left to be discovered: bunx does not enforce the flag (#30748), `bun update --latest` skips transitive deps (#25305), and there is no bypass for vulnerability fixes (#26065) short of minimumReleaseAgeExcludes. Verified: check_wix_proxy_steps.py reports 12 of 12 non-publish jobs across 11 workflows with both publish jobs confirmed to abstain; 12 of 12 unit tests pass (4 new, covering both directions of the rule). Co-Authored-By: Claude Opus 5 (1M context) --- .github/scripts/check_wix_proxy_steps.py | 69 ++++++++++++++++--- .github/scripts/test_check_wix_proxy_steps.py | 64 +++++++++++++++++ .github/workflows/manual-publish.yml | 63 ++++++++++------- .github/workflows/preview-publish.yml | 60 ++++++++++------ bunfig.toml | 24 +++++++ docs/AGENTS.md | 2 +- 6 files changed, 224 insertions(+), 58 deletions(-) create mode 100644 bunfig.toml diff --git a/.github/scripts/check_wix_proxy_steps.py b/.github/scripts/check_wix_proxy_steps.py index 8110a74d..c039421f 100644 --- a/.github/scripts/check_wix_proxy_steps.py +++ b/.github/scripts/check_wix_proxy_steps.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 """Fail if any GitHub Actions job skips the mandatory Wix gateway proxy action. -There is no opt-out marker by design. A job that genuinely cannot run the proxy -(a non-ubuntu runner, say) changes this script in the same PR, so the exception -gets reviewed in the open. +There is still no per-job opt-out marker by design. A job that genuinely cannot +run the proxy changes this script in the same PR, so the exception gets reviewed +in the open — which is exactly how PUBLISH_WORKFLOWS below came to exist. + +The rule is bidirectional: non-publish jobs must run the proxy, and publish jobs +must not. """ from __future__ import annotations @@ -19,6 +22,28 @@ # so a sparse checkout has to materialize both directories. REQUIRED_PATHS = (".github/actions/wix-gateway-proxy", ".github/certs") +# Workflows that must NOT route npm through the embargo gateway. +# +# secplatform's interim policy for OSS repos (Dima Ryskin): use embargo for +# non-publish tasks, and protect publish tasks with an enforced lockfile +# (`bun install --frozen-lockfile`) plus a package manager that honors a +# minimal-age directive (`minimumReleaseAge` in bunfig.toml) instead. Those jobs +# resolve nothing, so dropping the gateway does not widen what they can pull. +# The gateway cannot carry a publish today — `npm publish` sends +# `PUT /`, which misses its `^~ /-/` passthrough block, and it sets no +# client_max_body_size so nginx's 1 MB default rejects a packument carrying the +# base64 tarball. +# +# Keyed on exact filename, and every exemption is printed on success, so this +# cannot quietly grow. Revisit once the embargo publish bug is fixed: these +# workflows should go back to being gatewayed like everything else. +PUBLISH_WORKFLOWS = frozenset( + { + ".github/workflows/manual-publish.yml", + ".github/workflows/preview-publish.yml", + } +) + FIX_HINT = """Every job must run the Wix gateway proxy immediately after a checkout that puts it on disk, or that job's npm installs bypass the Wix embargo gateway. @@ -118,6 +143,20 @@ def job_problem(job: dict, workflows: frozenset[str]) -> str | None: return _checkout_problem(steps[0]) +def publish_job_problem(job: dict) -> str | None: + """Describe why this publish job wrongly runs the proxy, or None if it abstains.""" + if "uses" in job: + return None + steps = job.get("steps") or [] + if any(_uses(step) == PROXY_ACTION for step in steps): + return ( + "runs the Wix gateway proxy, but publish workflows must not: the gateway " + "cannot carry `npm publish`, so these workflows rely on the committed " + "lockfile plus min-release-age in .npmrc instead" + ) + return None + + def _job_lines(text: str) -> dict[str, int]: document = yaml.compose(text) if document is None: @@ -133,16 +172,23 @@ def main(repo_root: pathlib.Path = REPO_ROOT) -> int: paths = sorted(p for p in workflows_dir.iterdir() if p.suffix in (".yml", ".yaml")) workflows = frozenset(p.relative_to(repo_root).as_posix() for p in paths) problems = [] - jobs = calls = 0 + jobs = calls = exempt = 0 + exempt_paths = set() for path in paths: + rel = path.relative_to(repo_root).as_posix() text = path.read_text(encoding="utf-8") lines = _job_lines(text) for job_id, job in ((yaml.safe_load(text) or {}).get("jobs") or {}).items(): job = job or {} - jobs += 1 - calls += "uses" in job - problem = job_problem(job, workflows) + if rel in PUBLISH_WORKFLOWS: + exempt += 1 + exempt_paths.add(rel) + problem = publish_job_problem(job) + else: + jobs += 1 + calls += "uses" in job + problem = job_problem(job, workflows) if problem: problems.append((path.relative_to(repo_root), lines[job_id], job_id, problem)) @@ -156,9 +202,14 @@ def main(repo_root: pathlib.Path = REPO_ROOT) -> int: print( f"Wix gateway proxy: verified {jobs - calls} of {jobs} jobs across " - f"{len(paths)} workflows ({calls} reusable-workflow calls delegate to " - f"the workflow they call)." + f"{len(paths) - len(exempt_paths)} workflows ({calls} reusable-workflow calls " + f"delegate to the workflow they call)." ) + if exempt: + print( + f"Publish workflows exempt by policy, and verified to abstain " + f"({exempt} job(s)): " + ", ".join(sorted(exempt_paths)) + ) return 0 diff --git a/.github/scripts/test_check_wix_proxy_steps.py b/.github/scripts/test_check_wix_proxy_steps.py index a407a0f4..dbdcede8 100644 --- a/.github/scripts/test_check_wix_proxy_steps.py +++ b/.github/scripts/test_check_wix_proxy_steps.py @@ -331,6 +331,70 @@ def test_job_with_no_body_is_reported_as_missing_the_proxy(self): self.assertIn('Job "build" does not run the Wix gateway proxy.', output) +class PublishExemptionTests(unittest.TestCase): + """Publish workflows must abstain from the proxy; everything else must run it.""" + + PUBLISH_WITHOUT_PROXY = textwrap.dedent("""\ + name: Manual Package Publish + on: + workflow_dispatch: + + jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm ci + - run: npm publish + """) + + PUBLISH_WITH_PROXY = textwrap.dedent("""\ + name: Package Preview Publish + on: + pull_request: + + jobs: + publish-preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Wix gateway proxy (mandatory) + uses: ./.github/actions/wix-gateway-proxy + - run: npm publish + """) + + def test_publish_workflow_may_omit_the_proxy(self): + with fixture_repo(**{"manual-publish": self.PUBLISH_WITHOUT_PROXY}) as root: + code, output = run_main(root) + + self.assertEqual(code, 0) + self.assertIn("exempt by policy", output) + self.assertIn(".github/workflows/manual-publish.yml", output) + + def test_exempt_jobs_are_not_counted_as_verified(self): + with fixture_repo( + **{"manual-publish": self.PUBLISH_WITHOUT_PROXY, "good": COMPLIANT_WORKFLOW} + ) as root: + code, output = run_main(root) + + self.assertEqual(code, 0) + self.assertIn("verified 1 of 1 jobs across 1 workflows", output) + + def test_publish_workflow_running_the_proxy_is_rejected(self): + with fixture_repo(**{"preview-publish": self.PUBLISH_WITH_PROXY}) as root: + code, output = run_main(root) + + self.assertEqual(code, 1) + self.assertIn("but publish workflows must not", output) + + def test_a_non_publish_workflow_still_needs_the_proxy(self): + with fixture_repo(**{"some-publish-helper": self.PUBLISH_WITHOUT_PROXY}) as root: + code, output = run_main(root) + + self.assertEqual(code, 1) + self.assertIn("does not run the Wix gateway proxy", output) + + class RepositoryTests(unittest.TestCase): def test_every_job_in_this_repository_runs_the_proxy(self): self.assertEqual(checker.main(), 0) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 89234c24..f5fc9160 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -27,19 +27,33 @@ on: env: CLI_PACKAGE_DIR: packages/cli +# This workflow deliberately does NOT run the Wix gateway proxy. +# +# secplatform's interim policy for OSS repos (Dima Ryskin): use embargo for +# non-publish tasks, and protect publish tasks with an enforced lockfile plus a +# package manager honoring a minimal-age directive — here `minimumReleaseAge` in +# bunfig.toml. The gateway cannot carry a publish today: `npm publish` sends +# `PUT /`, which matches neither its `^~ /-/` passthrough block nor +# `~ \.tgz$` and so lands in `location /` (proxy_metadata, a read path with +# caching); it also sets no client_max_body_size, so nginx's 1 MB default rejects +# a packument carrying the base64 tarball. This workflow used to pin the registry +# and then strip the pin from /etc/hosts just before publishing; that hack is gone. +# +# `bun install --frozen-lockfile` below installs bun.lock verbatim and resolves +# nothing, so removing the gateway does not widen what this job can pull. +# +# check-wix-proxy.yml enforces the split: the proxy is mandatory everywhere +# except the two publish workflows, where it is forbidden. jobs: publish: runs-on: ubuntu-latest + permissions: + # contents: write for the release commit, tag, and GitHub Release. + # id-token: write for npm trusted publishing (OIDC). + contents: write + id-token: write steps: - - name: Checkout for wix gateway proxy - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - sparse-checkout: .github - - - name: Wix gateway proxy (mandatory) - uses: ./.github/actions/wix-gateway-proxy - - name: Generate a token id: generate-token uses: actions/create-github-app-token@v2 @@ -60,8 +74,9 @@ jobs: node-version-file: ".node-version" registry-url: "https://registry.npmjs.org" - - name: Update npm - run: npm install -g npm@latest + # No `npm install -g npm@latest`. Trusted publishing needs npm >= 11.5.1 and + # setup-node resolves `.node-version` (24) to the newest 24.x, which bundles + # npm >= 11.17.0. The upgrade was also a needless registry fetch. - name: Setup Bun id: setup-bun @@ -82,13 +97,13 @@ jobs: - name: Set version working-directory: ${{ env.CLI_PACKAGE_DIR }} + # `npm version` takes both a keyword (patch/minor/major) and an explicit + # version, so it replaces the old branch. It also replaces `bunx json-bump`: + # json-bump is declared in no manifest, so bunx fetched it from the registry + # at run time — outside bun.lock, and outside the cooldown, since bunx + # accepts --minimum-release-age without enforcing it (oven-sh/bun#30748). run: | - VERSION_INPUT="${{ github.event.inputs.version }}" - if [[ "$VERSION_INPUT" =~ ^(patch|minor|major)$ ]]; then - bunx json-bump package.json --$VERSION_INPUT - else - bunx json-bump package.json --replace="$VERSION_INPUT" - fi + npm version "${{ github.event.inputs.version }}" --no-git-tag-version --no-workspaces echo "NEW_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV - name: Build package @@ -120,11 +135,13 @@ jobs: echo "NPM tag: ${{ github.event.inputs.npm_tag }}" echo "Dry run: ${{ github.event.inputs.dry_run }}" - - name: Unpin npm registry for first-party publish - # The embargo would refuse the just-built version; installs above stayed gatewayed. - run: sudo sed -i '/registry\.npmjs\.org/d' /etc/hosts - - name: Publish to NPM + # Authenticates via npm trusted publishing (OIDC) using `id-token: write` + # above — no NODE_AUTH_TOKEN/NPM_TOKEN, so no npm credential reaches the + # build. Requires a trusted publisher for `base44` registered on npmjs.com + # against this repo AND this workflow filename (the registry keys on the + # filename, so each publish workflow needs its own entry). Provenance is + # then generated automatically. working-directory: ${{ env.CLI_PACKAGE_DIR }} run: | # Remove devDependencies before publish (everything is bundled) @@ -190,9 +207,3 @@ jobs: "release_url": "${{ env.RELEASE_URL }}", "release_name": "Release v${{ env.NEW_VERSION }}" } - -permissions: - contents: write - packages: write - pull-requests: read - id-token: write diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml index 225235f9..49f9bf7f 100644 --- a/.github/workflows/preview-publish.yml +++ b/.github/workflows/preview-publish.yml @@ -4,9 +4,32 @@ on: pull_request: types: [opened, synchronize, reopened] +# This workflow deliberately does NOT run the Wix gateway proxy. +# +# secplatform's interim policy for OSS repos (Dima Ryskin): use embargo for +# non-publish tasks, and protect publish tasks with an enforced lockfile plus a +# package manager honoring a minimal-age directive — here `minimumReleaseAge` in +# bunfig.toml. The gateway cannot carry a publish today: `npm publish` sends +# `PUT /`, which matches neither its `^~ /-/` passthrough block nor +# `~ \.tgz$` and so lands in `location /` (proxy_metadata, a read path with +# caching); it also sets no client_max_body_size, so nginx's 1 MB default rejects +# a packument carrying the base64 tarball. This workflow used to pin the registry +# and then strip the pin from /etc/hosts just before publishing; that hack is gone. +# +# `bun install --frozen-lockfile` below installs bun.lock verbatim and resolves +# nothing, so removing the gateway does not widen what this job can pull. +# +# check-wix-proxy.yml enforces the split: the proxy is mandatory everywhere +# except the two publish workflows, where it is forbidden. jobs: publish-preview: runs-on: ubuntu-latest + permissions: + # id-token: write for npm trusted publishing (OIDC). + # pull-requests: write for the install-instructions comment. + contents: read + id-token: write + pull-requests: write defaults: run: working-directory: packages/cli @@ -15,19 +38,15 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Wix gateway proxy (mandatory) - uses: ./.github/actions/wix-gateway-proxy - + # No `npm install -g npm@latest`. Trusted publishing needs npm >= 11.5.1 and + # setup-node resolves `.node-version` (24) to the newest 24.x, which bundles + # npm >= 11.17.0. The upgrade was also a needless registry fetch. - name: Setup Node.js uses: actions/setup-node@v4 with: node-version-file: ".node-version" registry-url: "https://registry.npmjs.org" - - name: Update npm - run: npm install -g npm@latest - working-directory: . - - name: Setup Bun id: setup-bun uses: oven-sh/setup-bun@v2 @@ -90,14 +109,16 @@ jobs: exit 1 fi - # Update name with error handling - if ! bunx json-bump package.json --entry=name --replace="$PREVIEW_PACKAGE"; then + # `npm pkg set` replaces `bunx json-bump`: json-bump is declared in no + # manifest, so bunx fetched it from the registry at run time — outside + # bun.lock, and outside the cooldown, since bunx accepts + # --minimum-release-age without enforcing it (oven-sh/bun#30748). + if ! npm pkg set name="$PREVIEW_PACKAGE"; then echo "❌ ERROR: Failed to set package name to $PREVIEW_PACKAGE" exit 1 fi - # Update version with error handling - if ! bunx json-bump package.json --replace="${{ steps.preview_info.outputs.version }}"; then + if ! npm pkg set version="${{ steps.preview_info.outputs.version }}"; then echo "❌ ERROR: Failed to set package version to ${{ steps.preview_info.outputs.version }}" exit 1 fi @@ -120,13 +141,13 @@ jobs: echo "✅ Safety check passed. Package name is safe to publish." - - name: Unpin npm registry for first-party publish - # The embargo would refuse the just-built version; installs above stayed gatewayed. - run: sudo sed -i '/registry\.npmjs\.org/d' /etc/hosts - - name: Publish preview package - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # Authenticates via npm trusted publishing (OIDC) using `id-token: write` + # above — NODE_AUTH_TOKEN/secrets.NPM_TOKEN removed so no npm credential + # reaches the build. Requires a trusted publisher for `@base44-preview/cli` + # registered on npmjs.com against this repo AND this workflow filename (the + # registry keys on the filename, so this workflow needs its own entry, + # separate from manual-publish.yml). run: | # Remove devDependencies before publish (everything is bundled) jq 'del(.devDependencies)' package.json > package.json.tmp && mv package.json.tmp package.json @@ -231,8 +252,3 @@ jobs: await createOrUpdateComment(context.issue.number); } } - -permissions: - contents: read - pull-requests: write - id-token: write diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 00000000..baa3a344 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,24 @@ +[install] +# Supply-chain cooldown — Wix secplatform interim policy for OSS repos. +# +# Bun resolves only package versions published at least this long ago, so a +# compromised release cannot enter bun.lock inside the window. This is what lets +# the publish workflows run without the embargo gateway, which cannot carry +# `npm publish` today (see .github/workflows/manual-publish.yml). +# +# Units are SECONDS, unlike npm's `min-release-age`, which takes days. +# 604800 = 7 days. +# +# Applies at resolution time: `bun install` without a lockfile entry, `bun add`, +# `bun update`. `bun install --frozen-lockfile`, which is all CI runs, installs +# bun.lock verbatim and never resolves, so it is unaffected by design. +# +# Known gaps, accepted knowingly rather than discovered later: +# - `bunx` accepts --minimum-release-age but does not enforce it +# (oven-sh/bun#30748). Do not fetch tooling with bunx in a release path; +# the publish workflows use `npm version` / `npm pkg set` instead. +# - `bun update --latest` skips the cooldown for transitive deps +# (oven-sh/bun#25305). +# - No bypass when an update fixes a known vulnerability +# (oven-sh/bun#26065) — use minimumReleaseAgeExcludes for that case. +minimumReleaseAge = 604800 diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 453cff78..99f98c5f 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -61,7 +61,7 @@ bun run lint:fix # Biome - auto-fix These apply to every task. See topic guides below for domain-specific rules. -1. **Bun for everything** - Use `bun` commands for install, test, build, run +1. **Bun for everything** - Use `bun` commands for install, test, build, run. `bunfig.toml` sets `minimumReleaseAge` (7 days, in **seconds**), a supply-chain cooldown: Bun will not resolve a version published more recently, so a compromised release cannot enter `bun.lock`. It applies to `bun add`/`bun update`, never to `bun install --frozen-lockfile` (which resolves nothing). Two traps: **never fetch tooling with `bunx` in a release path** — it accepts `--minimum-release-age` without enforcing it ([oven-sh/bun#30748](https://github.com/oven-sh/bun/issues/30748)) — and `bun update --latest` skips the cooldown for transitive deps ([#25305](https://github.com/oven-sh/bun/issues/25305)). Use `minimumReleaseAgeExcludes` when a security fix must land inside the window 2. **Zod validation** - Required for all external data (API responses, config files) 3. **@clack/prompts only** - For all user interaction (prompts, spinners, logs). No `console.log`. Under the global `--json` flag the lifecycle runs **silent** (prompts and spinners suppressed, logs routed to stderr) — never assume a TTY 4. **ES Modules** - Use `.js` extensions in all imports From 9f69e84f2ac4ea46c730c51f57726f305eb7e27c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 13:58:49 +0000 Subject: [PATCH 2/4] =?UTF-8?q?ci:=20address=20review=20=E2=80=94=20trim?= =?UTF-8?q?=20comments,=20drop=20the=20reverse=20proxy=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check_wix_proxy_steps.py: skip workflows listed in PUBLISH_WORKFLOWS instead of also asserting they don't run the proxy, per review. Cut the rationale block down to the security note about adding entries. - bunfig.toml, publish workflows, AGENTS.md: cut the long comment blocks to the facts that change what a reader does. --- .../check_wix_proxy_steps.cpython-311.pyc | Bin 0 -> 12278 bytes .github/scripts/check_wix_proxy_steps.py | 65 +++++------------- .github/scripts/test_check_wix_proxy_steps.py | 26 +------ .github/workflows/manual-publish.yml | 44 ++++-------- .github/workflows/preview-publish.yml | 45 ++++-------- bunfig.toml | 29 ++------ docs/AGENTS.md | 2 +- 7 files changed, 55 insertions(+), 156 deletions(-) create mode 100644 .github/scripts/__pycache__/check_wix_proxy_steps.cpython-311.pyc diff --git a/.github/scripts/__pycache__/check_wix_proxy_steps.cpython-311.pyc b/.github/scripts/__pycache__/check_wix_proxy_steps.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..138e5a00f0afd27ea8b07b0f5e82698eda4f6ec7 GIT binary patch literal 12278 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 W{D{ibYYeJVAz_Vfi literal 0 HcmV?d00001 diff --git a/.github/scripts/check_wix_proxy_steps.py b/.github/scripts/check_wix_proxy_steps.py index c039421f..c33e95a9 100644 --- a/.github/scripts/check_wix_proxy_steps.py +++ b/.github/scripts/check_wix_proxy_steps.py @@ -1,12 +1,9 @@ #!/usr/bin/env python3 """Fail if any GitHub Actions job skips the mandatory Wix gateway proxy action. -There is still no per-job opt-out marker by design. A job that genuinely cannot -run the proxy changes this script in the same PR, so the exception gets reviewed -in the open — which is exactly how PUBLISH_WORKFLOWS below came to exist. - -The rule is bidirectional: non-publish jobs must run the proxy, and publish jobs -must not. +There is no per-job opt-out marker by design. A job that genuinely cannot run the +proxy changes this script in the same PR, so the exception gets reviewed in the +open — which is exactly how PUBLISH_WORKFLOWS below came to exist. """ from __future__ import annotations @@ -22,21 +19,12 @@ # so a sparse checkout has to materialize both directories. REQUIRED_PATHS = (".github/actions/wix-gateway-proxy", ".github/certs") -# Workflows that must NOT route npm through the embargo gateway. -# -# secplatform's interim policy for OSS repos (Dima Ryskin): use embargo for -# non-publish tasks, and protect publish tasks with an enforced lockfile -# (`bun install --frozen-lockfile`) plus a package manager that honors a -# minimal-age directive (`minimumReleaseAge` in bunfig.toml) instead. Those jobs -# resolve nothing, so dropping the gateway does not widen what they can pull. -# The gateway cannot carry a publish today — `npm publish` sends -# `PUT /`, which misses its `^~ /-/` passthrough block, and it sets no -# client_max_body_size so nginx's 1 MB default rejects a packument carrying the -# base64 tarball. +# Publish workflows, exempt from the gateway by secplatform's interim policy for +# OSS repos: the gateway cannot carry `npm publish`, so these rely on the +# committed lockfile plus bunfig.toml's minimumReleaseAge instead. # -# Keyed on exact filename, and every exemption is printed on success, so this -# cannot quietly grow. Revisit once the embargo publish bug is fixed: these -# workflows should go back to being gatewayed like everything else. +# Adding a file here drops its embargo protection. That is a security decision, +# not a formality — do not do it lightly. PUBLISH_WORKFLOWS = frozenset( { ".github/workflows/manual-publish.yml", @@ -143,20 +131,6 @@ def job_problem(job: dict, workflows: frozenset[str]) -> str | None: return _checkout_problem(steps[0]) -def publish_job_problem(job: dict) -> str | None: - """Describe why this publish job wrongly runs the proxy, or None if it abstains.""" - if "uses" in job: - return None - steps = job.get("steps") or [] - if any(_uses(step) == PROXY_ACTION for step in steps): - return ( - "runs the Wix gateway proxy, but publish workflows must not: the gateway " - "cannot carry `npm publish`, so these workflows rely on the committed " - "lockfile plus min-release-age in .npmrc instead" - ) - return None - - def _job_lines(text: str) -> dict[str, int]: document = yaml.compose(text) if document is None: @@ -172,23 +146,21 @@ def main(repo_root: pathlib.Path = REPO_ROOT) -> int: paths = sorted(p for p in workflows_dir.iterdir() if p.suffix in (".yml", ".yaml")) workflows = frozenset(p.relative_to(repo_root).as_posix() for p in paths) problems = [] - jobs = calls = exempt = 0 + jobs = calls = 0 exempt_paths = set() for path in paths: rel = path.relative_to(repo_root).as_posix() + if rel in PUBLISH_WORKFLOWS: + exempt_paths.add(rel) + continue text = path.read_text(encoding="utf-8") lines = _job_lines(text) for job_id, job in ((yaml.safe_load(text) or {}).get("jobs") or {}).items(): job = job or {} - if rel in PUBLISH_WORKFLOWS: - exempt += 1 - exempt_paths.add(rel) - problem = publish_job_problem(job) - else: - jobs += 1 - calls += "uses" in job - problem = job_problem(job, workflows) + jobs += 1 + calls += "uses" in job + problem = job_problem(job, workflows) if problem: problems.append((path.relative_to(repo_root), lines[job_id], job_id, problem)) @@ -205,11 +177,8 @@ def main(repo_root: pathlib.Path = REPO_ROOT) -> int: f"{len(paths) - len(exempt_paths)} workflows ({calls} reusable-workflow calls " f"delegate to the workflow they call)." ) - if exempt: - print( - f"Publish workflows exempt by policy, and verified to abstain " - f"({exempt} job(s)): " + ", ".join(sorted(exempt_paths)) - ) + if exempt_paths: + print("Publish workflows exempt by policy: " + ", ".join(sorted(exempt_paths))) return 0 diff --git a/.github/scripts/test_check_wix_proxy_steps.py b/.github/scripts/test_check_wix_proxy_steps.py index dbdcede8..5345b712 100644 --- a/.github/scripts/test_check_wix_proxy_steps.py +++ b/.github/scripts/test_check_wix_proxy_steps.py @@ -332,7 +332,7 @@ def test_job_with_no_body_is_reported_as_missing_the_proxy(self): class PublishExemptionTests(unittest.TestCase): - """Publish workflows must abstain from the proxy; everything else must run it.""" + """Workflows listed in PUBLISH_WORKFLOWS are skipped; everything else is checked.""" PUBLISH_WITHOUT_PROXY = textwrap.dedent("""\ name: Manual Package Publish @@ -348,21 +348,6 @@ class PublishExemptionTests(unittest.TestCase): - run: npm publish """) - PUBLISH_WITH_PROXY = textwrap.dedent("""\ - name: Package Preview Publish - on: - pull_request: - - jobs: - publish-preview: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Wix gateway proxy (mandatory) - uses: ./.github/actions/wix-gateway-proxy - - run: npm publish - """) - def test_publish_workflow_may_omit_the_proxy(self): with fixture_repo(**{"manual-publish": self.PUBLISH_WITHOUT_PROXY}) as root: code, output = run_main(root) @@ -380,14 +365,7 @@ def test_exempt_jobs_are_not_counted_as_verified(self): self.assertEqual(code, 0) self.assertIn("verified 1 of 1 jobs across 1 workflows", output) - def test_publish_workflow_running_the_proxy_is_rejected(self): - with fixture_repo(**{"preview-publish": self.PUBLISH_WITH_PROXY}) as root: - code, output = run_main(root) - - self.assertEqual(code, 1) - self.assertIn("but publish workflows must not", output) - - def test_a_non_publish_workflow_still_needs_the_proxy(self): + def test_a_workflow_not_on_the_list_still_needs_the_proxy(self): with fixture_repo(**{"some-publish-helper": self.PUBLISH_WITHOUT_PROXY}) as root: code, output = run_main(root) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index f5fc9160..31b0dfb6 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -27,23 +27,12 @@ on: env: CLI_PACKAGE_DIR: packages/cli -# This workflow deliberately does NOT run the Wix gateway proxy. -# -# secplatform's interim policy for OSS repos (Dima Ryskin): use embargo for -# non-publish tasks, and protect publish tasks with an enforced lockfile plus a -# package manager honoring a minimal-age directive — here `minimumReleaseAge` in -# bunfig.toml. The gateway cannot carry a publish today: `npm publish` sends -# `PUT /`, which matches neither its `^~ /-/` passthrough block nor -# `~ \.tgz$` and so lands in `location /` (proxy_metadata, a read path with -# caching); it also sets no client_max_body_size, so nginx's 1 MB default rejects -# a packument carrying the base64 tarball. This workflow used to pin the registry -# and then strip the pin from /etc/hosts just before publishing; that hack is gone. -# -# `bun install --frozen-lockfile` below installs bun.lock verbatim and resolves -# nothing, so removing the gateway does not widen what this job can pull. -# -# check-wix-proxy.yml enforces the split: the proxy is mandatory everywhere -# except the two publish workflows, where it is forbidden. +# This workflow deliberately does NOT run the Wix gateway proxy: the gateway +# cannot carry `npm publish` (it rejects `PUT /`), so per secplatform's +# interim policy for OSS repos this job relies on `--frozen-lockfile` plus +# bunfig.toml's minimumReleaseAge instead. It resolves nothing, so dropping the +# gateway does not widen what it can pull. Exemption lives in +# .github/scripts/check_wix_proxy_steps.py. jobs: publish: runs-on: ubuntu-latest @@ -74,9 +63,8 @@ jobs: node-version-file: ".node-version" registry-url: "https://registry.npmjs.org" - # No `npm install -g npm@latest`. Trusted publishing needs npm >= 11.5.1 and - # setup-node resolves `.node-version` (24) to the newest 24.x, which bundles - # npm >= 11.17.0. The upgrade was also a needless registry fetch. + # No `npm install -g npm@latest`: trusted publishing needs npm >= 11.5.1, and + # the npm bundled with .node-version's Node 24 is already newer. - name: Setup Bun id: setup-bun @@ -98,10 +86,8 @@ jobs: - name: Set version working-directory: ${{ env.CLI_PACKAGE_DIR }} # `npm version` takes both a keyword (patch/minor/major) and an explicit - # version, so it replaces the old branch. It also replaces `bunx json-bump`: - # json-bump is declared in no manifest, so bunx fetched it from the registry - # at run time — outside bun.lock, and outside the cooldown, since bunx - # accepts --minimum-release-age without enforcing it (oven-sh/bun#30748). + # version, so it replaces the old branch. It also replaces `bunx json-bump`, + # which fetched an undeclared package at run time, outside the cooldown. run: | npm version "${{ github.event.inputs.version }}" --no-git-tag-version --no-workspaces echo "NEW_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV @@ -136,12 +122,10 @@ jobs: echo "Dry run: ${{ github.event.inputs.dry_run }}" - name: Publish to NPM - # Authenticates via npm trusted publishing (OIDC) using `id-token: write` - # above — no NODE_AUTH_TOKEN/NPM_TOKEN, so no npm credential reaches the - # build. Requires a trusted publisher for `base44` registered on npmjs.com - # against this repo AND this workflow filename (the registry keys on the - # filename, so each publish workflow needs its own entry). Provenance is - # then generated automatically. + # Authenticates via npm trusted publishing (OIDC), so no NPM_TOKEN reaches + # the build. Needs a trusted publisher for `base44` on npmjs.com registered + # against this repo and this workflow filename — the registry keys on the + # filename, so each publish workflow needs its own entry. working-directory: ${{ env.CLI_PACKAGE_DIR }} run: | # Remove devDependencies before publish (everything is bundled) diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml index 49f9bf7f..010cedb9 100644 --- a/.github/workflows/preview-publish.yml +++ b/.github/workflows/preview-publish.yml @@ -4,23 +4,12 @@ on: pull_request: types: [opened, synchronize, reopened] -# This workflow deliberately does NOT run the Wix gateway proxy. -# -# secplatform's interim policy for OSS repos (Dima Ryskin): use embargo for -# non-publish tasks, and protect publish tasks with an enforced lockfile plus a -# package manager honoring a minimal-age directive — here `minimumReleaseAge` in -# bunfig.toml. The gateway cannot carry a publish today: `npm publish` sends -# `PUT /`, which matches neither its `^~ /-/` passthrough block nor -# `~ \.tgz$` and so lands in `location /` (proxy_metadata, a read path with -# caching); it also sets no client_max_body_size, so nginx's 1 MB default rejects -# a packument carrying the base64 tarball. This workflow used to pin the registry -# and then strip the pin from /etc/hosts just before publishing; that hack is gone. -# -# `bun install --frozen-lockfile` below installs bun.lock verbatim and resolves -# nothing, so removing the gateway does not widen what this job can pull. -# -# check-wix-proxy.yml enforces the split: the proxy is mandatory everywhere -# except the two publish workflows, where it is forbidden. +# This workflow deliberately does NOT run the Wix gateway proxy: the gateway +# cannot carry `npm publish` (it rejects `PUT /`), so per secplatform's +# interim policy for OSS repos this job relies on `--frozen-lockfile` plus +# bunfig.toml's minimumReleaseAge instead. It resolves nothing, so dropping the +# gateway does not widen what it can pull. Exemption lives in +# .github/scripts/check_wix_proxy_steps.py. jobs: publish-preview: runs-on: ubuntu-latest @@ -38,9 +27,8 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # No `npm install -g npm@latest`. Trusted publishing needs npm >= 11.5.1 and - # setup-node resolves `.node-version` (24) to the newest 24.x, which bundles - # npm >= 11.17.0. The upgrade was also a needless registry fetch. + # No `npm install -g npm@latest`: trusted publishing needs npm >= 11.5.1, and + # the npm bundled with .node-version's Node 24 is already newer. - name: Setup Node.js uses: actions/setup-node@v4 with: @@ -109,10 +97,8 @@ jobs: exit 1 fi - # `npm pkg set` replaces `bunx json-bump`: json-bump is declared in no - # manifest, so bunx fetched it from the registry at run time — outside - # bun.lock, and outside the cooldown, since bunx accepts - # --minimum-release-age without enforcing it (oven-sh/bun#30748). + # `npm pkg set` replaces `bunx json-bump`, which fetched an undeclared + # package at run time, outside the cooldown. if ! npm pkg set name="$PREVIEW_PACKAGE"; then echo "❌ ERROR: Failed to set package name to $PREVIEW_PACKAGE" exit 1 @@ -142,12 +128,11 @@ jobs: echo "✅ Safety check passed. Package name is safe to publish." - name: Publish preview package - # Authenticates via npm trusted publishing (OIDC) using `id-token: write` - # above — NODE_AUTH_TOKEN/secrets.NPM_TOKEN removed so no npm credential - # reaches the build. Requires a trusted publisher for `@base44-preview/cli` - # registered on npmjs.com against this repo AND this workflow filename (the - # registry keys on the filename, so this workflow needs its own entry, - # separate from manual-publish.yml). + # Authenticates via npm trusted publishing (OIDC), so no NPM_TOKEN reaches + # the build. Needs a trusted publisher for `@base44-preview/cli` on + # npmjs.com registered against this repo and this workflow filename — the + # registry keys on the filename, so this needs its own entry, separate from + # manual-publish.yml. run: | # Remove devDependencies before publish (everything is bundled) jq 'del(.devDependencies)' package.json > package.json.tmp && mv package.json.tmp package.json diff --git a/bunfig.toml b/bunfig.toml index baa3a344..058789c4 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,24 +1,7 @@ [install] -# Supply-chain cooldown — Wix secplatform interim policy for OSS repos. -# -# Bun resolves only package versions published at least this long ago, so a -# compromised release cannot enter bun.lock inside the window. This is what lets -# the publish workflows run without the embargo gateway, which cannot carry -# `npm publish` today (see .github/workflows/manual-publish.yml). -# -# Units are SECONDS, unlike npm's `min-release-age`, which takes days. -# 604800 = 7 days. -# -# Applies at resolution time: `bun install` without a lockfile entry, `bun add`, -# `bun update`. `bun install --frozen-lockfile`, which is all CI runs, installs -# bun.lock verbatim and never resolves, so it is unaffected by design. -# -# Known gaps, accepted knowingly rather than discovered later: -# - `bunx` accepts --minimum-release-age but does not enforce it -# (oven-sh/bun#30748). Do not fetch tooling with bunx in a release path; -# the publish workflows use `npm version` / `npm pkg set` instead. -# - `bun update --latest` skips the cooldown for transitive deps -# (oven-sh/bun#25305). -# - No bypass when an update fixes a known vulnerability -# (oven-sh/bun#26065) — use minimumReleaseAgeExcludes for that case. -minimumReleaseAge = 604800 +# Supply-chain cooldown: Bun resolves only versions published at least this long +# ago, so a compromised release cannot enter bun.lock inside the window. Seconds, +# unlike npm's min-release-age (days). Applies to `bun add`/`bun update`, not to +# `bun install --frozen-lockfile` (resolves nothing) and not to `bunx`, which +# accepts the flag without enforcing it (oven-sh/bun#30748). +minimumReleaseAge = 604800 # 7 days diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 99f98c5f..5c06c8f2 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -61,7 +61,7 @@ bun run lint:fix # Biome - auto-fix These apply to every task. See topic guides below for domain-specific rules. -1. **Bun for everything** - Use `bun` commands for install, test, build, run. `bunfig.toml` sets `minimumReleaseAge` (7 days, in **seconds**), a supply-chain cooldown: Bun will not resolve a version published more recently, so a compromised release cannot enter `bun.lock`. It applies to `bun add`/`bun update`, never to `bun install --frozen-lockfile` (which resolves nothing). Two traps: **never fetch tooling with `bunx` in a release path** — it accepts `--minimum-release-age` without enforcing it ([oven-sh/bun#30748](https://github.com/oven-sh/bun/issues/30748)) — and `bun update --latest` skips the cooldown for transitive deps ([#25305](https://github.com/oven-sh/bun/issues/25305)). Use `minimumReleaseAgeExcludes` when a security fix must land inside the window +1. **Bun for everything** - Use `bun` commands for install, test, build, run. `bunfig.toml` sets a 7-day `minimumReleaseAge` supply-chain cooldown on `bun add`/`bun update`, so a freshly published version cannot enter `bun.lock`. Never fetch tooling with `bunx` in a release path — it ignores the cooldown ([oven-sh/bun#30748](https://github.com/oven-sh/bun/issues/30748)) 2. **Zod validation** - Required for all external data (API responses, config files) 3. **@clack/prompts only** - For all user interaction (prompts, spinners, logs). No `console.log`. Under the global `--json` flag the lifecycle runs **silent** (prompts and spinners suppressed, logs routed to stderr) — never assume a TTY 4. **ES Modules** - Use `.js` extensions in all imports From 47a949fc1c03d2afbcb5d914568f90a57f8ccad7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 14:02:41 +0000 Subject: [PATCH 3/4] ci: raise the bun cooldown from 7 to 14 days --- bunfig.toml | 2 +- docs/AGENTS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bunfig.toml b/bunfig.toml index 058789c4..e3f3e4de 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -4,4 +4,4 @@ # unlike npm's min-release-age (days). Applies to `bun add`/`bun update`, not to # `bun install --frozen-lockfile` (resolves nothing) and not to `bunx`, which # accepts the flag without enforcing it (oven-sh/bun#30748). -minimumReleaseAge = 604800 # 7 days +minimumReleaseAge = 1209600 # 14 days diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 5c06c8f2..d683d139 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -61,7 +61,7 @@ bun run lint:fix # Biome - auto-fix These apply to every task. See topic guides below for domain-specific rules. -1. **Bun for everything** - Use `bun` commands for install, test, build, run. `bunfig.toml` sets a 7-day `minimumReleaseAge` supply-chain cooldown on `bun add`/`bun update`, so a freshly published version cannot enter `bun.lock`. Never fetch tooling with `bunx` in a release path — it ignores the cooldown ([oven-sh/bun#30748](https://github.com/oven-sh/bun/issues/30748)) +1. **Bun for everything** - Use `bun` commands for install, test, build, run. `bunfig.toml` sets a 14-day `minimumReleaseAge` supply-chain cooldown on `bun add`/`bun update`, so a freshly published version cannot enter `bun.lock`. Never fetch tooling with `bunx` in a release path — it ignores the cooldown ([oven-sh/bun#30748](https://github.com/oven-sh/bun/issues/30748)) 2. **Zod validation** - Required for all external data (API responses, config files) 3. **@clack/prompts only** - For all user interaction (prompts, spinners, logs). No `console.log`. Under the global `--json` flag the lifecycle runs **silent** (prompts and spinners suppressed, logs routed to stderr) — never assume a TTY 4. **ES Modules** - Use `.js` extensions in all imports From 08d1bbc219a443e85bc0b9ac62249805293bcc2d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 14:10:25 +0000 Subject: [PATCH 4/4] chore: drop the accidentally committed __pycache__ artifact --- .../check_wix_proxy_steps.cpython-311.pyc | Bin 12278 -> 0 bytes .gitignore | 4 ++++ 2 files changed, 4 insertions(+) delete mode 100644 .github/scripts/__pycache__/check_wix_proxy_steps.cpython-311.pyc diff --git a/.github/scripts/__pycache__/check_wix_proxy_steps.cpython-311.pyc b/.github/scripts/__pycache__/check_wix_proxy_steps.cpython-311.pyc deleted file mode 100644 index 138e5a00f0afd27ea8b07b0f5e82698eda4f6ec7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12278 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 W{D{ibYYeJVAz_Vfi diff --git a/.gitignore b/.gitignore index 6ea7b7d3..ed29b187 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,7 @@ error-reports/ # Deno lockfile — generated when running deno against backend-runtime/ or fixtures. # The repo uses Bun for dependencies; this is a local artifact. deno.lock + +# Python bytecode — from running the .github/scripts checks locally. +__pycache__/ +*.pyc