Skip to content

fix(selfhost): require setup token for app wizard - #1233

Merged
JSONbored merged 3 commits into
mainfrom
codex/fix-public-first-run-setup-vulnerability
Jun 24, 2026
Merged

fix(selfhost): require setup token for app wizard#1233
JSONbored merged 3 commits into
mainfrom
codex/fix-public-first-run-setup-vulnerability

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Motivation

  • Prevent unauthenticated remote actors from completing the first-run GitHub App manifest flow and writing attacker-controlled credentials to the instance.
  • Harden the /setup and /setup/callback flow so the server only accepts a setup-initiation from an operator-controlled secret and verifies the callback against a signed cookie.

Description

  • Require an operator-set SELFHOST_SETUP_TOKEN before exposing the first-run wizard and return 400 when it is unset and 403 on a missing/invalid token in requests to /setup (changes in src/server.ts).
  • Replace the unauthenticated setup_state cookie with a signed HttpOnly setup_auth cookie and add helper functions setupAuthCookieValue, isValidSetupAuthCookie, and cookieValue in src/selfhost/setup-wizard.ts to generate and verify the signed cookie using an HMAC.
  • Validate the setup callback by verifying the signed cookie against the state parameter before calling exchangeManifestCode and writing credentials to disk, and add Referrer-Policy: no-referrer to the setup response to avoid leaking token-bearing URLs.
  • Update operator-facing materials (.env.example and docs/self-hosting.md) to document PUBLIC_API_ORIGIN and the new required SELFHOST_SETUP_TOKEN, and add unit tests covering signed-cookie generation, extraction, and invalid cases (test/unit/selfhost-setup-wizard.test.ts).

Testing

  • Ran unit tests for the setup wizard with npx vitest run test/unit/selfhost-setup-wizard.test.ts, which passed (8 tests all ✓).
  • Verified git diff --check succeeded locally and updated docs/sample env (.env.example, docs/self-hosting.md).
  • Attempted npm run typecheck but it was blocked by locally missing optional dependencies/types for pg and ioredis in the environment; typecheck was not completed here.
  • Attempted coverage and npm audit but both were blocked by local toolchain/registry issues (jsTokens coverage remapping error and npm audit 403), so full local coverage and audit could not be completed in this environment.

Codex Task

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.04%. Comparing base (b03bcbc) to head (3c51484).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1233   +/-   ##
=======================================
  Coverage   95.04%   95.04%           
=======================================
  Files         177      177           
  Lines       19933    19944   +11     
  Branches     7176     7179    +3     
=======================================
+ Hits        18945    18956   +11     
  Misses        395      395           
  Partials      593      593           
Files with missing lines Coverage Δ
src/selfhost/setup-wizard.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored JSONbored self-assigned this Jun 24, 2026
@JSONbored JSONbored added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jun 24, 2026
@JSONbored
JSONbored force-pushed the codex/fix-public-first-run-setup-vulnerability branch from ab4495f to 432181e Compare June 24, 2026 22:16

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

Comment thread src/server.ts Outdated
new URL(request.url).searchParams.get("token") ??
request.headers.get("x-setup-token") ??
request.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
if (suppliedToken !== setupToken) return new Response("invalid setup token", { status: 403 });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Setup token compared with timing-unsafe string operator

Setup token comparison uses !==, enabling timing side-channel attacks.

Use crypto.timingSafeEqual for constant-time token comparison.

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="src/server.ts">
<violation number="1" location="src/server.ts:266">
<priority>P2</priority>
<title>Setup token compared with timing-unsafe string operator</title>
<evidence>The suppliedToken is compared against setupToken using the !== operator, which short-circuits on the first mismatched character and is vulnerable to timing side-channel attacks.</evidence>
<recommendation>Use crypto.timingSafeEqual to compare the supplied token against the configured token, after first checking lengths match (or padding to a fixed length).</recommendation>
</violation>
</file>

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
Comment thread src/server.ts Outdated
}
if (path === "/setup") {
const suppliedToken =
new URL(request.url).searchParams.get("token") ??

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Setup token accepted from URL query parameter, leaking secret to logs and history

Setup token can be passed in the URL query string, leaking it to logs and browser history.

Remove query parameter support; accept the setup token only via secure headers.

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="src/server.ts">
<violation number="1" location="src/server.ts:263">
<priority>P2</priority>
<title>Setup token accepted from URL query parameter, leaking secret to logs and history</title>
<evidence>The /setup endpoint reads the setup token from new URL(request.url).searchParams.get("token"), allowing the secret to be passed in the URL query string. This leaks the token to web server access logs, reverse-proxy logs, browser history, and shared links. The PR adds Referrer-Policy: no-referrer to mitigate referrer leakage but does not address query-string logging.</evidence>
<recommendation>Remove the query-parameter fallback for the setup token. Accept the token only via the x-setup-token or Authorization headers. Update documentation to stop instructing users to visit /setup?token=....</recommendation>
</violation>
</file>

…n via POST form, not URL

Resolve the Superagent findings on the first-run setup wizard:
- Compare the setup token with a constant-time timingSafeStrEqual instead of `!==`,
  closing a timing side-channel; reuse it for the signed setup_auth cookie check (DRY).
- Stop reading the token from the URL query string (it leaked to access logs, proxies,
  and browser history). The browser flow now uses a token-entry form that POSTs the token
  in the request body (renderTokenEntryPage); scripted setups still use the x-setup-token /
  Authorization: Bearer header. Docs updated.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

1 similar comment
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
@JSONbored
JSONbored merged commit 480f2c3 into main Jun 24, 2026
19 checks passed
@JSONbored
JSONbored deleted the codex/fix-public-first-run-setup-vulnerability branch June 24, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant