Skip to content

ci: reject new install-time lifecycle scripts by default - #495

Open
larryjay007 wants to merge 1 commit into
Sub-Rosa-Issue:mainfrom
larryjay007:feat/492-reject-lifecycle-scripts
Open

larryjay007 wants to merge 1 commit into
Sub-Rosa-Issue:mainfrom
larryjay007:feat/492-reject-lifecycle-scripts

Conversation

@larryjay007

Copy link
Copy Markdown

What this does

Adds a dependency-free CI check that rejects install-time lifecycle scripts in workspace manifests, and runs it before pnpm install.

  • scripts/check-lifecycle-scripts.mjs scans the root package.json and every package matched by pnpm-workspace.yaml. It fails on preinstall, install, postinstall, prepare, prepublish and prepublishOnly.
  • scripts/lifecycle-scripts-allowlist.json is empty. An entry is scoped to one manifest path and one script, and needs a reason.
  • It parses JSON only. It never runs a script and never prints a command body; a finding is the manifest path and the script name.
  • It fails rather than skips when a manifest is not valid JSON, when a workspace glob is one it cannot expand (**, negation), and when an allowlist entry matches nothing (so a stale exception cannot linger).
  • The step runs before pnpm install in bindings-check.yml and coverage.yml, the two workflows that install dependencies. contract.yml and docs-links.yml do not run pnpm install.
  • docs/CI.md documents the check and how maintainers review an exception. Root scripts: pnpm lifecycle-scripts:check and pnpm lifecycle-scripts:test.

Verification

  • node --test scripts/check-lifecycle-scripts.test.mjs: 19 tests pass. They cover each of the six script names, the root manifest, every workspace glob, near-miss names (install:docs), allowlist match/stale/malformed entries, invalid JSON, an unsupported glob, a missing examples/ directory, and that the command body never reaches the output.
  • The checker passes on this repository (no lifecycle scripts today).
  • Mutation checks: dropping prepare from the forbidden list fails a test, and leaking a command body into the output fails 10. The file was restored byte for byte afterwards.
  • scripts/check-logging.mjs still passes (the checker uses the repo logger, no console).

Not verified: the two workflow edits have not run on a GitHub runner yet; they only call node on files already in the checkout, so they need no install.

Closes #492

@drips-wave

drips-wave Bot commented Oct 21, 2026

Copy link
Copy Markdown

@larryjay007 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@karagozemin

Copy link
Copy Markdown
Collaborator

The dependency-free scanner and its tests are sound, but the PR now conflicts with main and no longer covers every install path: web.yml and the newly merged line-lengths.yml also run pnpm install, while this diff guards only bindings and coverage. Please rebase and place the check before installation in all four workflows (or centralize it so future install jobs cannot omit it).

…sue#492)

No workspace package defines an install-time lifecycle script today. Adding
preinstall, install, postinstall or prepare later would create a new
automatic execution path on every contributor machine and CI runner.

scripts/check-lifecycle-scripts.mjs reads the root package.json and every
package matched by pnpm-workspace.yaml and fails on preinstall, install,
postinstall, prepare, prepublish or prepublishOnly unless the manifest path
and script are listed in scripts/lifecycle-scripts-allowlist.json. The
allowlist starts empty and each entry needs a reason.

The checker parses JSON only. It never runs a script and never prints a
command body: a finding names the manifest path and the script name. It also
fails on a manifest that is not valid JSON, on a workspace glob it cannot
expand, and on an allowlist entry that matches nothing, so none of those can
turn into a silent pass.

The check runs in the two workflows that install dependencies, before
pnpm install. docs/CI.md describes it and how maintainers review an
exception.
@larryjay007
larryjay007 force-pushed the feat/492-reject-lifecycle-scripts branch from 2940753 to 833381b Compare October 23, 2026 06:22
@larryjay007

Copy link
Copy Markdown
Author

Rebased onto current main (single commit, no merge commit) and updated as requested.

  • The lifecycle-script check now runs before the install in all four workflows that run pnpm install: bindings-check.yml, coverage.yml, web.yml and the newly merged line-lengths.yml.
  • To make sure a future install job cannot omit it, scripts/check-lifecycle-scripts.mjs now also reads .github/workflows and fails if a job runs pnpm install (or npm ci, npm install, yarn install) before running the check. It reports lifecycle-install-unguarded with the file and line. docs/CI.md describes this.
  • 29 scanner tests pass locally, including the workflow-scan cases, and the check passes on the repo as it stands.

Note: the red jobs on this PR (round-contract, web, coverage, bindings-check) are also red on main; none of them involve these files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(ci): reject new install-time lifecycle scripts by default

2 participants