Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 0 additions & 34 deletions apps/hub/src/asset-service-factory.test.ts

This file was deleted.

54 changes: 0 additions & 54 deletions apps/hub/src/asset-service-factory.ts

This file was deleted.

97 changes: 65 additions & 32 deletions apps/hub/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,71 @@
// paste-a-token provider card.

import { type } from "arktype";
import { spawnSync } from "node:child_process";
import { mkdirSync, realpathSync } from "node:fs";
import path from "node:path";

// Refuse to initialize hub git-on-disk state inside an existing git work
// tree. isomorphic-git walks up from a data dir looking for `.git`; when
// HUB_DATA_DIR lives inside this repo (the `.env` default `.data/hub`), a
// missed nested init authors a genesis commit onto the working branch and a
// checkout of that tree can delete the checkout (see AGENTS.md).
const GIT_DIR_VARS = new Set([
"GIT_DIR",
"GIT_WORK_TREE",
"GIT_INDEX_FILE",
"GIT_OBJECT_DIRECTORY",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
]);

function envWithoutGitDir(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const next: NodeJS.ProcessEnv = {};
for (const [key, value] of Object.entries(env)) {
if (value === undefined || GIT_DIR_VARS.has(key)) continue;
next[key] = value;
}
return next;
}

function enclosingGitWorkTree(dir: string, env: NodeJS.ProcessEnv): string | null {
const absolute = path.resolve(dir);
mkdirSync(absolute, { recursive: true });
const canonical = realpathSync(absolute);
const result = spawnSync(
"git",
["-c", "safe.directory=*", "-C", canonical, "rev-parse", "--show-toplevel"],
{ encoding: "utf8", env: envWithoutGitDir(env) },
);
if (result.status !== 0) return null;
const toplevel = result.stdout.trim();
return toplevel === "" ? null : realpathSync(toplevel);
}

/**
* Called at hub boot (`index.ts`), never from `readHubConfig` itself:
* this does real filesystem/subprocess IO against `dir`, which would
* make every test parsing a config trip it against whatever `.data/hub`-
* shaped path a fixture happens to use.
*/
export function assertHubDataDirGitSafety(
dir: string,
allowInsideWorkTree: boolean,
env: NodeJS.ProcessEnv,
): void {
if (allowInsideWorkTree) return;
const enclosing = enclosingGitWorkTree(dir, env);
if (enclosing === null) return;
throw new Error(
[
`HUB_DATA_DIR (${dir}) is inside the git work tree at ${enclosing}.`,
"The hub refuses to initialize git-on-disk state there: a nested init",
"that misses its own .git walks up and commits onto the working",
"branch, which can delete the checkout.",
"Point HUB_DATA_DIR at a directory outside any git work tree, or set",
"HUB_ALLOW_GIT_INSIDE_WORK_TREE=1 to opt in.",
].join(" "),
);
}

const HTTP_URL = /^https?:\/\/.+$/;

Expand Down Expand Up @@ -62,12 +127,6 @@ const HubEnv = type({
"SIGNUP_RATE_LIMIT_MAX?": type(/^[1-9]\d*$/).describe(
"the maximum sign-ups a single IP may make per window, e.g. 5",
),
"SIGNIN_RATE_LIMIT_WINDOW_SECONDS?": type(/^[1-9]\d*$/).describe(
"the per-account sign-in rate-limit window, in seconds, e.g. 60; overrides better-auth's built-in 10-second/3-attempt default, which is too tight for a person retyping a password",
),
"SIGNIN_RATE_LIMIT_MAX?": type(/^[1-9]\d*$/).describe(
"the maximum failed sign-in attempts a single account may accrue per window before further failures are rejected, e.g. 10 — keyed on the target email, not client IP (see sign-in-rate-limit.ts); a correct password always succeeds regardless of this budget",
),
"GOOGLE_CLIENT_ID?": type("string > 0").describe(
"Google OAuth client id; set together with GOOGLE_CLIENT_SECRET to enable Google sign-in",
),
Expand Down Expand Up @@ -141,20 +200,6 @@ const HubEnv = type({

const DEFAULT_SIGNUP_RATE_LIMIT_WINDOW_SECONDS = 60;
const DEFAULT_SIGNUP_RATE_LIMIT_MAX = 5;
// better-auth's own built-in special rule for /sign-in* is 3 attempts per
// 10 seconds, keyed per client IP -- too tight for a bucket that can end up
// shared (CL-6494): when the IP can't be resolved, or is forged, every
// signed-out visitor, or an attacker replaying the same forged header, can
// starve it. index.ts now disables that built-in rule for sign-in entirely
// (see sign-in-rate-limit.ts) and uses these knobs to configure its
// account-keyed replacement instead, applied the same in every environment
// rather than branched on NODE_ENV (this file already rejects inferring
// auth behavior from NODE_ENV — see `rateLimit.enabled` below). This gives
// real users room to mistype a password, and a lone local developer room
// to keep working even while no IP can be resolved, without loosening
// brute-force resistance.
const DEFAULT_SIGNIN_RATE_LIMIT_WINDOW_SECONDS = 60;
const DEFAULT_SIGNIN_RATE_LIMIT_MAX = 10;

// One member per implemented `SidecarProvisioner` backend. Adding a new
// backend (e.g. a remote sandbox) is: implement the contract in its own
Expand Down Expand Up @@ -222,10 +267,6 @@ export type HubConfig = {
readonly windowSeconds: number;
readonly max: number;
};
readonly signInRateLimit: {
readonly windowSeconds: number;
readonly max: number;
};
readonly socialProviders: Readonly<Partial<Record<SocialProviderId, SocialProviderCredential>>>;
readonly huggingfaceOAuthClientId?: string;
readonly githubAppClientId?: string;
Expand Down Expand Up @@ -477,14 +518,6 @@ export function readHubConfig(env: Record<string, string | undefined>): HubConfi
? Number(parsed.SIGNUP_RATE_LIMIT_MAX)
: DEFAULT_SIGNUP_RATE_LIMIT_MAX,
},
signInRateLimit: {
windowSeconds: parsed.SIGNIN_RATE_LIMIT_WINDOW_SECONDS
? Number(parsed.SIGNIN_RATE_LIMIT_WINDOW_SECONDS)
: DEFAULT_SIGNIN_RATE_LIMIT_WINDOW_SECONDS,
max: parsed.SIGNIN_RATE_LIMIT_MAX
? Number(parsed.SIGNIN_RATE_LIMIT_MAX)
: DEFAULT_SIGNIN_RATE_LIMIT_MAX,
},
};
if (parsed.HUB_ALLOW_GIT_INSIDE_WORK_TREE !== undefined) hubConfig.allowGitInsideWorkTree = true;
if (parsed.PORT !== undefined) hubConfig.listenPort = Number(parsed.PORT);
Expand Down
Loading
Loading