@vigilbase/cli
Version:
Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.
143 lines (142 loc) • 7.15 kB
JavaScript
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);
}