UNPKG

@vigilbase/cli

Version:

Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.

143 lines (142 loc) • 7.15 kB
import { homedir } from 'node:os'; import { isAbsolute, join } from 'node:path'; import { z } from 'zod'; import { CLI_VERSION } from '../version.js'; const DEFAULT_PUBLIC_BASE_URL = 'https://vigilbase.com'; const DEFAULT_ONE_BASE_URL = 'https://one.vigilbase.com'; /** Public client id accepted by One's device authorization plugin. */ const DEFAULT_CLIENT_ID = 'vigilbase-cli'; const originSchema = z.string().url().transform((raw) => new URL(raw)).refine((url) => (url.protocol === 'https:' || url.protocol === 'http:') && !url.username && !url.password && /^\/+$/.test(url.pathname) && !url.search && !url.hash, 'Expected an HTTP(S) origin without credentials, a path, query, or fragment.').transform((url) => url.origin); export function parseOrigin(raw, option) { const result = originSchema.safeParse(raw); if (!result.success) { // Do not echo the input: malformed URLs can contain credentials. throw new Error(`${option} must be an HTTP(S) origin without credentials, a path, query, or fragment.`); } return result.data; } const timeoutSchema = z.number().int().min(1).max(2_147_483_647); /** Default request timeout; also used when library callers omit `timeoutMs`. */ export const DEFAULT_TIMEOUT_MS = 60_000; export function resolvePublicBaseUrl(explicit) { const raw = explicit?.trim() || process.env.VIGILBASE_BASE_URL?.trim() || process.env.VIGILBASE_API_URL?.trim() || process.env.VIGILBASE_PUBLIC_URL?.trim() || DEFAULT_PUBLIC_BASE_URL; return parseOrigin(raw, '--base-url / VIGILBASE_BASE_URL'); } export function resolveOneBaseUrl(explicit) { const raw = explicit?.trim() || process.env.VIGILBASE_ONE_URL?.trim() || DEFAULT_ONE_BASE_URL; return validateOneOrigin(raw); } /** Validate at transport boundaries too: library callers may construct config objects. */ export function validateOneOrigin(raw) { const origin = parseOrigin(raw, '--one-url / VIGILBASE_ONE_URL'); const url = new URL(origin); const loopback = url.hostname === 'localhost' || url.hostname === '[::1]' || /^127\.(?:\d{1,3}\.){2}\d{1,3}$/.test(url.hostname); if (url.protocol !== 'https:' && !loopback) { throw new Error('--one-url / VIGILBASE_ONE_URL requires HTTPS; HTTP is allowed only for loopback development.'); } return origin; } /** Production Identity: the only issuer trusted unless --auth-url / VIGILBASE_AUTH_URL names another. */ export const DEFAULT_IDENTITY_ISSUER = 'https://auth.vigilbase.com'; export const STAGING_IDENTITY_ISSUER = 'https://identity-staging.vigilbase.com'; const IDENTITY_OPTION = '--auth-url / VIGILBASE_AUTH_URL'; function isIdentityLoopback(origin) { return ['localhost', '127.0.0.1', '[::1]'].includes(new URL(origin).hostname); } export function resolveIdentityBaseUrl(raw) { const origin = parseOrigin(raw, IDENTITY_OPTION); if (origin !== DEFAULT_IDENTITY_ISSUER && origin !== STAGING_IDENTITY_ISSUER && !isIdentityLoopback(origin)) { throw new Error(`${IDENTITY_OPTION} must be ${DEFAULT_IDENTITY_ISSUER}, ${STAGING_IDENTITY_ISSUER} or a loopback development origin.`); } return origin; } /** * The one Identity issuer this configuration trusts: --auth-url / VIGILBASE_AUTH_URL * when set, otherwise production. Staging is trusted only when configured explicitly. * Validated here too because library callers may construct config objects. */ export function trustedIdentityIssuer(config) { if (!config.identityBaseUrl) return DEFAULT_IDENTITY_ISSUER; const origin = resolveIdentityBaseUrl(config.identityBaseUrl); if (isIdentityLoopback(origin) && !isIdentityLoopback(validateOneOrigin(config.oneBaseUrl))) { throw new Error(`${IDENTITY_OPTION} can be a loopback origin only when --one-url / VIGILBASE_ONE_URL is loopback too.`); } return origin; } export function resolveClientId(explicit) { return (explicit?.trim() || process.env.VIGILBASE_CLIENT_ID?.trim() || DEFAULT_CLIENT_ID); } /** * VIGILBASE_CONFIG_DIR (or `explicit`), else $XDG_CONFIG_HOME/vigilbase, else ~/.config/vigilbase. A relative * XDG_CONFIG_HOME is ignored, as the XDG Base Directory spec says; a relative custom directory is returned as is. */ function configDirFromEnvironment(explicit, env) { const custom = explicit?.trim() || env.VIGILBASE_CONFIG_DIR?.trim(); if (custom) return custom; const xdg = env.XDG_CONFIG_HOME?.trim(); return xdg && isAbsolute(xdg) ? join(xdg, 'vigilbase') : join(homedir(), '.config', 'vigilbase'); } /** The config directory, which must be absolute. */ export function resolveConfigDir(explicit, env = process.env) { const configDir = configDirFromEnvironment(explicit, env); if (!isAbsolute(configDir)) throw new Error('VIGILBASE_CONFIG_DIR must be an absolute path.'); return configDir; } export function createConfig(options) { const publicBaseUrl = resolvePublicBaseUrl(options?.publicBaseUrl ?? options?.baseUrl); const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS; if (!timeoutSchema.safeParse(timeoutMs).success) { throw new Error('Request timeout must be an integer between 1 and 2147483647 milliseconds.'); } return { publicBaseUrl, /** Alias used by the current public tools client. */ baseUrl: publicBaseUrl, oneBaseUrl: resolveOneBaseUrl(options?.oneBaseUrl), ...(options?.identityBaseUrl || process.env.VIGILBASE_AUTH_URL ? { identityBaseUrl: resolveIdentityBaseUrl(options?.identityBaseUrl || process.env.VIGILBASE_AUTH_URL) } : {}), clientId: resolveClientId(options?.clientId), configDir: configDirFromEnvironment(options?.configDir, process.env), userAgent: `@vigilbase/cli/${CLI_VERSION} (+https://vigilbase.com; node/${process.version})`, timeoutMs, }; } /** * Config for `vigilbase local …` and `vigilbase cloudflare-checkup`. Local * checkups resolve their own submit origin (resolveSubmitOrigin) and never * read VIGILBASE_* URLs. They resolve the config directory themselves, and * only when they need it (the managed cf install, the default snapshot path), * so malformed values cannot block offline commands such as `inspect`. */ export function createLocalCommandConfig(timeoutMs) { const timeout = timeoutMs ?? DEFAULT_TIMEOUT_MS; if (!timeoutSchema.safeParse(timeout).success) { throw new Error('Request timeout must be an integer between 1 and 2147483647 milliseconds.'); } return { publicBaseUrl: DEFAULT_PUBLIC_BASE_URL, baseUrl: DEFAULT_PUBLIC_BASE_URL, oneBaseUrl: DEFAULT_ONE_BASE_URL, clientId: DEFAULT_CLIENT_ID, // Unused by local commands; never read from the environment. configDir: join(homedir(), '.config', 'vigilbase'), userAgent: `@vigilbase/cli/${CLI_VERSION} (+https://vigilbase.com; node/${process.version})`, timeoutMs: timeout, }; } /** Back-compat helper used by older call sites / tests. */ export function resolveBaseUrl(explicit) { return resolvePublicBaseUrl(explicit); }