import { resolve } from "path"; import { randomBytes } from "crypto"; interface Config { RESEND_API_KEY: string; EMAIL_FROM: string; GITHUB_APP_ENABLED: boolean; GITHUB_APP_NEW_CONNECTIONS: boolean; GITHUB_OAUTH_ENABLED: boolean; GITHUB_APP_ID: string; GITHUB_APP_SLUG: string; GITHUB_APP_CLIENT_ID: string; GITHUB_APP_CLIENT_SECRET: string; GITHUB_APP_PRIVATE_KEY: string; GITHUB_APP_PRIVATE_KEY_FILE: string; GITHUB_APP_CALLBACK: string; GITHUB_APP_WEBHOOK_SECRET: string; CREDENTIAL_KEYS: string; CREDENTIAL_ACTIVE_KEY_ID: string; CREDENTIAL_LEGACY_READS: boolean; SESSION_SECRET: string; REDIS_PORT: number; REDIS_HOSTNAME: string; CLIENT_ID: string; CLIENT_SECRET: string; GITHUB_TOKEN: string; DEFAULT_QUOTA: number; MAX_FILE_FOLDER: number; MAX_FILE_SIZE: number; MAX_REPO_SIZE: number; AUTO_DOWNLOAD_REPO_SIZE: number; FREE_DOWNLOAD_REPO_SIZE: number; AUTH_CALLBACK: string; /** * Allow to download repository and files */ ENABLE_DOWNLOAD: boolean; STREAMER_ENTRYPOINT: string | null; ANONYMIZATION_MASK: string; PORT: number; APP_HOSTNAME: string; DB_USERNAME: string; DB_PASSWORD: string; DB_HOSTNAME: string; /** * Complete MongoDB connection string. When set, this takes precedence over * DB_USERNAME, DB_PASSWORD, and DB_HOSTNAME and supports replica-set seed * lists and options. */ MONGODB_URI: string; FOLDER: string; additionalExtensions: string[]; S3_BUCKET: string | null; S3_CLIENT_ID: string | null; S3_CLIENT_SECRET: string | null; S3_ENDPOINT: string | null; S3_REGION: string | null; STORAGE: "filesystem" | "s3"; /** * Value for Express "trust proxy": either a fixed hop count ("1", "2") * or a comma-separated list of trusted subnets — named subnets * ("loopback", "linklocal", "uniquelocal"), IPs/CIDRs, and the keyword * "cloudflare" (expands to Cloudflare's published ranges). The subnet * form keeps request.ip pointing at the real visitor even when the * proxy chain gains or loses hops. See src/server/trustProxy.ts. */ TRUST_PROXY: string; RATE_LIMIT: number; } const config: Config = { RESEND_API_KEY: "", EMAIL_FROM: "", GITHUB_APP_ENABLED: false, GITHUB_APP_NEW_CONNECTIONS: true, GITHUB_OAUTH_ENABLED: true, GITHUB_APP_ID: "", GITHUB_APP_SLUG: "", GITHUB_APP_CLIENT_ID: "", GITHUB_APP_CLIENT_SECRET: "", GITHUB_APP_PRIVATE_KEY: "", GITHUB_APP_PRIVATE_KEY_FILE: "", GITHUB_APP_CALLBACK: "http://localhost:5000/github/app/callback", GITHUB_APP_WEBHOOK_SECRET: "", // Predictable defaults are dangerous: a known SESSION_SECRET lets anyone // forge session cookies. Default to empty and resolve below — random in // dev, required in production. See the post-env block. CREDENTIAL_KEYS: "", CREDENTIAL_ACTIVE_KEY_ID: "", CREDENTIAL_LEGACY_READS: false, SESSION_SECRET: "", CLIENT_ID: "CLIENT_ID", CLIENT_SECRET: "CLIENT_SECRET", GITHUB_TOKEN: "", DEFAULT_QUOTA: 2 * 1024 * 1024 * 1024 * 8, MAX_FILE_FOLDER: 1000, MAX_FILE_SIZE: 100 * 1024 * 1024, // in b, 100MB MAX_REPO_SIZE: 60000, // in kb, 60MB AUTO_DOWNLOAD_REPO_SIZE: 150, // in kb, 150kb FREE_DOWNLOAD_REPO_SIZE: 150, // in kb, 150kb ENABLE_DOWNLOAD: true, AUTH_CALLBACK: "http://localhost:5000/github/auth", ANONYMIZATION_MASK: "XXXX", PORT: 5000, // Trust the local reverse proxy (docker network / host nginx) and // Cloudflare's edges by subnet rather than by hop count, so client-IP // resolution survives Cloudflare adding or removing X-Forwarded-For // entries. Set a plain number to restore the legacy hop-count behavior. TRUST_PROXY: "loopback,uniquelocal,cloudflare", RATE_LIMIT: 350, APP_HOSTNAME: "anonymous.4open.science", DB_USERNAME: "admin", DB_PASSWORD: "password", DB_HOSTNAME: "mongodb", MONGODB_URI: "", REDIS_HOSTNAME: "redis", REDIS_PORT: 6379, FOLDER: resolve(__dirname, "..", "repositories"), additionalExtensions: [ "license", "dockerfile", "sbt", "ipynb", "gp", "out", "sol", "in", "jsonl", "ndjson", ], STORAGE: "filesystem", STREAMER_ENTRYPOINT: null, S3_BUCKET: null, S3_CLIENT_ID: null, S3_CLIENT_SECRET: null, S3_ENDPOINT: null, S3_REGION: null, }; for (const conf in process.env) { const configRecord = config as unknown as Record; if (configRecord[conf] !== undefined) { const currentValue = configRecord[conf]; const envValue = process.env[conf] as string; if (typeof currentValue === "number") { const parsed = Number(envValue); if (!isNaN(parsed)) { configRecord[conf] = parsed; } } else if (typeof currentValue === "boolean") { configRecord[conf] = envValue === "true" || envValue === "1"; } else { configRecord[conf] = envValue; } } } // Harden security-sensitive secrets that still hold an unset/predictable // value after reading the environment (CWE-798). const isProduction = process.env.NODE_ENV === "production"; // SESSION_SECRET: a known value allows session forgery. Require it in // production; in development fall back to a per-process random value so the // app still boots without shipping a guessable secret. if (!config.SESSION_SECRET || config.SESSION_SECRET === "SESSION_SECRET") { if (isProduction) { throw new Error( "SESSION_SECRET must be set to a strong random value in production" ); } config.SESSION_SECRET = randomBytes(32).toString("hex"); // eslint-disable-next-line no-console console.warn( "SESSION_SECRET not set — generated a random development secret. " + "Sessions will not persist across restarts. Set SESSION_SECRET in production." ); } // Refuse to start in production with the placeholder OAuth credentials or the // default database password baked into the image. if (isProduction) { const insecureDefaults: [string, string][] = config.GITHUB_OAUTH_ENABLED ? [ ["CLIENT_ID", "CLIENT_ID"], ["CLIENT_SECRET", "CLIENT_SECRET"], ] : []; if (!config.MONGODB_URI) { insecureDefaults.push(["DB_PASSWORD", "password"]); } for (const [key, badValue] of insecureDefaults) { if ((config as unknown as Record)[key] === badValue) { throw new Error( `${key} is using its insecure default value; set it via the environment in production` ); } } } if (config.GITHUB_APP_ENABLED) { for (const name of ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID", "GITHUB_APP_CLIENT_SECRET", "GITHUB_APP_CALLBACK", "GITHUB_APP_WEBHOOK_SECRET"] as const) { if (!config[name]) throw new Error(`${name} is required when GITHUB_APP_ENABLED=true`); } if (!config.GITHUB_APP_PRIVATE_KEY && !config.GITHUB_APP_PRIVATE_KEY_FILE) throw new Error("A GitHub App private key is required"); } export default config;