fix(selfhost): reject known-placeholder and weak critical secrets at boot - #2686
Merged
Conversation
…boot .env.selfhost.example shipped ENABLED (not commented-out) placeholder values for GITHUB_WEBHOOK_SECRET, GITTENSORY_API_TOKEN, GITTENSORY_MCP_TOKEN, INTERNAL_JOB_TOKEN, and SELFHOST_SETUP_TOKEN. An operator who copies the starter to .env per the quickstart docs and misses "fill in the placeholders" runs an instance with a PUBLICLY KNOWN webhook HMAC secret (forgeable signatures) and PUBLICLY KNOWN static bearer tokens -- GITTENSORY_API_TOKEN authenticates as the server-to-server actor and bypasses per-repo write checks, INTERNAL_JOB_TOKEN gates internal routes -- silently, with no error at boot or runtime. - The boot-time preflight check (already gates server.ts's main(), throwing before the process starts serving) now rejects any of the five critical secrets that is set to the exact known-placeholder string, or that is merely too short to be a real generated secret, or that duplicates another critical secret's value. Presence is still each secret's own concern (most are feature-gating, not universally required) -- this only judges STRENGTH whenever one is actually set, so it can never be silently bypassed by leaving the file's placeholder in place. - .env.selfhost.example now ships these five lines commented out, with explicit per-secret generation guidance, instead of enabled placeholders. - The quickstart doc callout it directed users through now explicitly warns about generating distinct random values for each secret. Defense in depth: the docs + example file guide an operator toward doing the right thing, and the preflight check makes doing the wrong thing impossible rather than merely discouraged.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | eb25275 | Commit Preview URL Branch Preview URL |
Jul 03 2026, 05:40 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2686 +/- ##
=======================================
Coverage ? 96.12%
=======================================
Files ? 238
Lines ? 26709
Branches ? 9686
=======================================
Hits ? 25675
Misses ? 424
Partials ? 610
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a critical Codex security finding:
.env.selfhost.exampleshipped ENABLED (not commented-out) placeholder values forGITHUB_WEBHOOK_SECRET,GITTENSORY_API_TOKEN,GITTENSORY_MCP_TOKEN,INTERNAL_JOB_TOKEN, andSELFHOST_SETUP_TOKEN. The self-hosting quickstart docs instruct operators tocp .env.selfhost.example .env. An operator who copies the file and misses "fill in the placeholders" runs an instance with:verifyGitHubSignatureaccepts any payload signed with the well-known string, so an attacker can forge valid-looking GitHub webhook deliveries.GITTENSORY_API_TOKEN— authenticates as the server-to-server API actor, bypasses app-role checks, and is exempt from per-repo write-permission checks (authenticatePrivateToken,requireRepoWriteAccess).INTERNAL_JOB_TOKEN— gates internal-only routes.All of this fails silently: nothing in the current preflight check or runtime path rejects these known values.
Fix (defense in depth — two independent layers)
Boot-time preflight now rejects the vulnerability class, not just this instance of it.
src/selfhost/preflight.ts'spreflightEnv(already gatesserver.ts'smain(), throwing before the process starts serving) now validates all five critical secrets whenever they're set:.env.selfhost.example/.env.example→ reject.openssl rand -hex 32value is 64 chars) → reject.Presence is intentionally not newly required — most of these gate optional features (MCP, internal routes) and I didn't want to risk breaking a legitimate not-yet-fully-configured boot. This closes the actual reported hole (a known-bad value silently accepted) without expanding scope into a "what should be mandatory" redesign.
.env.selfhost.exampleno longer ships an enabled placeholder for any of the five. They're now commented out with explicit per-secret generation guidance (openssl rand -hex 32). A naivecp && docker compose upwith an unedited file now refuses to boot with a clear, itemized preflight error instead of silently running with known-compromised credentials.Also updated the quickstart doc callout (the exact page the finding cited as instructing users toward the vulnerable path) to explicitly call out generating distinct random values for each secret.
Scope
wantedPathsapps/gittensory-ui/src/lib/selfhost-env-reference.ts(npm run selfhost:env-reference) since the source line numbers inpreflight.tsshiftedValidation
npm run typechecknpm run test:coverage(unsharded) — 100% line+branch coverage on every changed line insrc/selfhost/preflight.ts, verified by diffing changed lines against the v8 coverage map directlynpm run selfhost:env-reference:checknpm audit --audit-level=moderate— 0 vulnerabilitiesnpm run ui:test,npm run ui:lint(fixed one prettier violation in the docs page),npm run actionlint,npm run db:migrations:checktest/unit/selfhost-preflight.test.ts: rejects each critical secret at the exact placeholder value; rejects a too-short value without echoing it; boundary test at exactly/one-under the minimum length; presence still optional; rejects two secrets sharing an identical value; accepts five distinct sufficiently-long real values; collects a problem for every affected secret, not just the firstTwo pre-existing, environment-specific failures unrelated to this change (
test/unit/check-schema-drift-script.test.ts'stsxbinary path issue,ui:typecheck's missingnode:process/@lovable.devtypes) reproduce identically on a cleanmaincheckout in this worktree and are excluded above.Note: this branch also carries a small unrelated commit (
fix(review): add the missing securityFocus field to 5 AI-review-cache test fixtures) cherry-picked from #2684, needed to get a greennpm run typechecklocally sincemainis currently broken for anyone branching fresh off it. Will rebase to drop the duplicate once #2684 merges.Safety