599 lines
38 KiB
Markdown
599 lines
38 KiB
Markdown
# Promotion Security Review Fixes Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Make the captured candidate archive and a strict exact-five bundle validator the only promotion authority while closing evidence, lifecycle, staging, output, freshness, and workflow gaps found by security review.
|
||
|
||
**Architecture:** Candidate identity and local verification are derived exclusively from the inode-captured tar stream. The finalizer revalidates archived subordinate evidence, provider signatures, freshness, role-separated trust, and then creates and validates an exact-five bundle before returning descriptor- and inode-bound cleanup metadata. Provider execution, CLI output publication, and workflow gating expose small injectable boundaries so failure and cleanup behavior can be tested directly.
|
||
|
||
**Tech Stack:** Node.js 24, TypeScript, Zod, Vitest, GNU tar, bubblewrap, Gitea Actions workflow generation.
|
||
|
||
## Global Constraints
|
||
|
||
- Work sequentially on the current clean `develop` HEAD and produce one review-fix commit.
|
||
- Every production change follows focused RED, observed expected failure, minimal GREEN, and regression verification.
|
||
- Candidate verification performs no checkout reads; test subprocesses from outside the checkout with contradictory canaries.
|
||
- Preserve real GNU tar and bubblewrap coverage; do not claim native uploader or atomic `renameat2`/`unlinkat` semantics.
|
||
- Gitea 1.26.4 and act_runner 1.0.0 exact-five upload/download/cancel behavior remains an explicitly documented external smoke boundary.
|
||
|
||
---
|
||
|
||
### Task 1: Canonical Captured Archive and Archived Local Authority
|
||
|
||
**Files:**
|
||
- Modify: `scripts/lib/release-candidate.ts`
|
||
- Modify: `scripts/lib/local-release-evidence.ts`
|
||
- Modify: `scripts/lib/ci-candidate-archive.ts`
|
||
- Delete: `scripts/lib/promotion-verifier.ts`
|
||
- Delete: `scripts/verify-provider-evidence.ts`
|
||
- Delete: `scripts/verify-supply-chain-promotion.ts`
|
||
- Modify: `tests/unit/security-followup.test.ts`
|
||
- Modify: `tests/unit/supply-chain.test.ts`
|
||
- Modify: `tests/integration/security-followup-archive.test.ts`
|
||
|
||
**Interfaces:**
|
||
- Consumes: captured tar bytes plus expected SHA-256.
|
||
- Produces: `withVerifiedCapturedCandidate()` callback data derived only from the extracted, exact-member, digest-verified tar; `verifyArchivedLocalEvidence()` independently recomputes all feasible archived checks.
|
||
|
||
- [x] Add failing tests for invalid tar, archive/tree mismatch, contradictory archived subordinate FAIL, exact archived policy bytes, and checkout-independent execution.
|
||
- [x] Run focused tests and record the expected RED diagnostics in the durable task report.
|
||
- [x] Archive the exact policy/verifier inputs required for independent release, supply-chain, dependency, license, vulnerability, and secret-scan checks.
|
||
- [x] Re-run producer checks against the extracted archive and require their result to agree with the assessment and member identities.
|
||
- [x] Remove the obsolete standalone PASS issuers and route all fixture checking through real captured tar/finalizer validation.
|
||
- [ ] Run focused archive, supply-chain, and integration tests to GREEN.
|
||
|
||
### Task 2: Exact-Five Validator and Role-Separated Trust
|
||
|
||
**Files:**
|
||
- Create: `scripts/lib/exact-promotion-bundle.ts`
|
||
- Create: `scripts/verify-exact-promotion-bundle.ts`
|
||
- Modify: `scripts/lib/provider-evidence.ts`
|
||
- Modify: `scripts/lib/promotion-stager.ts`
|
||
- Modify: `scripts/contracts/promotion-artifacts.ts`
|
||
- Modify: `tests/unit/security-followup.test.ts`
|
||
- Modify: `tests/unit/ci-artifact-contract.test.ts`
|
||
|
||
**Interfaces:**
|
||
- Consumes: exactly five captured byte buffers and two trusted Ed25519 identities.
|
||
- Produces: `verifyExactPromotionBundle()` that requires literal verifier identity/version, provider and subordinate PASS states, exact hashes, and equal run/source/candidate/provider/trust fields.
|
||
|
||
- [x] Add failing tests for provider FAIL/absence, arbitrary provider hash, swapped roles, shared-field mismatch, archive/report digest mismatch, and identical role keys.
|
||
- [x] Run focused tests and record RED.
|
||
- [x] Implement strict exact-five parsing/cross-record validation and expose a downstream CLI command.
|
||
- [x] Reject equal DER-SPKI fingerprints and equal role key identity before evaluation/finalization.
|
||
- [x] Invoke exact-five validation inside the finalizer before publication; the full real-build fixture rerun remains sandbox-blocked below.
|
||
|
||
### Task 3: Provider Lifecycle and Freshness
|
||
|
||
**Files:**
|
||
- Modify: `scripts/lib/provider-supervisor.ts`
|
||
- Create: `scripts/lib/provider-process-runner.ts`
|
||
- Modify: `scripts/run-and-validate-provider.ts`
|
||
- Modify: `tests/unit/security-followup.test.ts`
|
||
- Modify: `tests/unit/ci-artifact-contract.test.ts`
|
||
|
||
**Interfaces:**
|
||
- Consumes: a spawned bubblewrap child, injected timeout/clock, captured report.
|
||
- Produces: a runner that SIGKILLs on timeout but rejects only after `close`, and supervision that samples freshness after provider/report capture.
|
||
|
||
- [x] Add failing stubborn-descendant/short-timeout and sequence-clock expiry tests.
|
||
- [x] Run focused tests and record RED.
|
||
- [x] Extract the process runner, wait for close after timeout, and preserve the timeout diagnostic.
|
||
- [x] Issue provider timestamps immediately before execution, validate with a fresh clock after capture, and reject crossing expiry.
|
||
- [x] Run focused lifecycle tests, including real stubborn descendants, to GREEN; the shared real-build/bubblewrap fixture remains sandbox-blocked below.
|
||
|
||
### Task 4: Inode-Pinned Staging and Output-Failure Cleanup
|
||
|
||
**Files:**
|
||
- Modify: `scripts/lib/promotion-stager.ts`
|
||
- Create: `scripts/lib/stage-verified-promotion-cli.ts`
|
||
- Modify: `scripts/stage-verified-promotion.ts`
|
||
- Modify: `scripts/cleanup-verified-promotion.ts`
|
||
- Modify: `tests/unit/ci-artifact-contract.test.ts`
|
||
|
||
**Interfaces:**
|
||
- Produces: `FinalizedPromotion.stagingIdentity` and a testable CLI function whose append failure invokes cleanup from the in-memory result.
|
||
|
||
- [x] Add failing tests for leaf replacement during writes, final visibility mismatch, partial-failure cleanup, GITHUB_OUTPUT open/write failure, and expiry during staging.
|
||
- [x] Run focused tests and record RED.
|
||
- [x] Open the created leaf with `O_DIRECTORY|O_NOFOLLOW`, write through `/proc/self/fd/<leafFd>`, pin dev/ino, require visible identity equality, and propagate identity through cleanup.
|
||
- [x] Force directory/file modes with `fchmod(0700/0400)` independent of a restrictive owner-preserving umask.
|
||
- [x] Extract CLI dependencies; on any post-finalization output failure call direct cleanup before rethrowing.
|
||
- [x] Revalidate evidence freshness before sealing and immediately before publication; isolated lifecycle/mode/output tests are GREEN and the shared real-build fixture remains sandbox-blocked below.
|
||
|
||
### Task 5: Workflow and Install Policy
|
||
|
||
**Files:**
|
||
- Modify: `package.json`
|
||
- Modify: `scripts/check-ci-contract.ts`
|
||
- Modify: `scripts/contracts/ci-gates.ts`
|
||
- Modify: `scripts/generate-ci-workflow.ts`
|
||
- Modify: `config/ci/gates.json`
|
||
- Modify: `.gitea/workflows/quality-gates.yml`
|
||
- Modify: `tests/unit/ci-workflow-generation.test.ts`
|
||
- Modify: `tests/unit/__snapshots__/ci-workflow-generation.test.ts.snap`
|
||
|
||
**Interfaces:**
|
||
- Produces: promotion job `if: ${{ always() && needs.immutable_build.result == 'success' && needs.vulnerability_provider.result == 'success' && needs.provenance_provider.result == 'success' }}` and install-bearing script graph enforcement.
|
||
|
||
- [x] Add failing contract/generator tests for the job condition, upload without `always()`, missing cleanup outputs, and nested install scripts lacking `--ignore-scripts`.
|
||
- [x] Run focused tests and record RED.
|
||
- [x] Add `--ignore-scripts` to `verify:lockfile` and recursively reject each reachable install invocation without it.
|
||
- [x] Extend the typed job condition and render the explicit cancellation-resistant exact-needs predicate.
|
||
- [x] Regenerate workflow/snapshot and run workflow contract/byte tests to GREEN.
|
||
|
||
### Task 6: Fixtures, Documentation, Full Verification, and Commit
|
||
|
||
**Files:**
|
||
- Modify: `scripts/check-supply-chain-provider-fixtures.ts`
|
||
- Modify: `docs/security/supply-chain.md`
|
||
- Modify: `docs/operations/ci-quality-gates.md`
|
||
- Modify: `.superpowers/sdd/2026-08-01-release-evidence-remediation/task-4-report.md` (ignored durable report)
|
||
|
||
- [x] Replace plaintext candidate fixtures with a real tar and canonical captured-archive/exact-five validation.
|
||
- [x] Rewrite operator docs around the sole captured-archive/exact-five authority and retain the Gitea/runner external-smoke residual.
|
||
- [ ] Run focused fixtures, archive integration, workflow snapshot/bytes, full unit, types, lint, `check:ci`, and diff checks; escalate only a sandbox-caused EPERM.
|
||
- [x] Append all RED/GREEN and verification evidence/constraints to the durable report.
|
||
- [ ] Invoke verification-before-completion, review the complete diff, commit once, and report commit/range/status.
|
||
|
||
---
|
||
|
||
## Review-Fix Wave D: Sealed Bytes, Replay Context, Scan Trust, and Cancellation
|
||
|
||
**Constraint:** Work only in the existing uncommitted tree. Do not write `.git`, stage, or commit. Each task follows a focused RED→GREEN cycle and records sandbox `EPERM` separately from product failures.
|
||
|
||
### Task D1: Seal the actual staged inode bytes
|
||
|
||
**Files:** `scripts/lib/promotion-stager.ts`, `tests/unit/security-followup.test.ts`, `tests/unit/ci-artifact-contract.test.ts`
|
||
|
||
**Interface:** The staging writer captures each canonical file through the already-open leaf FD using `O_NOFOLLOW`; it requires a regular single-link inode, mode `0400`, stable dev/ino/size, and the declared SHA-256. `verifyExactPromotionBundle` receives only these captured staged buffers immediately before return.
|
||
|
||
- [x] Add RED tests for unlink/recreate and chmod/mutation after a file write.
|
||
- [x] Implement bounded descriptor-relative capture and exact-five seal validation.
|
||
- [x] Run focused staging tests to GREEN.
|
||
|
||
### Task D2: Bind downstream verification to external expected identity
|
||
|
||
**Files:** `scripts/lib/exact-promotion-bundle.ts`, `scripts/verify-exact-promotion-bundle.ts`, `tests/unit/ci-artifact-contract.test.ts`
|
||
|
||
**Interface:** `verifyExactPromotionBundle` requires `expected.run.id`, `expected.run.attempt`, `expected.sourceRevision`, and `expected.archiveSha256`; optional bundle/dist/lock/source-set digests are compared when supplied. The CLI obtains these values from dedicated environment variables and never derives them from the bundle.
|
||
|
||
- [x] Add a RED signed other-run replay test.
|
||
- [x] Implement external expected-context comparison in library and CLI.
|
||
- [x] Run exact-bundle tests to GREEN where the sandbox permits.
|
||
|
||
### Task D3: Pin mkdir-to-open identity
|
||
|
||
**Files:** `scripts/lib/promotion-stager.ts`, `tests/unit/security-followup.test.ts`, `docs/security/supply-chain.md`, `docs/operations/ci-quality-gates.md`
|
||
|
||
**Interface:** A post-mkdir/pre-open test hook can replace the leaf. The implementation compares mkdir-returned pathname metadata with the `O_DIRECTORY|O_NOFOLLOW` handle `fstat` before any write; it never uses pathname chmod.
|
||
|
||
- [x] Add a RED pre-open replacement test.
|
||
- [x] Compare created and opened metadata and reject replacement.
|
||
- [x] Document the residual portable Node same-UID pre-lstat/native-privilege boundary.
|
||
|
||
### Task D4: Conservatively parse install invocations
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
|
||
|
||
**Interface:** A bounded shell/token parser recognizes `pnpm install|i`, `npm install|ci|i`, and `yarn install` after supported manager-global options with split or `=` values. Any reachable package-manager invocation that cannot be classified is rejected.
|
||
|
||
- [x] Add the eight required global-option/alias RED cases plus malformed fail-closed cases.
|
||
- [x] Implement tokenization and manager-specific invocation classification.
|
||
- [x] Run install-policy tests to GREEN.
|
||
|
||
### Task D5: Signed secret-scan attestation
|
||
|
||
**Files:** `scripts/lib/provider-evidence.ts`, `scripts/lib/provider-supervisor.ts`, `scripts/lib/provider-upload-validator.ts`, `scripts/lib/promotion-stager.ts`, `scripts/lib/exact-promotion-bundle.ts`, relevant unit/integration tests and docs.
|
||
|
||
**Interface:** Vulnerability evidence v2 contains a strict `secretScanAttestation` with `status: PASS`, local-assessment, source-set, policy, SARIF, and scan-input digests. The supervisor derives the expected tuple from captured archive members, exports it to the provider, and upload/final verification requires exact equality under the Ed25519 signature.
|
||
|
||
- [x] Add RED forged-empty-SARIF and attestation-mismatch tests.
|
||
- [x] Derive one captured-archive scan context and bind it through supervisor, signed schema, finalizer records, and exact validation.
|
||
- [x] Run provider/security tests to GREEN where the sandbox permits.
|
||
|
||
### Task D6: Cancellation-safe workflow and exact upload paths
|
||
|
||
**Files:** `scripts/contracts/ci-gates.ts`, `scripts/generate-ci-workflow.ts`, `config/ci/gates.json`, generated workflow/snapshot, workflow tests, and operations/security docs.
|
||
|
||
**Interface:** Promotion uses a typed dependency-success/no-job-if variant, so cancellation cannot be overridden by job-level `always()`. Step cleanup retains bare `always()` for ordinary failures. Upload documentation names the five canonical paths under `staging_root` and states cancellation cleanup remains a runner/native smoke boundary.
|
||
|
||
- [x] Add RED generator/contract assertions for no promotion job `if` and retained cleanup `always()`.
|
||
- [x] Regenerate workflow and snapshot after the typed condition change.
|
||
- [x] Correct operator/security wording and run workflow/CI checks to GREEN.
|
||
|
||
### Task D7: Verification
|
||
|
||
- [x] Run focused suites after each GREEN, then affected/full unit tests, all TypeScript targets, ESLint, `check:ci`, generated-byte check, and both diff checks.
|
||
- [x] Append exact PASS totals and sandbox-blocked commands to the ignored durable report.
|
||
- [x] Report modified files and remaining native/Gitea/unsandboxed verification boundaries; do not attempt git staging or commit.
|
||
|
||
## Wave E: Unified parser and downstream boundary review
|
||
|
||
### Task E1: One tokenized manager parser
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
|
||
|
||
**Interface:** One parse result reports manager invocations, package-script dependencies, unsupported controls, and effective lifecycle suppression. Both graph traversal and install policy consume it. Single `&`, unknown manager grammar, and malformed options fail closed. The last valid `--ignore-scripts` assignment controls the effective value; false, contradictory, valueless, and malformed assignments are unsafe. Lifecycle-capable mutation builtins are never implicit repository scripts and require effective suppression.
|
||
|
||
- [x] Add RED tables for single-ampersand segmentation, false/override/malformed suppression, global-option run/implicit dependencies, yarn/corepack reachability, and builtin/script-name collisions.
|
||
- [x] Replace the regex traversal and separate install scan with one parser result.
|
||
- [x] Run the parser-focused and full workflow-generation suites.
|
||
|
||
### Task E2: Evaluator-owned secret-scan equality
|
||
|
||
**Files:** `scripts/lib/provider-evidence.ts`, `tests/unit/security-followup.test.ts`, finalizer tests.
|
||
|
||
**Interface:** `evaluatePromotionEvidence` itself compares the parsed vulnerability report's signed `secretScanAttestation` with `expected.secretScanAttestation`. A mismatch makes vulnerability and overall promotion status `FAIL_UNVERIFIED`, including the production finalizer path.
|
||
|
||
- [x] Add a RED evaluator mismatch test.
|
||
- [x] Implement exact equality before vulnerability PASS assignment.
|
||
- [x] Run security/provider-focused tests.
|
||
|
||
### Task E3: Downstream CLI exact-five contract
|
||
|
||
**Files:** `tests/unit/ci-artifact-contract.test.ts`, `tests/unit/security-followup.test.ts`, `scripts/verify-exact-promotion-bundle.ts` if required.
|
||
|
||
**Interface:** A real finalizer-produced canonical exact-five directory passes the downstream CLI when all required external expected values and trust keys are supplied. Every required expected variable missing or mismatched exits non-zero. Optional digests remain exact when present.
|
||
|
||
- [x] Add RED happy-path and required-env negative coverage using real finalizer output where sandbox execution permits.
|
||
- [x] Make only the minimal CLI/library changes needed for GREEN.
|
||
- [x] Separate child-process sandbox blockers from library assertions.
|
||
|
||
### Task E4: Verification
|
||
|
||
- [x] Run focused parser/security/CLI suites, TypeScript, ESLint, `check:ci`, and `git diff --check`.
|
||
- [x] Run full unit if feasible and report nested-process `EPERM` separately.
|
||
- [x] Do not stage or commit.
|
||
|
||
## Wave F: Manager parser boundary hardening
|
||
|
||
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`, stage, or commit. Add behavior tests before production changes and keep unsupported manager grammar fail-closed.
|
||
|
||
### Task F1: Workspace dispatch and authoritative script lookup
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
|
||
|
||
**Interface:** The parser receives the authoritative root `scripts` record. Explicit `run` resolves a root script; pnpm/yarn implicit dispatch resolves only a known root script. Yarn `workspace` and `workspaces` dispatch are unsupported because the root graph does not load workspace package scripts. Builtin aliases are canonicalized before root-script lookup.
|
||
|
||
- [x] Add RED policy and graph tables for the three Yarn workspace dispatchers, pnpm `ln`, and unknown manager subcommands.
|
||
- [x] Remove workspace dispatchers from safe builtins, pass known root scripts into the parser, and canonicalize `pnpm ln` to lifecycle `link` before implicit lookup.
|
||
- [x] Run the focused dependency/lifecycle cases to GREEN.
|
||
|
||
### Task F2: Shell comments and lifecycle option state
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`
|
||
|
||
**Interface:** Unquoted `#` in a manager-bearing command is unsupported control syntax; quoted `#` remains ordinary token content. Lifecycle options are parsed in order into a canonical suppression state covering `--ignore-scripts`, `--no-ignore-scripts`, and `--config.ignore-scripts`; conflicting, malformed, unknown, or ineffective states fail closed. Other option-like lifecycle arguments require an explicit manager allowlist.
|
||
|
||
- [x] Add RED comment, negative suppression, supported positive, and unknown lifecycle-option tables.
|
||
- [x] Implement comment-aware tokenization and one ordered lifecycle argument parser.
|
||
- [x] Preserve the checked-in `--frozen-lockfile --ignore-scripts` path and run focused tests to GREEN.
|
||
|
||
### Task F3: Verification
|
||
|
||
- [x] Validate every checked-in package script through graph/install consumers without false positives.
|
||
- [x] Run the full workflow-generation file and related security tests.
|
||
- [x] Run all TypeScript targets, ESLint, `check:ci`, and `git diff --check`; report nested-process `EPERM` separately.
|
||
- [x] Update the durable report; do not stage or commit.
|
||
|
||
## Wave G: Verified cleanup and complete gate/parser preflight
|
||
|
||
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`, stage, or commit. Every production change follows a focused failing behavior test.
|
||
|
||
### Task G1: Verified-FD-only failure cleanup
|
||
|
||
**Files:** `scripts/lib/promotion-stager.ts`, `tests/unit/security-followup.test.ts`, security/operations documentation.
|
||
|
||
**Interface:** `openedIdentityVerified` becomes true only after the opened directory descriptor matches the post-`mkdir` device/inode. Failure cleanup may unlink canonical files or `rmdir` only through that verified descriptor and a still-matching visible identity. A mismatched opened descriptor and any visible replacement are close-only; a moved original directory remains for fixture/operator cleanup because portable Node cannot safely recover it.
|
||
|
||
- [x] Change the pre-open replacement regression to require both the replacement canary and displaced original directory to survive the failure.
|
||
- [x] Run the focused test to RED against parent-directory identity scanning.
|
||
- [x] Remove unverified inode discovery/recovery and gate descriptor cleanup on explicit identity verification.
|
||
- [x] Run staging race and cleanup tests to GREEN and document the native residual.
|
||
|
||
### Task G2: Contract-wide lifecycle preflight
|
||
|
||
**Files:** `scripts/contracts/ci-gates.ts`, `scripts/run-ci-gate.ts`, optional focused runner helper, `tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** `loadCiGateContract` runs `validateInstallScriptPolicy` over every unique contract command script after script existence and graph checks. The runner enters its execution callback only after this loader succeeds, enabling a no-execute regression without relying on a nested child process.
|
||
|
||
- [x] Add RED loader tables for contradictory npm suppression, pnpm config false, and `pnpm ln`, plus a production runner-boundary no-execute spy.
|
||
- [x] Enforce contract-command install policy and route runner execution through the preflight boundary.
|
||
- [x] Run loader/runner preflight tests to GREEN.
|
||
|
||
### Task G3: Foreign manifest scope by command class
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** Manager-global options that change cwd, manifest or workspace scope are recorded during parsing. Explicit and implicit package-script dispatch with any such option is unsupported under the root-only graph. Lifecycle commands remain classifiable and are accepted only when their own ordered suppression/option grammar is safe.
|
||
|
||
- [x] Add RED policy+graph tables for pnpm filter/dir/`-C`, npm workspace/prefix, and Yarn cwd dispatch.
|
||
- [x] Add positive externally scoped lifecycle cases with verified suppression.
|
||
- [x] Track scope options and reject only package-script dispatch; run focused tests to GREEN.
|
||
|
||
### Task G4: Argument-sensitive builtin grammar and verification
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`, durable report.
|
||
|
||
**Interface:** Broad command-name-only safe builtins are replaced by exact per-manager read-only invocations. Init/explore/Yarn npm namespaces are unsupported. Audit is accepted only as an exact bare read-only command; `fix` and all unknown arguments are rejected.
|
||
|
||
- [x] Add RED policy+graph coverage for npm init/explore/audit-fix and Yarn npm publish, plus a bare-audit positive.
|
||
- [x] Replace permissive builtin lookup with exact argument grammar.
|
||
- [x] Audit every current package script for graph/policy false positives.
|
||
- [x] Run staging/parser/no-execute/workflow/security suites, all TypeScript targets, ESLint, `check:ci`, and `git diff --check`; record sandbox `EPERM` separately and do not stage or commit.
|
||
|
||
## Wave H: npm post-script scope-option boundary
|
||
|
||
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`, stage, or commit. Reproduce every reviewer command in a failing test before changing the parser.
|
||
|
||
### Task H1: Explicit and implicit npm dispatch arguments
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** After an explicit `npm run`/`run-script` dependency or an implicit npm lifecycle script, manager options before the first literal `--` are parsed using an exact harmless allowlist. Workspace/prefix selectors (`--workspace`, `-w`, `--workspaces`, `--prefix`, including supported attached/equal forms) and unknown manager options fail closed. Tokens after the first literal `--` are script arguments and cannot change the authoritative manifest scope.
|
||
|
||
- [x] Add RED policy-and-graph coverage for all seven reviewer inputs, short/equal forms, unknown pre-delimiter options, the literal `--` boundary, and ordinary current-tree dispatch.
|
||
- [x] Implement one npm post-script argument parser shared by explicit and implicit dispatch.
|
||
- [x] Run focused parser tests to GREEN.
|
||
|
||
### Task H2: Contract loader and runner boundary
|
||
|
||
**Files:** `tests/unit/ci-workflow-generation.test.ts`, contract preflight only if the RED test exposes a separate integration defect.
|
||
|
||
**Interface:** Every reviewer input is rejected by contract loading while the referenced root scripts exist and are otherwise safe. `withCiGatePreflight` must not enter its callback for any rejected command.
|
||
|
||
- [x] Add a table-driven loader/no-callback regression for the same seven reviewer inputs.
|
||
- [x] Run focused preflight tests to GREEN.
|
||
|
||
### Task H3: Verification
|
||
|
||
- [x] Re-audit current package scripts through graph and policy consumers.
|
||
- [x] Run workflow/security suites, all TypeScript targets, ESLint, `check:ci`, and `git diff --check`.
|
||
- [x] Record results in the durable report and do not stage or commit.
|
||
|
||
**Verification evidence:** The focused npm parser/preflight selection passed
|
||
31/31. The workflow file passed 208/210; its two remaining tests reached the
|
||
known nested-spawn sandbox boundary and reported `EPERM`. Security, supply-chain,
|
||
and local-promotion tests passed 64/64. All six TypeScript targets, ESLint,
|
||
`check:ci`, and `git diff --check` passed. Auditing the checked-in package found
|
||
zero policy failures across 109 scripts and zero graph failures across 108
|
||
entries (excluding the intentionally direct runner entry `ci:gate`). A broader
|
||
artifact-contract run passed 102 assertions and blocked 23 fixture cases at the
|
||
same nested `git ls-files` `EPERM` boundary. No `.git` write was performed.
|
||
|
||
Local pnpm 11.17 execution showed post-script `--filter`/`--dir` tokens arriving
|
||
in the root script's argv, and official Yarn run documentation defines all
|
||
parameters after the script name as script arguments. Those pre-existing
|
||
negative expectations were therefore corrected to positive regressions; only
|
||
npm receives the new post-script manager-option grammar.
|
||
|
||
## Wave I: npm hook closure and environment scope
|
||
|
||
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`,
|
||
stage, or commit. Add focused behavior tests and observe RED before each
|
||
production change.
|
||
|
||
### Task I1: npm pre/main/post dependency closure
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`,
|
||
`tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** Explicit npm `run`/`run-script` and implicit
|
||
`start`/`stop`/`restart`/`test` return existing root-manifest lifecycle hooks in
|
||
`pre`, main, `post` order. Hooks are omitted only when ordered manager/tail
|
||
suppression is unambiguously effective before the first literal `--`; bare,
|
||
false, negative, contradictory, malformed, or post-delimiter suppression keeps
|
||
hook traversal active or fails closed.
|
||
|
||
- [x] Add RED policy/graph tables for nested, test, and restart pre/post hooks.
|
||
- [x] Add RED suppression positives and false/negative/contradictory/delimiter negatives.
|
||
- [x] Make npm tail parsing update the invocation suppression state and expand dependencies.
|
||
- [x] Run hook/parser tests to GREEN.
|
||
|
||
### Task I2: Tokenized npm scope environment
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`, `scripts/contracts/ci-gates.ts`,
|
||
`tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** Case-insensitive assignments to `npm_config_workspace`,
|
||
`npm_config_workspaces`, or `npm_config_prefix` fail closed when their shell
|
||
segment executes npm. Direct assignment, `env`, `/usr/bin/env`, and an exported
|
||
assignment inherited by a later npm segment are covered without raw-substring
|
||
false positives for quoted text. `withCiGatePreflight` also rejects the same
|
||
sensitive keys inherited through `process.env` before entering its callback.
|
||
|
||
- [x] Add RED policy/graph coverage for all reviewer assignment forms and quoted/current-tree positives.
|
||
- [x] Add RED loader/no-callback coverage for command assignments and inherited process environment.
|
||
- [x] Implement token/segment assignment state and the preflight environment boundary.
|
||
- [x] Run environment/parser/preflight tests to GREEN.
|
||
|
||
### Task I3: Verification
|
||
|
||
- [x] Audit every current script through graph and policy consumers.
|
||
- [x] Run focused parser/preflight, workflow/security, all TypeScript targets,
|
||
ESLint, `check:ci`, and `git diff --check`.
|
||
- [x] Update durable operations/security documentation and record sandbox-only
|
||
nested spawn failures separately; do not stage or commit.
|
||
|
||
**Verification evidence:** Hook closure began RED 9/9 and GREEN 9/9;
|
||
ordered suppression began with 6 expected failures and finished GREEN 22/22;
|
||
wrapper/export/inherited environment coverage began with 7 expected failures
|
||
and finished GREEN 28/28. A final all-command-class environment RED 3/3
|
||
closed scoped lifecycle and builtin invocations. The combined Wave I focused
|
||
selection passed 59/59. The complete workflow file passed 269/271; its only
|
||
two failures were the existing nested child-spawn `EPERM` fixtures. Security,
|
||
supply-chain, and local-promotion tests passed 64/64. All six TypeScript
|
||
targets, ESLint, `check:ci`, and `git diff --check` passed. The checked-in tree
|
||
had zero policy failures across 109 scripts, zero graph failures across 108
|
||
entries after excluding the intentional direct runner entry `ci:gate`, and no
|
||
sensitive inherited npm scope environment. No `.git` write was performed.
|
||
|
||
## Wave J: coherent npm environment state and hook semantics
|
||
|
||
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`,
|
||
stage, or commit. Add each reviewer form as a failing regression before changing
|
||
the parser.
|
||
|
||
### Task J1: Stateful shell npm-scope environment analysis
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`,
|
||
`tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** Shell segments carry a conservative npm-scope environment state
|
||
to later reachable npm invocations. The parser recognizes any static path whose
|
||
basename is `env`, optionally behind `command`, and rejects case-insensitive
|
||
scope assignments in direct or env-wrapper contexts. Static assignment/export
|
||
and `set -a` transitions are modeled across segments. Dynamic assignment names
|
||
and environment mutations that cannot be modeled accurately (`set +a`,
|
||
`unset`, `export -n`, `eval`, dot/source) make later npm dispatch unsupported.
|
||
Quoted harmless text and non-scope static assignments remain accepted; analysis
|
||
uses token and segment structure rather than raw substring matching.
|
||
|
||
- [x] Add RED policy/graph tables for every reviewer state transition, env path,
|
||
command wrapper, dynamic assignment name, and unsupported mutation.
|
||
- [x] Implement a shared tokenized shell-environment state machine and immediate
|
||
npm invocation environment inspection.
|
||
- [x] Add harmless quoted/static positive regressions and run focused tests GREEN.
|
||
|
||
### Task J2: Actual npm lifecycle-hook suppression semantics
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`,
|
||
`tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** Existing pre/main/post hooks are traversed for `run`,
|
||
`run-script`, `start`, `stop`, `restart`, and `test`. Bare `--ignore-scripts`
|
||
means true and omits hooks just like explicit true. False, negative,
|
||
contradictory, malformed, and post-delimiter forms retain hook traversal or fail
|
||
closed according to the existing ordered grammar.
|
||
|
||
- [x] Add hook safety/order coverage for run-script, start, and stop.
|
||
- [x] Move bare suppression forms to positive regressions and retain all false,
|
||
negative, contradictory, and delimiter negatives.
|
||
- [x] Remove the explicitly-valued distinction and run focused tests GREEN.
|
||
|
||
### Task J3: Contract boundary and verification
|
||
|
||
- [x] Run every environment reviewer command through policy, graph, contract
|
||
loading, and `withCiGatePreflight`, asserting the callback is never entered.
|
||
- [x] Retain the inherited process-environment regression and audit the current
|
||
package tree for policy/graph false positives.
|
||
- [x] Run workflow/security suites, all TypeScript targets, ESLint, `check:ci`,
|
||
and `git diff --check`; record sandbox-only failures and do not stage or commit.
|
||
|
||
**Verification evidence:** The initial Wave J selection produced 38 expected
|
||
failures across loader/policy/graph environment cases and bare hook suppression,
|
||
then passed 84/84 after implementation. A separate unsupported dynamic env-wrapper
|
||
expansion regression went RED 2/2 and GREEN 2/2; the final combined selection
|
||
passed 86/86. The full workflow file passed 327/329, with only the existing two
|
||
nested child-spawn `EPERM` fixtures failing at the sandbox boundary. Security,
|
||
supply-chain, local-promotion, and promotion-readiness tests passed 71/71. All
|
||
six TypeScript targets, ESLint, `check:ci`, and `git diff --check` passed. The
|
||
checked-in tree had zero policy failures across 109 scripts, zero graph failures
|
||
across 108 entries after excluding the intentional direct runner entry `ci:gate`,
|
||
and no sensitive inherited npm scope environment. No `.git` write was performed.
|
||
|
||
## Wave K: common shell-prefix grammar
|
||
|
||
**Constraint:** Continue in the existing uncommitted tree. Do not write `.git`,
|
||
stage, or commit. Every wrapper/prefix reviewer command must be RED in all four
|
||
public enforcement paths before production changes.
|
||
|
||
### Task K1: Shared prefix parser and state-builtin targeting
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`,
|
||
`tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** A single token-based prefix helper consumes leading static
|
||
assignments, then exact `command`/`exec` wrapper chains and their supported
|
||
separator syntax. It reports the effective command token/index, whether parsing
|
||
is uncertain, and the leading assignments. Both immediate npm env inspection
|
||
and persistent `export`/`set` state updates use this result. `command --` is
|
||
accepted; unknown `command` options and unmodeled `exec` options before npm/env
|
||
fail closed. Static paths retain basename-`env` behavior.
|
||
|
||
- [x] Add common policy/graph RED cases for `exec env`, `exec /bin/env`,
|
||
`command exec env`, `command -- env`, assignment-prefixed `export`, and
|
||
assignment-prefixed `set -a`.
|
||
- [x] Reuse the same reviewer table through `loadCiGateContract` and
|
||
`withCiGatePreflight`, asserting rejection and no callback entry.
|
||
- [x] Implement the shared prefix parser, route immediate env inspection and
|
||
state-builtin updates through it, and run the reviewer selection GREEN.
|
||
- [x] Preserve split assignment/export ordering, dynamic LHS, quoted text,
|
||
harmless `MESSAGE=...`, and supported command-wrapper positives.
|
||
|
||
### Task K2: Hook selection and final verification
|
||
|
||
**Files:** `tests/unit/ci-workflow-generation.test.ts`, durable report.
|
||
|
||
- [x] Ensure the final focused selection explicitly includes the bare npm hook
|
||
suppression table as well as prefix/environment policy and runner tests.
|
||
- [x] Run the full workflow file and security/supply/local-promotion suites;
|
||
classify only the known nested-spawn sandbox failures separately.
|
||
- [x] Audit all current scripts through policy and graph, then run all TypeScript
|
||
targets, ESLint, `check:ci`, and `git diff --check`; do not stage or commit.
|
||
|
||
**Verification evidence:** The nine shared shell-prefix reviewer commands began
|
||
RED in both enforcement tables, producing 18 expected failures across
|
||
loader/runner and policy/graph, then passed 18/18 after the common parser was
|
||
connected. The prefix negatives plus harmless positives passed 39/39. The final
|
||
focused selection explicitly combined prefix cases, dynamic environment cases,
|
||
effective/bare npm hook suppression, and harmless positives and passed 106/106.
|
||
The complete workflow file passed 353/355; its only two failures were the known
|
||
nested child-spawn `EPERM` fixtures. Security, supply-chain, local-promotion, and
|
||
promotion-readiness tests passed 71/71. The current package tree had zero policy
|
||
failures across 109 scripts, zero graph failures across 108 entries after
|
||
excluding `ci:gate`, and no sensitive inherited npm scope environment. All six
|
||
TypeScript targets, ESLint, `check:ci`, and `git diff --check` passed. The shared
|
||
workspace was preserved and no `.git` write was performed.
|
||
|
||
## Wave L: structural manager-prefix gap rejection
|
||
|
||
**Constraint:** Continue in the shared uncommitted tree. Do not write `.git`,
|
||
stage, or commit. Generalize the existing parser; do not add wrapper names to an
|
||
allowlist.
|
||
|
||
### Task L1: Reject unmodeled tokens before package managers
|
||
|
||
**Files:** `scripts/lib/package-script-graph.ts`,
|
||
`tests/unit/ci-workflow-generation.test.ts`.
|
||
|
||
**Interface:** The common shell-prefix result identifies the first effective
|
||
command after modeled assignments and `command`/`exec` wrappers. When manager
|
||
scanning later finds a package manager, every token between that effective
|
||
command position and the manager position must belong to a grammar explicitly
|
||
consumed by immediate env or corepack parsing. Otherwise the invocation is
|
||
unsupported. This structural rule covers `nice`, absolute-path `nice`, `nohup`,
|
||
and future unknown wrappers without naming them.
|
||
|
||
- [x] Add policy/graph RED coverage for the four env-wrapper reviewer commands
|
||
and direct unknown-wrapper manager commands (`nice npm`, `time pnpm`).
|
||
- [x] Reuse the env-wrapper reviewer commands through contract loading and
|
||
`withCiGatePreflight`, asserting the callback remains false.
|
||
- [x] Implement one structural gap check in manager parsing and run RED cases
|
||
GREEN without adding wrapper names.
|
||
- [x] Retain modeled assignment, `command`/`exec`/env/corepack, current-tree,
|
||
quoted echo, and harmless assignment positives.
|
||
|
||
### Task L2: Verification
|
||
|
||
- [x] Run a focused selection containing structural negatives and all modeled
|
||
prefix/environment positives.
|
||
- [x] Run the workflow and security/supply/local-promotion suites, current-tree
|
||
policy/graph/environment audit, all TypeScript targets, ESLint, `check:ci`, and
|
||
`git diff --check`; record sandbox-only failures and do not stage or commit.
|
||
|
||
**Verification evidence:** The six unmodeled-prefix reviewer commands began RED
|
||
in both enforcement tables, producing 12 expected loader/runner and policy/graph
|
||
failures, then passed 12/12 after one structural prefix-gap check was added. The
|
||
unmodeled negatives plus harmless/modeled positives passed 36/36. The final
|
||
focused Wave H–L environment/prefix and effective/bare hook selection passed
|
||
121/121. The full workflow file passed 368/370, with only the two known nested
|
||
child-spawn `EPERM` fixtures failing at the sandbox boundary. Security,
|
||
supply-chain, local-promotion, and promotion-readiness tests passed 71/71. The
|
||
current package tree had zero policy failures across 109 scripts, zero graph
|
||
failures across 108 entries after excluding `ci:gate`, and no sensitive inherited
|
||
npm scope environment. All six TypeScript targets, ESLint, `check:ci`, and
|
||
`git diff --check` passed. No wrapper-name allowlist was added, the workspace was
|
||
preserved, and no `.git` write was performed.
|