feat(miner): add a gittensory-miner init --interactive first-run onboarding wizard - #5621
Conversation
| mkdirSync(stateDir, { recursive: true, mode: 0o700 }); | ||
| const envFilePath = resolveWizardEnvFilePath(env); | ||
| writeFileSync(envFilePath, renderWizardEnvFile(entries)); | ||
| chmodSync(envFilePath, 0o600); |
There was a problem hiding this comment.
P2: .env file written with default permissions before chmodSync restricts access
.env created with default umask permissions before chmodSync locks it down.
Pass { mode: 0o600 } to writeFileSync to create the file with secure permissions atomically.
AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.
<file name="packages/gittensory-miner/lib/init-wizard.js">
<violation number="1" location="packages/gittensory-miner/lib/init-wizard.js:95">
<priority>P2</priority>
<title>.env file written with default permissions before chmodSync restricts access</title>
<evidence>writeFileSync(envFilePath, renderWizardEnvFile(entries)); followed by chmodSync(envFilePath, 0o600); creates the credential file with the process's default umask permissions, leaving a small but exploitable window where other users or processes could read the freshly written GITHUB_TOKEN.</evidence>
<recommendation>Pass { mode: 0o600 } as the third argument to writeFileSync so the file is created atomically with restrictive permissions, eliminating the TOCTOU race condition.</recommendation>
</violation>
</file>
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
…arding wizard First-time operators had to hand-author a .env with GITHUB_TOKEN and MINER_CODING_AGENT_PROVIDER with no prompts and no validation until a later doctor run or a failed live attempt. Adds packages/gittensory-miner/lib/init-wizard.js, wired behind a new `init --interactive` flag: prompts for GITHUB_TOKEN with masked, never-echoed input, an optional coding-agent provider from the same CODING_AGENT_DRIVER_NAMES menu the driver factory resolves, and that provider's skippable model/timeout companion vars. Writes the collected values to a starter .env in the state dir, then reruns the existing offline doctor checks so the operator sees pass/fail immediately. Non-interactive init is untouched; no new network calls are introduced.
writeFileSync created the file at the default umask permissions for an
instant before the follow-up chmodSync locked it down to 0600, briefly
exposing the collected GITHUB_TOKEN in cleartext to any other local reader.
Passing { mode: 0o600 } to writeFileSync closes that window for a newly
created file; chmodSync still runs afterward to correct the mode on a
pre-existing file from an earlier run.
2382806 to
a112be8
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5621 +/- ##
=======================================
Coverage 95.01% 95.02%
=======================================
Files 575 576 +1
Lines 45738 45797 +59
Branches 14661 14661
=======================================
+ Hits 43459 43518 +59
Misses 1528 1528
Partials 751 751
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-13 12:07:10 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 5 non-blocking
Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
Summary
gittensory-miner initpreviously only created an empty state dir with no guided setup — first-timeoperators had to hand-author a
.envreferencingGITHUB_TOKENandMINER_CODING_AGENT_PROVIDERwith noprompts and no validation until a later
doctorrun or a failed live attempt.packages/gittensory-miner/lib/init-wizard.js(+.d.ts), wired behind a new opt-ingittensory-miner init --interactiveflag:GITHUB_TOKENwith masked, never-echoed input; an empty answer is rejected and re-prompted.CODING_AGENT_DRIVER_NAMES(noop/claude-cli/codex-cli/agent-sdk) — so the choices can never driftfrom what the driver factory actually resolves. Pressing Enter skips provider selection entirely
(leaves
MINER_CODING_AGENT_PROVIDERunwritten, deferring to whatever default the CLI already resolves) —distinct from explicitly choosing the
noopentry.from the same
CODING_AGENT_DRIVER_CONFIG_ENVmap the real driver factory reads — each individuallyskippable with Enter.
.envin the miner state dir (mode0600), then automaticallyreruns the existing offline
doctorchecks (status.js'srunDoctor) against the collected values so theoperator sees pass/fail immediately.
*instead of thetyped characters while readline still does its normal cooked-mode line editing (Enter/Backspace work exactly
as with a plain prompt) — no raw-mode byte handling and no new dependency.
input/outputstreams areparameters (defaulting to real stdio) purely so tests drive the exact same code path with fake streams
instead of a real terminal — this let me write a REGRESSION test proving the raw secret literally never
appears anywhere in the captured output stream, not just that a mocked prompt function was called correctly.
--interactivemakes no network calls of its own beyond whatdoctoralready makes (none — it'soffline by contract). Non-interactive
initinvocations are completely untouched (the existingrunInitinlaptop-init.jsis not modified at all; the new dispatch lives one layer up, inbin/gittensory-miner.js,so there is zero risk of behavior drift on the default path).
Fixes #5176
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally —packages/gittensory-miner/lib/**/*.jsis in this repo'scodecov/patchgate; targeted coverage run (--coverage.includescoped toinit-wizard.js): 100% statements, 100% branches, 100% functions, 100% lines.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run build:minernpm run test:miner-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateRan the full local gate:
npm run test:ci(0 failures) andnpm audit --audit-level=moderate(0 vulnerabilities), both clean on the final commit. Also directly ran the exactFORBIDDEN_PATH/FORBIDDEN_CONTENTregexes fromscripts/check-miner-package.mjs/scripts/forbidden-content.mjsagainst every new/touched file — zero matches.npm run build:minerwas extended withnode --check lib/init-wizard.js, and confirmed the file packs correctly vianpm run test:miner-pack.New
test/unit/miner-init-wizard.test.ts(17 tests):renderWizardEnvFile/resolveWizardEnvFilePathpure-function tests;promptProviderSelectioncovering skip-on-empty, re-prompt-on-invalid-then-valid, and that every menu entry is drawn from the liveCODING_AGENT_DRIVER_NAMESlist;promptCompanionVarscovering a provider with vars, one without (noop), and a defensive unknown-provider default; fullrunInteractiveInitorchestration tests asserting the written.envfile's exact content and0600mode, laptop-state initialization, a token-required re-prompt-on-empty path, provider + companion-var entries written correctly with the skipped var omitted, the "(already existed)" branch on a second run, and — the key invariant — that the rawGITHUB_TOKENvalue never appears anywhere in anything written to the terminal, including the final doctor summary; direct tests of the realcreateWizardIoterminal adapter driven over fake TTY-like streams (not mocked away), including a REGRESSION test proving masked input never writes the raw secret to the output stream at the byte level; and a lightweight e2e test spawning the realbin/gittensory-miner.js init --interactiveconfirming the CLI actually dispatches to the wizard's distinct prompt path.Safety
GITHUB_TOKEN) interactively, the REGRESSION test described above directly verifies it is never echoed/logged, including at the byte level in the real terminal-output stream.UI Evidencesection below. — N/A, see below.README.mdandDEPLOYMENT.mdwith a short mention of the new flag, andlib/cli.js's--helptext.UI Evidence
N/A — this is a local terminal CLI feature (one new flag) with no visible UI surface. No screenshots apply.
Notes
runInit/laptop-init.jsat all — the--interactivedispatch lives inbin/gittensory-miner.js, one layer above, so the non-interactive path's byte-for-byte-unchanged requirementis satisfied by construction rather than by a test asserting it after the fact.
CLAUDE_CODE_OAUTH_TOKEN/codexauth.json— those already have theirown dedicated CLI auth flows (
claude setup-token,codex auth) outside this wizard's scope; the issue's ownscope is credential-and-provider-only (GITHUB_TOKEN + provider + provider config), not a port of those flows.
readlinemasking trick works correctlyover non-TTY fake streams — this is what made the real terminal adapter itself directly unit-testable rather
than an untested "trust it" branch, and is what the REGRESSION test above exercises.