Skip to content

fix: harden scaffold — execFileSync, re-run guard, slug validation#38

Merged
tonytino merged 3 commits into
mainfrom
fix/23-24-scaffold-hardening
Jun 10, 2026
Merged

fix: harden scaffold — execFileSync, re-run guard, slug validation#38
tonytino merged 3 commits into
mainfrom
fix/23-24-scaffold-hardening

Conversation

@tonytino

Copy link
Copy Markdown
Owner

Closes #23 and #24 (both rewrite scripts/scaffold.mjs, so combined into one PR).

#23 — shell-injection / error swallowing

  • Replaced the interpolated execSync(\gh label create "..."`)withexecFileSync("gh", [...argv]), so label fields containing quotes, backticks, $, ;`, etc. can never break or inject into a shell command.
  • The underlying gh error (not installed / not authenticated / wrong repo) is now surfaced once instead of being swallowed by an empty catch {}.

#24 — robustness

  • Re-run guard — aborts with a clear message if .construct already exists (already scaffolded, or a prior run failed partway).
  • Slug validationslugify now collapses non-alphanumeric runs to single hyphens and trims leading/trailing ones; falls back to my-project when a name slugifies to nothing, so we never write an invalid package.json name.
  • delete pkg.scripts.scaffold instead of relying on JSON.stringify dropping undefined.
  • Partial-state error — if a file mutation throws mid-run, the user gets a clear message + git checkout . && git clean -fd recovery hint instead of a raw stack trace. (True rollback isn't feasible once files are written/deleted; this is the pragmatic interpretation.)
  • AC item "next-steps .env consistency" was already handled in [Task]: Fix documentation & setup-instruction accuracy #26.

Verification (replicated scaffold flow)

  • Normal run scaffolds and self-cleans; scaffolded tsc / vitest (10/10) / biome pass.
  • Re-run guard aborts (exit 1) on a pre-existing .construct.
  • Project name "!!!" falls back to package name my-project.
  • A failing gh (no auth/remote) surfaces its error and continues.

🤖 Generated with Claude Code

tonytino and others added 3 commits June 10, 2026 15:49
…loses #23, #24)

#23 (shell injection):
- Replace the interpolated `execSync("gh label create ...")` with
  `execFileSync("gh", [...])`, so label fields containing quotes/backticks/$/;
  can never break or inject into a shell command.
- Surface the underlying gh error (not installed / not authenticated / wrong
  repo) at least once instead of swallowing it in an empty catch.

#24 (robustness):
- Re-run guard: abort with a clear message if `.construct` already exists
  (already scaffolded, or a previous run failed partway).
- Harden slugify (collapse non-alphanumeric runs to single hyphens, trim
  leading/trailing) and fall back to "my-project" when a name slugifies to
  nothing — never write an invalid package.json name.
- `delete pkg.scripts.scaffold` instead of relying on JSON.stringify dropping
  an `undefined` value.
- Surface a clear partial-state error (with a git reset hint) if a file
  mutation throws mid-run, rather than a raw stack trace. True rollback isn't
  feasible once files are written/deleted.

Verified by replicating the scaffold flow: normal run scaffolds + self-cleans
(scaffolded tsc/vitest/biome pass); the re-run guard aborts on a pre-existing
.construct; "!!!" falls back to my-project; and a failing gh surfaces its error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
biome's lint/performance/noDelete rule rejected `delete pkg.scripts.scaffold`,
failing CI. Rebuild `pkg.scripts` without the `scaffold` key via
Object.fromEntries/filter instead — still a clean key removal (not the
incidental JSON.stringify undefined-drop the issue called out), and lint-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tonytino tonytino merged commit edac15f into main Jun 10, 2026
3 checks passed
@tonytino tonytino deleted the fix/23-24-scaffold-hardening branch June 10, 2026 22:06
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.

[Task]: scaffold.mjs builds a shell command from label fields (injection-prone)

1 participant