import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import process from 'node:process'; import pg from 'pg'; import { loadTenantForeignKeyRelations, summarizeTenantForeignKeySchema, } from './lib/tenant-foreign-key-audit.js'; const DEFAULT_DATABASE_URL = 'postgresql://postgres:postgres@127.0.0.1:54322/postgres'; const DEFAULT_AUTH_CODE_PEPPER = 'development-code-pepper-change-me'; const DEFAULT_AUTH_SESSION_SECRET = 'development-session-secret-change-me'; const DEFAULT_AUTH_JWT_SECRET = 'development-jwt-secret-change-me'; const DEFAULT_PLATFORM_ADMIN_API_KEY = 'local-platform-admin-key'; const HARD_MAX_JSON_BODY_BYTES = 50 * 1024 * 1024; const PRODUCTION_SMS_PROVIDERS = new Set([ 'aliyun-pnvs', 'aliyun-pnvs-sms', 'aliyun-sms-auth', ]); const PRODUCTION_STORAGE_PROVIDERS = new Set(['aliyun_oss', 'tencent_cos', 'supabase_storage']); const AUTH_PROVIDER_ALIASES = { sms: new Set(['aliyun', 'aliyun-sms', 'aliyun_sms', 'aliyun-pnvs', 'aliyun_pnvs', 'aliyun-sms-auth', 'aliyun_sms_auth', 'tencent', 'tencent-sms', 'tencent_sms']), aliyun: new Set(['aliyun', 'aliyun-sms', 'aliyun_sms']), aliyunPnvs: new Set(['aliyun-pnvs', 'aliyun_pnvs', 'aliyun-pnvs-sms', 'aliyun_sms_auth', 'aliyun-sms-auth']), tencent: new Set(['tencent', 'tencent-sms', 'tencent_sms']), wechatMiniapp: new Set(['wechat-miniapp', 'wechat_miniapp', 'wechat-mini', 'wx-miniapp', 'wx_miniapp']), wechatWeb: new Set(['wechat-web', 'wechat_web', 'wechat', 'wechat-oauth', 'wechat_oauth']), qq: new Set(['qq', 'qq-oauth', 'qq_oauth']), }; const PAYMENT_PROVIDER_ALIASES = { wechatPay: new Set(['wechat_pay', 'wechat-pay', 'wechatpay', 'wxpay', 'wx_pay']), alipay: new Set(['alipay', 'ali_pay']), manual: new Set(['manual']), }; const args = new Set(process.argv.slice(2)); const argValues = new Map(); for (let index = 2; index < process.argv.length; index += 1) { const current = process.argv[index]; if (current.startsWith('--') && process.argv[index + 1] && !process.argv[index + 1].startsWith('--')) { argValues.set(current, process.argv[index + 1]); index += 1; } } const jsonOutput = args.has('--json'); const checkDb = args.has('--check-db'); const skipDb = args.has('--skip-db') || !checkDb; const envFile = argValues.get('--env-file') || path.resolve(process.cwd(), '.env'); const providerConfigFixture = argValues.get('--provider-config-fixture') || ''; const tenantConfigFixture = argValues.get('--tenant-config-fixture') || ''; const environmentSafetyFixture = argValues.get('--environment-safety-fixture') || ''; const migrationHistoryFixture = argValues.get('--migration-history-fixture') || ''; const checks = []; function loadRepositoryMigrationState() { const migrationsDir = path.resolve(process.cwd(), 'supabase', 'migrations'); if (!fs.existsSync(migrationsDir)) { return { error: `Migration directory is missing: ${migrationsDir}` }; } const files = fs.readdirSync(migrationsDir) .filter(file => file.endsWith('.sql')) .sort(); const invalidFiles = []; const versions = []; for (const file of files) { const match = /^(\d+)_.*\.sql$/.exec(file); if (!match) invalidFiles.push(file); else versions.push(match[1]); } const duplicateVersions = [...new Set( versions.filter((version, index) => versions.indexOf(version) !== index), )]; const latestVersion = versions.reduce((latest, version) => { if (!latest) return version; if (version.length !== latest.length) return version.length > latest.length ? version : latest; return version > latest ? version : latest; }, ''); return { migrationsDir, files, versions, invalidFiles, duplicateVersions, latestVersion, }; } function compareMigrationVersions(left, right) { const normalizedLeft = String(left || '').replace(/^0+(?=\d)/, ''); const normalizedRight = String(right || '').replace(/^0+(?=\d)/, ''); if (normalizedLeft.length !== normalizedRight.length) { return normalizedLeft.length > normalizedRight.length ? 1 : -1; } return normalizedLeft === normalizedRight ? 0 : normalizedLeft > normalizedRight ? 1 : -1; } function validateMigrationHistory(row, source = 'database') { const repository = loadRepositoryMigrationState(); if (repository.error || repository.files?.length === 0 || repository.invalidFiles?.length > 0 || repository.duplicateVersions?.length > 0 || !repository.latestVersion) { block( 'db.migrations.repository', 'Repository migrations must use unique numeric Supabase versions', { source, error: repository.error || '', migrationCount: repository.files?.length || 0, invalidFiles: repository.invalidFiles || [], duplicateVersions: repository.duplicateVersions || [], }, ); return; } const latestVersion = String(row?.latest_version ?? row?.latestVersion ?? '').trim(); const appliedCount = Number(row?.applied_count ?? row?.appliedCount); const distinctVersionCount = Number(row?.distinct_version_count ?? row?.distinctVersionCount); const expectedVersionApplied = row?.expected_version_applied ?? row?.expectedVersionApplied; const details = { source, expectedVersion: repository.latestVersion, repositoryMigrationCount: repository.files.length, latestAppliedVersion: latestVersion || '(missing)', appliedCount, distinctVersionCount, expectedVersionApplied: expectedVersionApplied === true, }; const validCounts = Number.isInteger(appliedCount) && appliedCount >= repository.files.length && Number.isInteger(distinctVersionCount) && distinctVersionCount === appliedCount; const current = /^\d+$/.test(latestVersion) && compareMigrationVersions(latestVersion, repository.latestVersion) >= 0 && expectedVersionApplied === true; if (!validCounts || !current) { block( 'db.migrations.current', 'Database migration history must include the repository latest migration with no duplicate versions', details, ); return; } pass( 'db.migrations.current', 'Database migration history includes the repository latest migration', details, ); } function loadEnvFile(filePath) { if (!filePath || !fs.existsSync(filePath)) return; const content = fs.readFileSync(filePath, 'utf8'); for (const line of content.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const index = trimmed.indexOf('='); if (index === -1) continue; const key = trimmed.slice(0, index).trim(); const value = trimmed.slice(index + 1).trim().replace(/^"|"$/g, ''); if (key && process.env[key] === undefined) process.env[key] = value; } } function env(key, fallback = '') { return process.env[key] ?? fallback; } function envBool(key, fallback = false) { const value = process.env[key]; if (value === undefined) return fallback; return ['true', '1', 'yes', 'y', 'on'].includes(value.toLowerCase()); } function envNumber(key, fallback) { const parsed = Number(process.env[key]); return Number.isFinite(parsed) ? parsed : fallback; } function envList(key, fallback = '') { return env(key, fallback) .split(',') .map(item => item.trim()) .filter(Boolean); } function add(status, id, message, details = {}) { checks.push({ status, id, message, details }); } function pass(id, message, details = {}) { add('pass', id, message, details); } function warn(id, message, details = {}) { add('warn', id, message, details); } function block(id, message, details = {}) { add('blocker', id, message, details); } function isUnsafeSecret(value, defaultValue) { const normalized = String(value || '').trim().toLowerCase(); return ( !normalized || value === defaultValue || normalized.length < 32 || normalized.includes('replace_with') || normalized.includes('change-me') || normalized.includes('changeme') || normalized.includes('your_') || normalized.includes('example') ); } function hostFromUrl(value) { try { return new URL(value).hostname.toLowerCase(); } catch { return ''; } } function urlFromValue(value) { try { return new URL(String(value || '')); } catch { return null; } } function isLocalHost(hostname) { return ['localhost', '127.0.0.1', '::1', '0.0.0.0'].includes(hostname); } function isProductionHttpsUrl(value) { const url = urlFromValue(value); return Boolean(url && url.protocol === 'https:' && !isLocalHost(url.hostname.toLowerCase())); } function isAllowedHost(hostname, allowedHosts) { const normalized = String(hostname || '').toLowerCase(); return allowedHosts.some(host => normalized === host || normalized.endsWith(`.${host}`)); } function publicString(configPublic, keys) { for (const key of keys) { const value = configPublic?.[key]; if (typeof value === 'string' && value.trim()) return value.trim(); } return ''; } function missingPublicKeys(configPublic, keys) { return keys.filter(key => !publicString(configPublic, [key])); } function normalizeProvider(value) { return String(value || '').trim().toLowerCase(); } function normalizeSmsProvider(value) { return normalizeProvider(value).replace(/[_\s]/g, '-'); } function providerIn(provider, aliases) { return aliases.has(normalizeProvider(provider)); } function currentSmsProviderAliases() { const provider = normalizeSmsProvider(env('AUTH_SMS_PROVIDER', 'mock')); if (AUTH_PROVIDER_ALIASES.aliyunPnvs.has(provider)) return { provider: 'aliyun-pnvs', aliases: AUTH_PROVIDER_ALIASES.aliyunPnvs }; return { provider, aliases: new Set([provider]) }; } function validateActiveSmsProviderRows(rows, sourceLabel = 'database') { const expected = currentSmsProviderAliases(); if (!PRODUCTION_SMS_PROVIDERS.has(normalizeSmsProvider(env('AUTH_SMS_PROVIDER', 'mock')))) return; const activeRows = rows.filter(row => row.source === 'auth' && providerIn(row.provider, expected.aliases)); if (activeRows.length === 0) { block('db.auth_sms_provider_configured', 'AUTH_SMS_PROVIDER must have a matching active/testing tenant_auth_providers row', { provider: expected.provider, source: sourceLabel, }); return; } pass('db.auth_sms_provider_configured', 'AUTH_SMS_PROVIDER has matching active/testing tenant auth provider rows', { provider: expected.provider, count: activeRows.length, source: sourceLabel, }); } function validateProviderUrl({ id, value, message, allowedHosts = [], required = false, details = {}, }) { const raw = String(value || '').trim(); if (!raw) { if (required) block(`${id}.missing`, message || 'Production URL is required', details); return; } const url = urlFromValue(raw); if (!url || url.protocol !== 'https:' || isLocalHost(url.hostname.toLowerCase())) { block(`${id}.unsafe`, message || 'Production URL must use HTTPS and must not point to localhost', details); return; } if (allowedHosts.length > 0 && !isAllowedHost(url.hostname, allowedHosts)) { block(`${id}.host`, 'Provider endpoint host is not allowed for production', { ...details, host: url.hostname, allowedHosts, }); } } function missingPublicKeyGroups(configPublic, groups) { return groups .filter(group => !publicString(configPublic, group.keys)) .map(group => group.label); } function pnvsTemplateParamHasCodePlaceholder(configPublic) { const value = configPublic?.templateParam; if (!value || typeof value !== 'object' || Array.isArray(value)) return true; return Object.values(value).some(item => String(item) === '##code##'); } function blockMissingPublicConfig(row, missing) { if (missing.length === 0) return; block( `db.${row.source}.${safeProviderName(row.provider)}.public_required`, 'Provider public config is missing production-required fields', { tenantId: row.tenant_id, provider: row.provider, missing, }, ); } function validateAuthProviderPublicConfig(row) { const provider = normalizeProvider(row.provider); const configPublic = row.config_public || {}; const details = { tenantId: row.tenant_id, provider: row.provider }; if (provider === 'mock') { block('db.auth.mock_provider', 'Active/testing mock auth provider is not allowed in production', details); return; } if (providerIn(provider, AUTH_PROVIDER_ALIASES.aliyun)) { block( `db.auth.${safeProviderName(row.provider)}.legacy_sms_provider`, 'Traditional Aliyun SMS auth provider must not be active/testing in production; use aliyun-pnvs', details, ); return; } if (providerIn(provider, AUTH_PROVIDER_ALIASES.aliyunPnvs)) { blockMissingPublicConfig(row, missingPublicKeyGroups(configPublic, [ { label: 'signName', keys: ['signName'] }, { label: 'templateCode', keys: ['templateCode'] }, ])); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.endpoint`, value: publicString(configPublic, ['endpoint']), allowedHosts: ['aliyuncs.com'], details, }); if (!publicString(configPublic, ['regionId'])) { warn(`db.auth.${safeProviderName(row.provider)}.region`, 'Aliyun PNVS regionId is not set; default cn-hangzhou will be used', details); } if (!pnvsTemplateParamHasCodePlaceholder(configPublic)) { warn( `db.auth.${safeProviderName(row.provider)}.template_param`, 'Aliyun PNVS templateParam should include ##code##; backend will add code placeholder automatically', details, ); } return; } if (providerIn(provider, AUTH_PROVIDER_ALIASES.tencent)) { block( `db.auth.${safeProviderName(row.provider)}.legacy_sms_provider`, 'Tencent SMS auth provider must not be active/testing in production; use aliyun-pnvs', details, ); return; } if (providerIn(provider, AUTH_PROVIDER_ALIASES.wechatMiniapp)) { blockMissingPublicConfig(row, missingPublicKeyGroups(configPublic, [ { label: 'appId', keys: ['appId'] }, ])); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.endpoint`, value: publicString(configPublic, ['endpoint']), allowedHosts: ['weixin.qq.com'], details, }); return; } if (providerIn(provider, AUTH_PROVIDER_ALIASES.wechatWeb)) { blockMissingPublicConfig(row, missingPublicKeyGroups(configPublic, [ { label: 'appId', keys: ['appId'] }, ])); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.token_endpoint`, value: publicString(configPublic, ['tokenEndpoint', 'accessTokenEndpoint', 'endpoint']), allowedHosts: ['weixin.qq.com'], details, }); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.userinfo_endpoint`, value: publicString(configPublic, ['userInfoEndpoint', 'userinfoEndpoint']), allowedHosts: ['weixin.qq.com'], details, }); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.redirect_uri`, value: publicString(configPublic, ['redirectUri', 'callbackUrl']), details, }); return; } if (providerIn(provider, AUTH_PROVIDER_ALIASES.qq)) { blockMissingPublicConfig(row, missingPublicKeyGroups(configPublic, [ { label: 'appId/clientId', keys: ['appId', 'clientId'] }, { label: 'redirectUri/callbackUrl', keys: ['redirectUri', 'callbackUrl'] }, ])); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.redirect_uri`, value: publicString(configPublic, ['redirectUri', 'callbackUrl']), required: true, details, }); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.token_endpoint`, value: publicString(configPublic, ['tokenEndpoint', 'accessTokenEndpoint', 'endpoint']), allowedHosts: ['graph.qq.com'], details, }); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.openid_endpoint`, value: publicString(configPublic, ['openIdEndpoint', 'openidEndpoint']), allowedHosts: ['graph.qq.com'], details, }); validateProviderUrl({ id: `db.auth.${safeProviderName(row.provider)}.userinfo_endpoint`, value: publicString(configPublic, ['userInfoEndpoint', 'userinfoEndpoint']), allowedHosts: ['graph.qq.com'], details, }); return; } block('db.auth.unsupported_provider', 'Active/testing auth provider is not in the supported production provider list', details); } function validatePaymentProviderPublicConfig(row) { const provider = normalizeProvider(row.provider); const configPublic = row.config_public || {}; const details = { tenantId: row.tenant_id, provider: row.provider }; if (providerIn(provider, PAYMENT_PROVIDER_ALIASES.manual)) { warn('db.payment.manual_provider', 'Manual payment account is active; use only for offline collection or migration operations', details); return; } if (providerIn(provider, PAYMENT_PROVIDER_ALIASES.wechatPay)) { blockMissingPublicConfig(row, missingPublicKeyGroups(configPublic, [ { label: 'appId', keys: ['appId'] }, { label: 'merchantId/mchId', keys: ['merchantId', 'mchId'] }, { label: 'merchantSerialNo', keys: ['merchantSerialNo'] }, { label: 'notifyUrl', keys: ['notifyUrl'] }, ])); validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.notify_url`, value: publicString(configPublic, ['notifyUrl']), required: true, details, }); validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.refund_notify_url`, value: publicString(configPublic, ['refundNotifyUrl']), details, }); for (const [key, allowedHosts] of [ ['endpoint', ['api.mch.weixin.qq.com']], ['refundEndpoint', ['api.mch.weixin.qq.com']], ['refundQueryEndpoint', ['api.mch.weixin.qq.com']], ]) { validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.${key}`, value: publicString(configPublic, [key]), allowedHosts, details, }); } return; } if (providerIn(provider, PAYMENT_PROVIDER_ALIASES.alipay)) { blockMissingPublicConfig(row, missingPublicKeyGroups(configPublic, [ { label: 'appId', keys: ['appId'] }, { label: 'notifyUrl', keys: ['notifyUrl'] }, ])); validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.notify_url`, value: publicString(configPublic, ['notifyUrl']), required: true, details, }); validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.return_url`, value: publicString(configPublic, ['returnUrl']), details, }); validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.quit_url`, value: publicString(configPublic, ['quitUrl']), details, }); for (const [key, allowedHosts] of [ ['endpoint', ['openapi.alipay.com']], ['refundEndpoint', ['openapi.alipay.com']], ['refundQueryEndpoint', ['openapi.alipay.com']], ]) { validateProviderUrl({ id: `db.payment.${safeProviderName(row.provider)}.${key}`, value: publicString(configPublic, [key]), allowedHosts, details, }); } return; } block('db.payment.unsupported_provider', 'Active payment account is not in the supported production provider list', details); } function validateProviderConfigRows(inputRows) { const rows = inputRows.map(row => ({ source: row.source, tenant_id: row.tenant_id || row.tenantId || 'fixture-tenant', provider: row.provider, config_public: row.config_public || row.configPublic || {}, })); if (rows.length === 0) { warn('db.provider_fixture.empty', 'Provider config fixture did not contain any rows'); return; } validateActiveSmsProviderRows(rows, 'fixture'); for (const row of rows) { const secretPaths = findSecretLikePaths(row.config_public || {}); if (secretPaths.length > 0) { block(`db.${row.source}.${safeProviderName(row.provider)}.public_secret`, 'Provider public config contains secret-like keys', { tenantId: row.tenant_id, provider: row.provider, secretPathCount: secretPaths.length, }); } if (row.source === 'auth') validateAuthProviderPublicConfig(row); else if (row.source === 'payment') validatePaymentProviderPublicConfig(row); else block('db.provider_fixture.source', 'Provider config fixture row source must be auth or payment', { source: row.source }); } if (rows.every(row => findSecretLikePaths(row.config_public || {}).length === 0)) { pass('db.provider_public_config', 'Active provider public configs do not contain secret-like keys'); } } function tenantPublicUrlEntries(value, prefix = 'publicConfig') { if (!value || typeof value !== 'object') return []; if (Array.isArray(value)) { return value.flatMap((item, index) => tenantPublicUrlEntries(item, `${prefix}[${index}]`)); } return Object.entries(value).flatMap(([key, child]) => { const path = `${prefix}.${key}`; const current = /(?:url|uri)$/i.test(key) && typeof child === 'string' && child.trim() ? [[path, child.trim()]] : []; return [...current, ...tenantPublicUrlEntries(child, path)]; }); } function validateTenantConfigRows(inputRows, sourceLabel = 'fixture') { const rows = inputRows.map(row => ({ tenantId: row.tenant_id || row.tenantId || 'fixture-tenant', slug: row.slug || '', name: row.name || '', publicConfig: row.public_config || row.publicConfig || {}, brandingTheme: row.branding_theme || row.brandingTheme || {}, publishedTheme: row.published_theme || row.publishedTheme || {}, themeStatus: row.theme_status || row.themeStatus || null, publishedAt: row.published_at || row.publishedAt || null, })); if (rows.length === 0) { warn('db.tenant_config.empty', 'No active tenant configuration rows were found', { source: sourceLabel }); return; } const unsafeUrls = rows.flatMap(row => tenantPublicUrlEntries(row.publicConfig) .filter(([, value]) => !isProductionHttpsUrl(value)) .map(([path, value]) => ({ tenantId: row.tenantId, slug: row.slug, path, protocol: urlFromValue(value)?.protocol || 'invalid', host: hostFromUrl(value) || 'invalid', }))); if (unsafeUrls.length > 0) { block('db.tenant_public_urls', 'Active tenant public URL values must use production HTTPS URLs', { count: unsafeUrls.length, source: sourceLabel, samples: unsafeUrls.slice(0, 10), }); } else { pass('db.tenant_public_urls', 'Active tenant public URL values use production HTTPS URLs or are empty', { source: sourceLabel, }); } const unpublishedThemeRows = rows.filter(row => { const hasPublishedTheme = row.themeStatus === 'published' && Boolean(row.publishedAt) && Object.keys(row.publishedTheme).length > 0; const hasBrandingFallback = Object.keys(row.brandingTheme).length > 0; return !hasPublishedTheme && !hasBrandingFallback; }); if (unpublishedThemeRows.length > 0) { warn('db.tenant_theme_published', 'Some active tenants have no published theme or tenant branding theme; platform defaults will be used', { count: unpublishedThemeRows.length, source: sourceLabel, samples: unpublishedThemeRows.slice(0, 10).map(row => ({ tenantId: row.tenantId, slug: row.slug, name: row.name, })), }); } else { pass('db.tenant_theme_published', 'Active tenants have a published or branding fallback theme', { source: sourceLabel, }); } } function validateEnvironmentSafetyMarker(inputRow, sourceLabel = 'database') { if (!inputRow) { pass( 'db.environment.destructive_tests_disabled', 'No destructive-test authorization marker is present', { source: sourceLabel, markerPresent: false }, ); return; } const environment = String(inputRow.environment || '').trim().toLowerCase(); const allowValue = inputRow.allow_destructive_tests ?? inputRow.allowDestructiveTests; const details = { source: sourceLabel, markerPresent: true, environment: environment || 'invalid', allowDestructiveTests: allowValue === true, }; if (typeof allowValue !== 'boolean') { block( 'db.environment.destructive_tests_disabled', 'Database destructive-test marker must contain a boolean allow_destructive_tests value', details, ); return; } if (allowValue || !['production', 'staging'].includes(environment)) { block( 'db.environment.destructive_tests_disabled', 'Production readiness requires a production/staging marker with destructive tests disabled', details, ); return; } pass( 'db.environment.destructive_tests_disabled', 'Database is classified as production/staging with destructive tests disabled', details, ); } function isSecretLikeKey(key) { const normalized = key.toLowerCase().replace(/[-_\s]/g, ''); const allowedSecretRef = normalized === 'secretref' || normalized.endsWith('secretref'); return ( !allowedSecretRef && (normalized.includes('secret') || normalized.includes('password') || normalized.includes('token') || normalized.includes('privatekey') || normalized.includes('apikey') || normalized.includes('apiv3key') || normalized.includes('mchkey') || normalized.includes('signkey') || normalized.includes('aeskey') || normalized.includes('partnerkey')) ); } function findSecretLikePaths(value, prefix = 'configPublic') { if (!value || typeof value !== 'object') return []; if (Array.isArray(value)) { return value.flatMap((item, index) => findSecretLikePaths(item, `${prefix}[${index}]`)); } const paths = []; for (const [key, child] of Object.entries(value)) { const nextPath = `${prefix}.${key}`; if (isSecretLikeKey(key)) paths.push(nextPath); paths.push(...findSecretLikePaths(child, nextPath)); } return paths; } function safeProviderName(provider) { return String(provider || '').replace(/[^a-zA-Z0-9_.:-]/g, '_'); } function formatTableName(row) { return `${row.table_schema}.${row.table_name}`; } function validateRuntimeRoleEnvironment({ required = false } = {}) { const expectedRole = env('DB_EXPECTED_RUNTIME_ROLE', '').trim(); if (!expectedRole) { if (required) { block( 'env.db_expected_runtime_role', 'DB_EXPECTED_RUNTIME_ROLE must be tiku_api or tiku_worker for database readiness', ); } else { warn( 'env.db_expected_runtime_role', 'DB_EXPECTED_RUNTIME_ROLE is not checked until readiness runs with --check-db', ); } return ''; } if (!['tiku_api', 'tiku_worker'].includes(expectedRole)) { block( 'env.db_expected_runtime_role', 'DB_EXPECTED_RUNTIME_ROLE must be tiku_api or tiku_worker', { expectedRole }, ); return expectedRole; } let databaseUser = ''; try { databaseUser = decodeURIComponent(new URL(env('DATABASE_URL', DEFAULT_DATABASE_URL)).username || ''); } catch { // DATABASE_URL has its own blocker; keep this check fail-closed as well. } if (databaseUser !== expectedRole) { block( 'env.database_runtime_role', 'DATABASE_URL username must match DB_EXPECTED_RUNTIME_ROLE', { expectedRole, databaseUser: databaseUser || '(missing)' }, ); } else { pass( 'env.database_runtime_role', 'DATABASE_URL username matches the expected runtime role', { expectedRole }, ); } return expectedRole; } function validateEnv() { const nodeEnv = env('NODE_ENV', 'development'); if (nodeEnv !== 'production') block('env.node_env', 'NODE_ENV must be production for production readiness checks'); else pass('env.node_env', 'NODE_ENV is production'); const databaseUrl = env('DATABASE_URL', DEFAULT_DATABASE_URL); const databaseHost = hostFromUrl(databaseUrl); if (!databaseUrl || databaseUrl === DEFAULT_DATABASE_URL) { block('env.database_url', 'DATABASE_URL must point to the production Supabase/PostgreSQL database'); } else if (isLocalHost(databaseHost)) { warn('env.database_url.localhost', 'DATABASE_URL points to a local host; confirm this is intentional for self-hosted deployment'); } else { pass('env.database_url', 'DATABASE_URL is not the local development default'); } const corsOrigins = envList('CORS_ORIGIN', '*'); if (corsOrigins.includes('*')) { block('env.cors_origin', 'CORS_ORIGIN must not include * in production'); } else if (corsOrigins.length === 0) { block('env.cors_origin.empty', 'CORS_ORIGIN must include central platform or operations HTTPS origins'); } else { const unsafeOrigins = corsOrigins.filter(origin => { const host = hostFromUrl(origin); return !origin.startsWith('https://') || isLocalHost(host); }); if (unsafeOrigins.length > 0) { block('env.cors_origin.unsafe', 'CORS_ORIGIN must use production HTTPS origins only', { count: unsafeOrigins.length }); } else { pass('env.cors_origin', 'CORS_ORIGIN is restricted to HTTPS origins', { count: corsOrigins.length }); } } if (!envBool('CORS_TENANT_DOMAINS_ENABLED', false)) { block( 'env.cors_tenant_domains_enabled', 'CORS_TENANT_DOMAINS_ENABLED must be true for production custom tenant domains', ); } else { pass('env.cors_tenant_domains_enabled', 'Dynamic active tenant-domain CORS validation is enabled'); } const corsCacheNumbers = [ ['CORS_TENANT_DOMAIN_CACHE_TTL_MS', 60_000, 1_000, 600_000], ['CORS_TENANT_DOMAIN_NEGATIVE_CACHE_TTL_MS', 10_000, 1_000, 300_000], ['CORS_TENANT_DOMAIN_CACHE_MAX_ENTRIES', 10_000, 100, 100_000], ]; for (const [key, fallback, min, max] of corsCacheNumbers) { const value = envNumber(key, fallback); if (!Number.isFinite(value) || value < min || value > max) { block(`env.${key.toLowerCase()}`, `${key} must be between ${min} and ${max}`); } else { pass(`env.${key.toLowerCase()}`, `${key} is within the production safety range`, { value }); } } validateRuntimeRoleEnvironment({ required: !skipDb }); const authSmsProvider = normalizeSmsProvider(env('AUTH_SMS_PROVIDER', 'mock')); if (!PRODUCTION_SMS_PROVIDERS.has(authSmsProvider)) { block('env.auth_sms_provider', 'AUTH_SMS_PROVIDER must be aliyun-pnvs in production', { provider: authSmsProvider || '(empty)', }); } else { pass('env.auth_sms_provider', 'AUTH_SMS_PROVIDER is aliyun-pnvs for production SMS authentication', { provider: authSmsProvider }); } if (isUnsafeSecret(env('AUTH_CODE_PEPPER', DEFAULT_AUTH_CODE_PEPPER), DEFAULT_AUTH_CODE_PEPPER)) { block('env.auth_code_pepper', 'AUTH_CODE_PEPPER must be a strong production secret'); } else { pass('env.auth_code_pepper', 'AUTH_CODE_PEPPER looks production-grade'); } if (isUnsafeSecret(env('AUTH_SESSION_SECRET', DEFAULT_AUTH_SESSION_SECRET), DEFAULT_AUTH_SESSION_SECRET)) { block('env.auth_session_secret', 'AUTH_SESSION_SECRET must be a strong production secret'); } else { pass('env.auth_session_secret', 'AUTH_SESSION_SECRET looks production-grade'); } const jwksUrl = env('AUTH_JWT_JWKS_URL', ''); const jwtSecret = env('AUTH_JWT_SECRET', DEFAULT_AUTH_JWT_SECRET); if (jwksUrl) { const host = hostFromUrl(jwksUrl); if (!jwksUrl.startsWith('https://') || isLocalHost(host)) { block('env.auth_jwt_jwks_url', 'AUTH_JWT_JWKS_URL must be an HTTPS production URL'); } else { pass('env.auth_jwt_jwks_url', 'AUTH_JWT_JWKS_URL is configured'); } if (!env('AUTH_JWT_ISSUER', '').trim()) { block('env.auth_jwt_issuer', 'AUTH_JWT_ISSUER is required when AUTH_JWT_JWKS_URL is configured'); } else { pass('env.auth_jwt_issuer', 'AUTH_JWT_ISSUER is configured for JWKS verification'); } } else if (isUnsafeSecret(jwtSecret, DEFAULT_AUTH_JWT_SECRET)) { block('env.auth_jwt_secret', 'AUTH_JWT_SECRET or AUTH_JWT_JWKS_URL must be configured for production JWT verification'); } else { pass('env.auth_jwt_secret', 'AUTH_JWT_SECRET looks production-grade'); } if (envBool('ALLOW_LEGACY_AUTH_HEADERS', false)) { block('env.allow_legacy_auth_headers', 'ALLOW_LEGACY_AUTH_HEADERS must be false in production'); } else { pass('env.allow_legacy_auth_headers', 'legacy x-user-id auth headers are disabled'); } if (envBool('ALLOW_PLATFORM_ADMIN_KEY', false)) { block('env.allow_platform_admin_key', 'ALLOW_PLATFORM_ADMIN_KEY must be false in production'); } else { pass('env.allow_platform_admin_key', 'platform admin API key compatibility is disabled'); } if (isUnsafeSecret(env('PLATFORM_ADMIN_API_KEY', DEFAULT_PLATFORM_ADMIN_API_KEY), DEFAULT_PLATFORM_ADMIN_API_KEY)) { warn('env.platform_admin_api_key', 'PLATFORM_ADMIN_API_KEY is weak/default; keep ALLOW_PLATFORM_ADMIN_KEY=false and rotate before any temporary use'); } else { pass('env.platform_admin_api_key', 'PLATFORM_ADMIN_API_KEY is not default'); } const maxJson = envNumber('MAX_JSON_BODY_BYTES', 1024 * 1024); const maxImportJson = envNumber('MAX_IMPORT_JSON_BODY_BYTES', 10 * 1024 * 1024); if (maxJson > HARD_MAX_JSON_BODY_BYTES || maxImportJson > HARD_MAX_JSON_BODY_BYTES) { block('env.body_size', 'JSON body limits must stay at or below 50MB'); } else { pass('env.body_size', 'JSON body limits are bounded'); } const storageProvider = env('STORAGE_DEFAULT_PROVIDER', 'local_dev').trim(); if (storageProvider === 'local_dev') { block('env.storage_provider', 'STORAGE_DEFAULT_PROVIDER=local_dev is not allowed for production assets'); } else if (!PRODUCTION_STORAGE_PROVIDERS.has(storageProvider)) { block('env.storage_provider.unsupported', 'STORAGE_DEFAULT_PROVIDER must be aliyun_oss, tencent_cos or supabase_storage in production', { provider: storageProvider || '(empty)', }); } else { pass('env.storage_provider', 'STORAGE_DEFAULT_PROVIDER is production-capable', { provider: storageProvider }); } if (!env('STORAGE_DEFAULT_BUCKET', '')) { block('env.storage_bucket', 'STORAGE_DEFAULT_BUCKET is required'); } else { pass('env.storage_bucket', 'STORAGE_DEFAULT_BUCKET is configured'); } if (!envBool('STORAGE_REQUIRE_TENANT_PREFIX', true)) { block('env.storage_tenant_prefix', 'STORAGE_REQUIRE_TENANT_PREFIX must remain true to protect tenant assets'); } else { pass('env.storage_tenant_prefix', 'tenant-prefixed object keys are required'); } if (env('STORAGE_PUBLIC_BASE_URL', '') && !isProductionHttpsUrl(env('STORAGE_PUBLIC_BASE_URL', ''))) { block('env.storage_public_base_url', 'STORAGE_PUBLIC_BASE_URL must be a production HTTPS URL when configured'); } if (storageProvider === 'aliyun_oss') { for (const key of ['ALIYUN_OSS_REGION', 'ALIYUN_OSS_ENDPOINT', 'ALIYUN_OSS_ACCESS_KEY_ID', 'ALIYUN_OSS_ACCESS_KEY_SECRET']) { if (!env(key, '')) block(`env.${key.toLowerCase()}`, `${key} is required for aliyun_oss`); } validateProviderUrl({ id: 'env.aliyun_oss_endpoint', value: env('ALIYUN_OSS_ENDPOINT', ''), message: 'ALIYUN_OSS_ENDPOINT must be a production HTTPS endpoint', allowedHosts: ['aliyuncs.com'], required: true, }); if (envBool('ALIYUN_OSS_INTERNAL', false)) { block('env.aliyun_oss_internal', 'ALIYUN_OSS_INTERNAL=true is not allowed for user-facing production signed URLs'); } } if (storageProvider === 'tencent_cos') { for (const key of ['TENCENT_COS_REGION', 'TENCENT_COS_APP_ID', 'TENCENT_COS_SECRET_ID', 'TENCENT_COS_SECRET_KEY']) { if (!env(key, '')) block(`env.${key.toLowerCase()}`, `${key} is required for tencent_cos`); } } if (storageProvider === 'supabase_storage') { for (const key of ['SUPABASE_STORAGE_URL', 'SUPABASE_STORAGE_SERVICE_KEY']) { if (!env(key, '')) block(`env.${key.toLowerCase()}`, `${key} is required for supabase_storage`); } validateProviderUrl({ id: 'env.supabase_storage_url', value: env('SUPABASE_STORAGE_URL', ''), message: 'SUPABASE_STORAGE_URL must be a production HTTPS URL', required: true, }); } if (envList('STORAGE_ALLOWED_MIME_TYPES').includes('application/octet-stream')) { warn('env.storage_octet_stream', 'application/octet-stream is allowed; consider removing it after import migration is stable'); } const scannerModes = envList('WORKER_ASSET_SECURITY_SCANNER', 'metadata_rules').map(item => item.toLowerCase()); const unsupportedScannerModes = scannerModes.filter(mode => mode !== 'metadata_rules' && mode !== 'http'); if (scannerModes.length === 0) { block('env.asset_security_scanner.empty', 'WORKER_ASSET_SECURITY_SCANNER must include metadata_rules and a production scanner'); } else if (unsupportedScannerModes.length > 0) { block('env.asset_security_scanner.unsupported', 'WORKER_ASSET_SECURITY_SCANNER contains unsupported modes', { modes: unsupportedScannerModes, }); } else if (!scannerModes.includes('http')) { block('env.asset_security_scanner.external', 'Production assets require WORKER_ASSET_SECURITY_SCANNER=http or metadata_rules,http'); } else { pass('env.asset_security_scanner', 'external asset security scanner is enabled', { modes: scannerModes }); } const scannerEndpoint = env('WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT', ''); if (scannerModes.includes('http')) { const scannerHost = hostFromUrl(scannerEndpoint); if (!scannerEndpoint) { block('env.asset_security_scan_http_endpoint', 'WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT is required when http scanner is enabled'); } else if (!scannerEndpoint.startsWith('https://') || isLocalHost(scannerHost)) { block('env.asset_security_scan_http_endpoint.unsafe', 'WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT must be a production HTTPS URL'); } else { pass('env.asset_security_scan_http_endpoint', 'asset security scanner endpoint is HTTPS'); } if (isUnsafeSecret(env('WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN', ''), '')) { block('env.asset_security_scan_http_token', 'WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN must be a strong shared secret or service token'); } else { pass('env.asset_security_scan_http_token', 'asset security scanner token looks production-grade'); } } const scannerTimeout = envNumber('WORKER_ASSET_SECURITY_SCAN_HTTP_TIMEOUT_MS', 10_000); if (scannerTimeout <= 0 || scannerTimeout > 60_000) { block('env.asset_security_scan_http_timeout', 'WORKER_ASSET_SECURITY_SCAN_HTTP_TIMEOUT_MS must be between 1 and 60000'); } else { pass('env.asset_security_scan_http_timeout', 'asset security scanner timeout is bounded'); } if (envBool('WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN', false)) { block('env.asset_security_scan_fail_open', 'WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN must be false in production'); } else { pass('env.asset_security_scan_fail_open', 'asset security scanning fails closed'); } if (envBool('WORKER_CRM_ALLOW_INSECURE_LOCALHOST', false)) { block('env.worker_crm_insecure_localhost', 'WORKER_CRM_ALLOW_INSECURE_LOCALHOST must be false in production'); } else { pass('env.worker_crm_insecure_localhost', 'CRM worker insecure localhost webhook mode is disabled'); } if (envBool('WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST', false)) { block('env.worker_platform_audit_notification_insecure_localhost', 'WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST must be false in production'); } else { pass('env.worker_platform_audit_notification_insecure_localhost', 'Platform audit notification worker insecure localhost webhook mode is disabled'); } if (envBool('WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST', false)) { block('env.worker_platform_dunning_notification_insecure_localhost', 'WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST must be false in production'); } else { pass('env.worker_platform_dunning_notification_insecure_localhost', 'Platform dunning notification worker insecure localhost webhook mode is disabled'); } const requiredPositiveNumbers = [ 'WORKER_CRM_BATCH_SIZE', 'WORKER_COMMERCE_BATCH_SIZE', 'WORKER_ASSET_BATCH_SIZE', 'WORKER_IMPORT_BATCH_SIZE', 'WORKER_PUBLIC_BANK_SYNC_BATCH_SIZE', ]; for (const key of requiredPositiveNumbers) { if (envNumber(key, 1) <= 0) block(`env.${key.toLowerCase()}`, `${key} must be greater than 0`); } const pollIntervals = { WORKER_CRM_POLL_INTERVAL_MS: 10_000, WORKER_COMMERCE_POLL_INTERVAL_MS: 30_000, WORKER_PROVIDER_BILL_POLL_INTERVAL_MS: 60_000, WORKER_PLATFORM_DUNNING_NOTIFICATION_POLL_INTERVAL_MS: 30_000, WORKER_PLATFORM_AUDIT_NOTIFICATION_POLL_INTERVAL_MS: 30_000, WORKER_ASSET_POLL_INTERVAL_MS: 30_000, WORKER_IMPORT_POLL_INTERVAL_MS: 10_000, WORKER_PUBLIC_BANK_SYNC_POLL_INTERVAL_MS: 60_000, WORKER_EXPORT_POLL_INTERVAL_MS: 10_000, }; for (const [key, fallback] of Object.entries(pollIntervals)) { const value = envNumber(key, fallback); if (!Number.isFinite(value) || value < 1_000 || value > 3_600_000) { block(`env.${key.toLowerCase()}`, `${key} must be between 1000 and 3600000`); } } const importLeaseSeconds = envNumber('WORKER_IMPORT_LEASE_SECONDS', 120); const importHeartbeatIntervalMs = envNumber('WORKER_IMPORT_HEARTBEAT_INTERVAL_MS', 30_000); if (!Number.isFinite(importLeaseSeconds) || importLeaseSeconds < 10 || importLeaseSeconds > 86_400) { block('env.worker_import_lease_seconds', 'WORKER_IMPORT_LEASE_SECONDS must be between 10 and 86400'); } if ( !Number.isFinite(importHeartbeatIntervalMs) || importHeartbeatIntervalMs < 1_000 || importHeartbeatIntervalMs >= importLeaseSeconds * 500 ) { block( 'env.worker_import_heartbeat_interval_ms', 'WORKER_IMPORT_HEARTBEAT_INTERVAL_MS must be at least 1000 and less than half the import lease duration', ); } } async function validateDatabase() { if (skipDb) { warn('db.skipped', 'Database readiness checks skipped; run with --check-db after production DATABASE_URL is configured'); return; } const pool = new pg.Pool({ connectionString: env('DATABASE_URL', DEFAULT_DATABASE_URL), max: 2 }); try { const expectedRuntimeRole = env('DB_EXPECTED_RUNTIME_ROLE', '').trim(); const runtimeIdentityRows = await pool.query(` select current_user, session_user, current_database() as database_name `); const runtimeIdentity = runtimeIdentityRows.rows[0] || {}; if (!['tiku_api', 'tiku_worker'].includes(expectedRuntimeRole) || runtimeIdentity.current_user !== expectedRuntimeRole || runtimeIdentity.session_user !== expectedRuntimeRole) { block( 'db.runtime_role.identity', 'Database readiness must connect directly as DB_EXPECTED_RUNTIME_ROLE without SET ROLE indirection', { expectedRole: expectedRuntimeRole || '(missing)', currentUser: runtimeIdentity.current_user || '(missing)', sessionUser: runtimeIdentity.session_user || '(missing)', database: runtimeIdentity.database_name || '(missing)', }, ); } else { pass( 'db.runtime_role.identity', 'Database connection uses the expected dedicated runtime role', { role: expectedRuntimeRole, database: runtimeIdentity.database_name }, ); } const runtimeRoleRows = await pool.query(` select r.rolname, r.rolsuper, r.rolinherit, r.rolcreaterole, r.rolcreatedb, r.rolcanlogin, r.rolreplication, r.rolbypassrls, r.rolconfig, exists ( select 1 from pg_auth_members membership where membership.member = r.oid ) as has_parent_roles from pg_roles r where r.rolname = any(array['tiku_api', 'tiku_worker']::name[]) order by r.rolname `); const runtimeRoleByName = new Map(runtimeRoleRows.rows.map(row => [row.rolname, row])); const unsafeRuntimeRoles = ['tiku_api', 'tiku_worker'].flatMap(roleName => { const row = runtimeRoleByName.get(roleName); if (!row) return [{ role: roleName, issue: 'missing' }]; const issues = []; if (row.rolsuper) issues.push('SUPERUSER'); if (row.rolinherit) issues.push('INHERIT'); if (row.rolcreaterole) issues.push('CREATEROLE'); if (row.rolcreatedb) issues.push('CREATEDB'); if (!row.rolcanlogin) issues.push('NOLOGIN'); if (row.rolreplication) issues.push('REPLICATION'); if (!row.rolbypassrls) issues.push('missing intentional BYPASSRLS'); if (row.has_parent_roles) issues.push('role membership'); const roleConfig = Array.isArray(row.rolconfig) ? row.rolconfig.map(String) : []; if (!roleConfig.includes('search_path=pg_catalog, public, extensions')) issues.push('unsafe search_path'); return issues.map(issue => ({ role: roleName, issue })); }); if (unsafeRuntimeRoles.length > 0) { block( 'db.runtime_role.attributes', 'Runtime roles must be LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION BYPASSRLS with no memberships', { issues: unsafeRuntimeRoles }, ); } else { pass( 'db.runtime_role.attributes', 'Dedicated API and worker roles have the required constrained attributes', ); } const runtimeSchemaPrivilegeRows = await pool.query(` select r.rolname, has_schema_privilege(r.oid, 'public', 'USAGE') as public_usage, has_schema_privilege(r.oid, 'public', 'CREATE') as public_create, has_schema_privilege(r.oid, 'app', 'USAGE') as app_usage, has_schema_privilege(r.oid, 'app', 'CREATE') as app_create, has_schema_privilege(r.oid, 'app_private', 'USAGE') as private_usage, has_schema_privilege(r.oid, 'app_private', 'CREATE') as private_create, has_schema_privilege(r.oid, 'extensions', 'USAGE') as extensions_usage, has_schema_privilege(r.oid, 'extensions', 'CREATE') as extensions_create from pg_roles r where r.rolname = any(array['tiku_api', 'tiku_worker']::name[]) order by r.rolname `); const unsafeSchemaPrivileges = runtimeSchemaPrivilegeRows.rows.filter(row => ( !row.public_usage || !row.private_usage || !row.extensions_usage || row.public_create || row.app_create || row.private_create || row.extensions_create || (row.rolname === 'tiku_api' && !row.app_usage) || (row.rolname === 'tiku_worker' && row.app_usage) )); if (runtimeSchemaPrivilegeRows.rowCount !== 2 || unsafeSchemaPrivileges.length > 0) { block( 'db.runtime_role.schema_acl', 'Runtime roles must have only the reviewed schema USAGE privileges and no effective CREATE privilege', { roles: runtimeSchemaPrivilegeRows.rows }, ); } else { pass( 'db.runtime_role.schema_acl', 'Runtime roles cannot create persistent objects in application schemas', ); } const requiredExtensionRows = await pool.query(` select extension.extname, namespace.nspname as schema_name, count(procedure_row.oid)::integer as function_count, count(procedure_row.oid) filter ( where has_function_privilege('anon', procedure_row.oid, 'EXECUTE') )::integer as anon_execute_count, count(procedure_row.oid) filter ( where has_function_privilege('authenticated', procedure_row.oid, 'EXECUTE') )::integer as authenticated_execute_count, count(procedure_row.oid) filter ( where has_function_privilege('tiku_api', procedure_row.oid, 'EXECUTE') )::integer as api_execute_count, count(procedure_row.oid) filter ( where has_function_privilege('tiku_worker', procedure_row.oid, 'EXECUTE') )::integer as worker_execute_count from pg_extension extension join pg_namespace namespace on namespace.oid = extension.extnamespace left join pg_depend dependency on dependency.refclassid = 'pg_extension'::regclass and dependency.refobjid = extension.oid and dependency.classid = 'pg_proc'::regclass and dependency.deptype = 'e' left join pg_proc procedure_row on procedure_row.oid = dependency.objid where extension.extname = any(array['pgcrypto', 'citext', 'ltree', 'pg_trgm']::name[]) group by extension.extname, namespace.nspname order by extension.extname `); const requiredExtensionNames = new Set(['pgcrypto', 'citext', 'ltree', 'pg_trgm']); const unsafeExtensions = requiredExtensionRows.rows.flatMap(row => { const issues = []; if (row.schema_name !== 'extensions') issues.push(`schema=${row.schema_name}`); const functionCount = Number(row.function_count); const backendExecuteCount = row.extname === 'pgcrypto' ? 0 : functionCount; if (functionCount <= 0) issues.push('no extension functions found'); if (Number(row.anon_execute_count) !== 0) issues.push(`anon EXECUTE=${row.anon_execute_count}`); if (Number(row.authenticated_execute_count) !== 0) issues.push(`authenticated EXECUTE=${row.authenticated_execute_count}`); if (Number(row.api_execute_count) !== backendExecuteCount) issues.push(`tiku_api EXECUTE=${row.api_execute_count}/${backendExecuteCount}`); if (Number(row.worker_execute_count) !== backendExecuteCount) issues.push(`tiku_worker EXECUTE=${row.worker_execute_count}/${backendExecuteCount}`); return issues.map(issue => ({ extension: row.extname, issue })); }); const observedExtensionNames = new Set(requiredExtensionRows.rows.map(row => row.extname)); const missingExtensions = [...requiredExtensionNames].filter(name => !observedExtensionNames.has(name)); if (missingExtensions.length > 0 || unsafeExtensions.length > 0) { block( 'db.extensions.isolation', 'Required extensions must live outside public with reviewed client/backend function ACLs', { missingExtensions, issues: unsafeExtensions }, ); } else { pass( 'db.extensions.isolation', 'Required extensions are isolated in extensions with reviewed function ACLs', ); } const runtimeTablePrivilegeRows = await pool.query(` select r.rolname, n.nspname as table_schema, c.relname as table_name, has_table_privilege(r.oid, c.oid, 'SELECT') as can_select, has_table_privilege(r.oid, c.oid, 'INSERT') as can_insert, has_table_privilege(r.oid, c.oid, 'UPDATE') as can_update, has_table_privilege(r.oid, c.oid, 'DELETE') as can_delete, has_table_privilege(r.oid, c.oid, 'TRUNCATE') as can_truncate, has_table_privilege(r.oid, c.oid, 'REFERENCES') as can_reference, has_table_privilege(r.oid, c.oid, 'TRIGGER') as can_trigger from pg_roles r cross join pg_class c join pg_namespace n on n.oid = c.relnamespace where r.rolname = any(array['tiku_api', 'tiku_worker']::name[]) and n.nspname in ('public', 'app_private') and c.relkind in ('r', 'p', 'v', 'm', 'f') order by r.rolname, n.nspname, c.relname `); const apiPrivateWriteMatrix = new Map([ ['auth_sessions', { insert: true, update: true, delete: false }], ['tenant_secrets', { insert: true, update: true, delete: false }], ['platform_secrets', { insert: true, update: true, delete: false }], ['sms_send_rate_limits', { insert: true, update: true, delete: true }], ]); const requiredPrivateTables = new Set([ 'auth_sessions', 'environment_safety', 'platform_secrets', 'sms_send_rate_limits', 'tenant_secrets', ]); const observedPrivateTables = new Set( runtimeTablePrivilegeRows.rows .filter(row => row.rolname === 'tiku_api' && row.table_schema === 'app_private') .map(row => row.table_name), ); const missingPrivateTables = [...requiredPrivateTables] .filter(tableName => !observedPrivateTables.has(tableName)); const unsafeTablePrivileges = runtimeTablePrivilegeRows.rows.flatMap(row => { const issues = []; if (!row.can_select) issues.push('missing SELECT'); if (row.can_truncate) issues.push('TRUNCATE'); if (row.can_reference) issues.push('REFERENCES'); if (row.can_trigger) issues.push('TRIGGER'); if (row.table_schema === 'public') { if (!row.can_insert) issues.push('missing INSERT'); if (!row.can_update) issues.push('missing UPDATE'); if (!row.can_delete) issues.push('missing DELETE'); } else { const expectedWrites = row.rolname === 'tiku_api' ? apiPrivateWriteMatrix.get(row.table_name) || { insert: false, update: false, delete: false } : { insert: false, update: false, delete: false }; if (row.can_insert !== expectedWrites.insert) issues.push(`INSERT=${row.can_insert}`); if (row.can_update !== expectedWrites.update) issues.push(`UPDATE=${row.can_update}`); if (row.can_delete !== expectedWrites.delete) issues.push(`DELETE=${row.can_delete}`); } return issues.map(issue => ({ role: row.rolname, object: `${row.table_schema}.${row.table_name}`, issue, })); }); if (missingPrivateTables.length > 0 || unsafeTablePrivileges.length > 0) { block( 'db.runtime_role.table_acl', 'Runtime table privileges must match the API/worker least-privilege matrix', { checkedObjects: runtimeTablePrivilegeRows.rowCount, missingPrivateTables, issues: unsafeTablePrivileges.slice(0, 50), }, ); } else { pass( 'db.runtime_role.table_acl', 'Runtime table privileges match the API/worker least-privilege matrix', { checkedObjects: runtimeTablePrivilegeRows.rowCount }, ); } const runtimeFunctionPrivilegeRows = await pool.query(` select r.rolname, n.nspname as function_schema, p.proname as function_name, oidvectortypes(p.proargtypes) as argument_types from pg_roles r cross join pg_proc p join pg_namespace n on n.oid = p.pronamespace where r.rolname = any(array['tiku_api', 'tiku_worker']::name[]) and n.nspname in ('public', 'app', 'app_private') and has_function_privilege(r.oid, p.oid, 'EXECUTE') order by r.rolname, n.nspname, p.proname, argument_types `); const allowedRuntimeFunctions = new Map([ ['tiku_api', new Set([ 'app.auth_user_exists(uuid)', 'app.production_migration_history(text)', 'app.uuid_array_from_jsonb(jsonb)', 'app.public_question_bank_grant_allows(uuid[], uuid[], uuid, uuid[])', 'app.public_question_bank_subscription_allows(jsonb, jsonb, uuid, uuid, uuid[])', ])], ['tiku_worker', new Set()], ]); const observedRuntimeFunctions = new Map([ ['tiku_api', new Set()], ['tiku_worker', new Set()], ]); const unexpectedRuntimeFunctions = []; for (const row of runtimeFunctionPrivilegeRows.rows) { const signature = `${row.function_schema}.${row.function_name}(${row.argument_types})`; observedRuntimeFunctions.get(row.rolname)?.add(signature); if (!allowedRuntimeFunctions.get(row.rolname)?.has(signature)) { unexpectedRuntimeFunctions.push({ role: row.rolname, function: signature }); } } const missingRuntimeFunctions = []; for (const [roleName, expectedFunctions] of allowedRuntimeFunctions) { for (const signature of expectedFunctions) { if (!observedRuntimeFunctions.get(roleName)?.has(signature)) { missingRuntimeFunctions.push({ role: roleName, function: signature }); } } } if (unexpectedRuntimeFunctions.length > 0 || missingRuntimeFunctions.length > 0) { block( 'db.runtime_role.function_acl', 'Runtime function EXECUTE privileges must match the reviewed allowlist', { unexpectedRuntimeFunctions, missingRuntimeFunctions }, ); } else { pass( 'db.runtime_role.function_acl', 'Runtime function EXECUTE privileges match the reviewed allowlist', { checkedGrants: runtimeFunctionPrivilegeRows.rowCount }, ); } const runtimeAuthPrivilegeRows = await pool.query(` select r.rolname, has_schema_privilege(r.oid, 'auth', 'USAGE') as auth_schema_usage, has_table_privilege(r.oid, 'auth.users', 'SELECT') as auth_users_select, has_function_privilege(r.oid, 'app.auth_user_exists(uuid)', 'EXECUTE') as auth_user_exists_execute from pg_roles r where r.rolname = any(array['tiku_api', 'tiku_worker']::name[]) order by r.rolname `); const unsafeAuthPrivileges = runtimeAuthPrivilegeRows.rows.filter(row => ( row.auth_schema_usage || row.auth_users_select || (row.rolname === 'tiku_api' && !row.auth_user_exists_execute) || (row.rolname === 'tiku_worker' && row.auth_user_exists_execute) )); if (runtimeAuthPrivilegeRows.rowCount !== 2 || unsafeAuthPrivileges.length > 0) { block( 'db.runtime_role.auth_acl', 'Runtime roles must not access auth.users directly; only tiku_api may execute the boolean existence boundary', { roles: runtimeAuthPrivilegeRows.rows }, ); } else { pass( 'db.runtime_role.auth_acl', 'Auth data remains private while tiku_api can validate an Auth UUID through the reviewed boolean boundary', ); } const runtimeOwnerRows = await pool.query(` with owned_objects as ( select c.relowner as owner_oid from pg_class c join pg_namespace n on n.oid = c.relnamespace where n.nspname in ('public', 'app', 'app_private') union all select p.proowner from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname in ('public', 'app', 'app_private') union all select t.typowner from pg_type t join pg_namespace n on n.oid = t.typnamespace where n.nspname in ('public', 'app', 'app_private') union all select n.nspowner from pg_namespace n where n.nspname in ('public', 'app', 'app_private') union all select d.datdba from pg_database d where d.datname = current_database() ) select owner_role.rolname, count(*)::int as object_count from owned_objects objects join pg_roles owner_role on owner_role.oid = objects.owner_oid where owner_role.rolname = any(array['tiku_api', 'tiku_worker']::name[]) group by owner_role.rolname `); if (runtimeOwnerRows.rowCount > 0) { block( 'db.runtime_role.ownership', 'Runtime roles must not own the database, schemas, relations, functions or types', { owners: runtimeOwnerRows.rows }, ); } else { pass( 'db.runtime_role.ownership', 'Runtime roles own no database or application schema objects', ); } const runtimeDdlRows = await pool.query(` select r.rolname, has_database_privilege(r.oid, current_database(), 'CREATE') as database_create, has_schema_privilege(r.oid, 'public', 'CREATE') as public_create, has_schema_privilege(r.oid, 'app', 'CREATE') as app_create, has_schema_privilege(r.oid, 'app_private', 'CREATE') as private_create, has_schema_privilege(r.oid, 'extensions', 'CREATE') as extensions_create from pg_roles r where r.rolname = any(array['tiku_api', 'tiku_worker']::name[]) order by r.rolname `); const runtimeDdlAllowed = runtimeDdlRows.rows.filter(row => ( row.database_create || row.public_create || row.app_create || row.private_create || row.extensions_create )); if (runtimeDdlRows.rowCount !== 2 || runtimeDdlAllowed.length > 0 || runtimeOwnerRows.rowCount > 0) { block( 'db.runtime_role.ddl_denied', 'Runtime roles must have no effective persistent database/schema CREATE privilege or object ownership', { roles: runtimeDdlRows.rows, owners: runtimeOwnerRows.rows }, ); } else { pass( 'db.runtime_role.ddl_denied', 'Runtime roles are denied persistent DDL by effective privilege and ownership checks', ); } const environmentSafetyTable = await pool.query(` select to_regclass('app_private.environment_safety')::text as table_name `); if (!environmentSafetyTable.rows[0]?.table_name) { block( 'db.environment.destructive_tests_disabled', 'Database safety marker table is missing; apply all production migrations', { source: 'database', markerPresent: false, tablePresent: false }, ); } else { const environmentSafetyRows = await pool.query(` select environment, allow_destructive_tests from app_private.environment_safety where id = true limit 1 `); validateEnvironmentSafetyMarker(environmentSafetyRows.rows[0] || null, 'database'); } const repositoryMigrationState = loadRepositoryMigrationState(); const migrationHistoryBoundary = await pool.query(` select to_regprocedure('app.production_migration_history(text)')::text as function_name `); if (!repositoryMigrationState.latestVersion) { validateMigrationHistory(null, 'database'); } else if (!migrationHistoryBoundary.rows[0]?.function_name) { block( 'db.migrations.current', 'Production migration history boundary is missing; apply the repository migrations', { source: 'database', expectedVersion: repositoryMigrationState.latestVersion, helperPresent: false, }, ); } else { try { const migrationHistoryRows = await pool.query(` select latest_version, applied_count, distinct_version_count, expected_version_applied from app.production_migration_history($1::text) `, [repositoryMigrationState.latestVersion]); validateMigrationHistory(migrationHistoryRows.rows[0] || null, 'database'); } catch (error) { block( 'db.migrations.current', 'Production migration history boundary could not be executed', { source: 'database', expectedVersion: repositoryMigrationState.latestVersion, errorCode: error && typeof error === 'object' && 'code' in error ? error.code : '', }, ); } } const tenantRows = await pool.query(` select id, name, status from public.tenants where status = 'active' order by created_at asc `); if (tenantRows.rowCount === 0) block('db.tenants', 'No active tenant exists in the production database'); else pass('db.tenants', 'Active tenants found', { count: tenantRows.rowCount }); const tenantConfigRows = await pool.query(` select t.id as tenant_id, t.slug::text, t.name, coalesce(s.public_config, '{}'::jsonb) as public_config, coalesce(b.theme, '{}'::jsonb) as branding_theme, coalesce(tc.active_theme, '{}'::jsonb) as published_theme, tc.status as theme_status, tc.published_at from public.tenants t left join public.tenant_settings s on s.tenant_id = t.id left join public.tenant_branding b on b.tenant_id = t.id left join public.tenant_theme_configs tc on tc.tenant_id = t.id where t.status = 'active' order by t.created_at asc `); validateTenantConfigRows(tenantConfigRows.rows, 'database'); const publicSecretRows = await pool.query(` select source, tenant_id, provider, config_public from ( select 'auth' as source, tenant_id, provider, config_public from public.tenant_auth_providers where status in ('active', 'testing') union all select 'payment' as source, tenant_id, provider, config_public from public.tenant_payment_accounts where status = 'active' ) providers `); for (const row of publicSecretRows.rows) { const secretPaths = findSecretLikePaths(row.config_public || {}); if (secretPaths.length > 0) { block(`db.${row.source}.${safeProviderName(row.provider)}.public_secret`, 'Provider public config contains secret-like keys', { tenantId: row.tenant_id, provider: row.provider, secretPathCount: secretPaths.length, }); } if (row.source === 'auth') validateAuthProviderPublicConfig(row); if (row.source === 'payment') validatePaymentProviderPublicConfig(row); } if (publicSecretRows.rows.every(row => findSecretLikePaths(row.config_public || {}).length === 0)) { pass('db.provider_public_config', 'Active provider public configs do not contain secret-like keys'); } validateActiveSmsProviderRows(publicSecretRows.rows, 'database'); const activePlatformAdminRows = await pool.query(` select id, username, phone, auth_user_id from public.platform_users where primary_role = 'platform_admin' and status = 'active' order by created_at asc limit 20 `); if (activePlatformAdminRows.rowCount === 0) { block('db.platform_admin_active', 'At least one active platform admin user is required'); } else { pass('db.platform_admin_active', 'Active platform admin users found', { count: activePlatformAdminRows.rowCount }); } const activePlatformAdminsWithoutAuth = await pool.query(` select id, username, phone from public.platform_users where primary_role = 'platform_admin' and status = 'active' and auth_user_id is null order by created_at asc limit 20 `); if (activePlatformAdminsWithoutAuth.rowCount > 0) { block('db.platform_admin_auth_binding', 'Active platform admin users must be bound to Supabase Auth users', { count: activePlatformAdminsWithoutAuth.rowCount, samples: activePlatformAdminsWithoutAuth.rows.map(row => ({ id: row.id, username: row.username, phone: row.phone ? `${String(row.phone).slice(0, 3)}****${String(row.phone).slice(-4)}` : null, })), }); } else { pass('db.platform_admin_auth_binding', 'Active platform admin users are bound to Supabase Auth users'); } const platformAdminsWithoutPermissions = await pool.query(` select id, username, phone from public.platform_users where primary_role = 'platform_admin' and status = 'active' and (platform_permissions is null or platform_permissions = '{}'::jsonb) order by created_at asc limit 20 `); if (platformAdminsWithoutPermissions.rowCount > 0) { block('db.platform_admin_permissions', 'Platform admin users must have explicit platform_permissions', { count: platformAdminsWithoutPermissions.rowCount, samples: platformAdminsWithoutPermissions.rows.map(row => ({ id: row.id, username: row.username, phone: row.phone ? `${String(row.phone).slice(0, 3)}****${String(row.phone).slice(-4)}` : null, })), }); } else { pass('db.platform_admin_permissions', 'Platform admin users have explicit permission maps'); } const disabledPlatformAdminsWithActiveSessions = await pool.query(` select u.id, u.username, u.phone, count(s.id)::int as active_session_count from public.platform_users u join app_private.auth_sessions s on s.user_id = u.id where u.primary_role = 'platform_admin' and u.status = 'disabled' and s.revoked_at is null and s.expires_at > now() group by u.id, u.username, u.phone order by active_session_count desc limit 20 `); if (disabledPlatformAdminsWithActiveSessions.rowCount > 0) { block('db.platform_admin_disabled_sessions', 'Disabled platform admin users must not have active legacy sessions', { count: disabledPlatformAdminsWithActiveSessions.rowCount, samples: disabledPlatformAdminsWithActiveSessions.rows.map(row => ({ id: row.id, username: row.username, phone: row.phone ? `${String(row.phone).slice(0, 3)}****${String(row.phone).slice(-4)}` : null, activeSessionCount: row.active_session_count, })), }); } else { pass('db.platform_admin_disabled_sessions', 'Disabled platform admin users have no active legacy sessions'); } const missingAuthSecretRows = await pool.query(` select p.tenant_id, p.provider from public.tenant_auth_providers p left join app_private.tenant_secrets s on s.tenant_id = p.tenant_id and s.secret_scope = case when lower(replace(p.provider, '_', '-')) in ('aliyun', 'aliyun-sms', 'aliyun-pnvs', 'aliyun-pnvs-sms', 'aliyun-sms-auth', 'tencent', 'tencent-sms') then 'sms' else 'oauth' end and s.secret_key = coalesce(nullif(split_part(p.config_public->>'secretRef', ':', 3), ''), p.provider) where p.status in ('active', 'testing') and p.provider not in ('mock') and s.id is null `); if (missingAuthSecretRows.rowCount > 0) { block('db.auth_provider_secrets', 'Some active auth providers are missing tenant secret rows', { count: missingAuthSecretRows.rowCount }); } else { pass('db.auth_provider_secrets', 'Active auth providers have private secret rows'); } const missingPaymentSecretRows = await pool.query(` select p.tenant_id, p.provider from public.tenant_payment_accounts p left join app_private.tenant_secrets s on s.tenant_id = p.tenant_id and s.secret_scope = 'payment' and s.secret_key = coalesce(nullif(split_part(p.config_public->>'secretRef', ':', 3), ''), p.provider) where p.status = 'active' and p.provider not in ('manual') and s.id is null `); if (missingPaymentSecretRows.rowCount > 0) { block('db.payment_provider_secrets', 'Some active payment accounts are missing tenant secret rows', { count: missingPaymentSecretRows.rowCount }); } else { pass('db.payment_provider_secrets', 'Active payment accounts have private secret rows'); } const unsafePlatformAuditNotificationRows = await pool.query(` select id, channel_code, provider, webhook_url from public.platform_audit_notification_channels where enabled = true and ( webhook_url !~* '^https://' or webhook_url ~* '^https?://(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)' ) `); if (unsafePlatformAuditNotificationRows.rowCount > 0) { block('db.platform_audit_notification_webhooks', 'Enabled platform audit notification webhooks must use production HTTPS URLs', { count: unsafePlatformAuditNotificationRows.rowCount, samples: unsafePlatformAuditNotificationRows.rows.slice(0, 5).map(row => ({ id: row.id, channelCode: row.channel_code, provider: row.provider, })), }); } else { pass('db.platform_audit_notification_webhooks', 'Enabled platform audit notification webhooks use production HTTPS URLs'); } const missingPlatformAuditNotificationSecretRows = await pool.query(` select c.id, c.channel_code, c.provider, c.secret_ref from public.platform_audit_notification_channels c left join app_private.platform_secrets s on s.secret_scope = split_part(c.secret_ref, ':', 2) and s.secret_key = split_part(c.secret_ref, ':', 3) where c.enabled = true and c.provider in ('dingtalk', 'feishu') and (c.secret_ref is null or c.secret_ref !~ '^app_private\\.platform_secrets:' or s.id is null) `); if (missingPlatformAuditNotificationSecretRows.rowCount > 0) { block('db.platform_audit_notification_secrets', 'Signed platform audit notification channels require app_private.platform_secrets rows', { count: missingPlatformAuditNotificationSecretRows.rowCount, samples: missingPlatformAuditNotificationSecretRows.rows.slice(0, 5).map(row => ({ id: row.id, channelCode: row.channel_code, provider: row.provider, })), }); } else { pass('db.platform_audit_notification_secrets', 'Signed platform audit notification channels have private secret rows'); } const unsafePlatformDunningNotificationRows = await pool.query(` select id, channel_code, provider, webhook_url from public.platform_dunning_notification_channels where enabled = true and ( webhook_url !~* '^https://' or webhook_url ~* '^https?://(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)' ) `); if (unsafePlatformDunningNotificationRows.rowCount > 0) { block('db.platform_dunning_notification_webhooks', 'Enabled platform dunning notification webhooks must use production HTTPS URLs', { count: unsafePlatformDunningNotificationRows.rowCount, samples: unsafePlatformDunningNotificationRows.rows.slice(0, 5).map(row => ({ id: row.id, channelCode: row.channel_code, provider: row.provider, })), }); } else { pass('db.platform_dunning_notification_webhooks', 'Enabled platform dunning notification webhooks use production HTTPS URLs'); } const missingPlatformDunningNotificationSecretRows = await pool.query(` select c.id, c.channel_code, c.provider, c.secret_ref from public.platform_dunning_notification_channels c left join app_private.platform_secrets s on s.secret_scope = split_part(c.secret_ref, ':', 2) and s.secret_key = split_part(c.secret_ref, ':', 3) where c.enabled = true and c.provider in ('dingtalk', 'feishu') and (c.secret_ref is null or c.secret_ref !~ '^app_private\\.platform_secrets:' or s.id is null) `); if (missingPlatformDunningNotificationSecretRows.rowCount > 0) { block('db.platform_dunning_notification_secrets', 'Signed platform dunning notification channels require app_private.platform_secrets rows', { count: missingPlatformDunningNotificationSecretRows.rowCount, samples: missingPlatformDunningNotificationSecretRows.rows.slice(0, 5).map(row => ({ id: row.id, channelCode: row.channel_code, provider: row.provider, })), }); } else { pass('db.platform_dunning_notification_secrets', 'Signed platform dunning notification channels have private secret rows'); } const unverifiedDomainRows = await pool.query(` select count(*)::int as count from public.tenant_domains where status not in ('active', 'verified') `); const unverifiedDomains = Number(unverifiedDomainRows.rows[0]?.count || 0); if (unverifiedDomains > 0) warn('db.tenant_domains', 'Some tenant domains are not active/verified', { count: unverifiedDomains }); else pass('db.tenant_domains', 'Tenant domains are active/verified or not configured'); const clientTablePrivilegeRows = await pool.query(` select r.rolname as role_name, n.nspname as table_schema, c.relname as table_name, privilege.privilege_type from pg_class c join pg_namespace n on n.oid = c.relnamespace cross join (values ('anon'), ('authenticated')) as requested_roles(role_name) join pg_roles r on r.rolname = requested_roles.role_name cross join lateral ( values ('SELECT', has_table_privilege(r.oid, c.oid, 'SELECT')), ('INSERT', has_table_privilege(r.oid, c.oid, 'INSERT')), ('UPDATE', has_table_privilege(r.oid, c.oid, 'UPDATE')), ('DELETE', has_table_privilege(r.oid, c.oid, 'DELETE')), ('TRUNCATE', has_table_privilege(r.oid, c.oid, 'TRUNCATE')), ('REFERENCES', has_table_privilege(r.oid, c.oid, 'REFERENCES')), ('TRIGGER', has_table_privilege(r.oid, c.oid, 'TRIGGER')) ) as privilege(privilege_type, allowed) where n.nspname = 'public' and c.relkind in ('r', 'p', 'v', 'm', 'f') and privilege.allowed order by r.rolname, c.relname, privilege.privilege_type `); if (clientTablePrivilegeRows.rowCount > 0) { block('db.data_api.public_table_acl', 'anon/authenticated must not have direct privileges on public business tables or views', { count: clientTablePrivilegeRows.rowCount, samples: clientTablePrivilegeRows.rows.slice(0, 20).map(row => ({ role: row.role_name, object: `${row.table_schema}.${row.table_name}`, privilege: row.privilege_type, })), }); } else { pass('db.data_api.public_table_acl', 'anon/authenticated have no direct privileges on public business tables or views'); } const clientSequencePrivilegeRows = await pool.query(` select r.rolname as role_name, n.nspname as sequence_schema, c.relname as sequence_name, privilege.privilege_type from pg_class c join pg_namespace n on n.oid = c.relnamespace cross join (values ('anon'), ('authenticated')) as requested_roles(role_name) join pg_roles r on r.rolname = requested_roles.role_name cross join lateral ( values ('USAGE', has_sequence_privilege(r.oid, c.oid, 'USAGE')), ('SELECT', has_sequence_privilege(r.oid, c.oid, 'SELECT')), ('UPDATE', has_sequence_privilege(r.oid, c.oid, 'UPDATE')) ) as privilege(privilege_type, allowed) where n.nspname = 'public' and c.relkind = 'S' and privilege.allowed order by r.rolname, c.relname, privilege.privilege_type `); if (clientSequencePrivilegeRows.rowCount > 0) { block('db.data_api.public_sequence_acl', 'anon/authenticated must not have direct privileges on public sequences', { count: clientSequencePrivilegeRows.rowCount, samples: clientSequencePrivilegeRows.rows.slice(0, 20).map(row => ({ role: row.role_name, object: `${row.sequence_schema}.${row.sequence_name}`, privilege: row.privilege_type, })), }); } else { pass('db.data_api.public_sequence_acl', 'anon/authenticated have no direct privileges on public sequences'); } const clientFunctionPrivilegeRows = await pool.query(` select r.rolname as role_name, n.nspname as function_schema, p.proname as function_name, pg_get_function_identity_arguments(p.oid) as arguments from pg_proc p join pg_namespace n on n.oid = p.pronamespace cross join (values ('anon'), ('authenticated')) as requested_roles(role_name) join pg_roles r on r.rolname = requested_roles.role_name where n.nspname = 'public' and has_function_privilege(r.oid, p.oid, 'EXECUTE') order by r.rolname, p.proname, arguments `); if (clientFunctionPrivilegeRows.rowCount > 0) { block('db.data_api.public_function_acl', 'anon/authenticated must not execute public RPC functions without an explicit reviewed exception', { count: clientFunctionPrivilegeRows.rowCount, samples: clientFunctionPrivilegeRows.rows.slice(0, 20).map(row => ({ role: row.role_name, function: `${row.function_schema}.${row.function_name}(${row.arguments})`, })), }); } else { pass('db.data_api.public_function_acl', 'anon/authenticated cannot execute public RPC functions'); } const unsafeDefaultAclRows = await pool.query(` with public_object_owners as ( select distinct c.relowner as owner_oid from pg_class c join pg_namespace n on n.oid = c.relnamespace where n.nspname = 'public' and c.relkind in ('r', 'p', 'S', 'v', 'm', 'f') union select distinct p.proowner as owner_oid from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' ), object_types as ( select 'r'::"char" as object_type, 'TABLES'::text as object_label union all select 'S'::"char", 'SEQUENCES'::text union all select 'f'::"char", 'FUNCTIONS'::text ), default_sources as ( select owners.owner_oid, object_types.object_type, object_types.object_label, 'GLOBAL'::text as default_scope, coalesce( ( select d.defaclacl from pg_default_acl d where d.defaclrole = owners.owner_oid and d.defaclnamespace = 0 and d.defaclobjtype = object_types.object_type ), acldefault(object_types.object_type, owners.owner_oid) ) as acl from public_object_owners owners cross join object_types union all select owners.owner_oid, object_types.object_type, object_types.object_label, 'PUBLIC_SCHEMA'::text as default_scope, d.defaclacl as acl from public_object_owners owners cross join object_types join pg_default_acl d on d.defaclrole = owners.owner_oid and d.defaclobjtype = object_types.object_type join pg_namespace n on n.oid = d.defaclnamespace and n.nspname = 'public' ) select owner.rolname as owner_name, defaults.object_label as object_type, defaults.default_scope, coalesce(grantee.rolname, 'PUBLIC') as grantee_name, acl.privilege_type from default_sources defaults join pg_roles owner on owner.oid = defaults.owner_oid cross join lateral aclexplode(defaults.acl) acl left join pg_roles grantee on grantee.oid = acl.grantee where acl.grantee = 0 or grantee.rolname in ('anon', 'authenticated') order by owner.rolname, defaults.object_label, grantee_name, acl.privilege_type `); if (unsafeDefaultAclRows.rowCount > 0) { block('db.data_api.public_default_acl', 'public schema default privileges must not expose future tables, sequences or functions to client roles', { count: unsafeDefaultAclRows.rowCount, samples: unsafeDefaultAclRows.rows.slice(0, 20).map(row => ({ owner: row.owner_name, objectType: row.object_type, defaultScope: row.default_scope, grantee: row.grantee_name || 'PUBLIC', privilege: row.privilege_type, })), }); } else { pass('db.data_api.public_default_acl', 'public schema default privileges are deny-by-default for client roles'); } const platformUserWritePolicies = await pool.query(` select p.polname as policy_name, p.polcmd as policy_command, array( select coalesce(r.rolname, 'PUBLIC') from unnest(p.polroles) policy_role(role_oid) left join pg_roles r on r.oid = policy_role.role_oid ) as policy_roles from pg_policy p where p.polrelid = 'public.platform_users'::regclass and p.polcmd in ('*', 'a', 'w', 'd') and ( p.polroles = '{0}'::oid[] or exists ( select 1 from unnest(p.polroles) policy_role(role_oid) join pg_roles r on r.oid = policy_role.role_oid where r.rolname in ('anon', 'authenticated') ) ) order by p.polname `); if (platformUserWritePolicies.rowCount > 0) { block('db.data_api.platform_users_write_policy', 'platform_users must not expose INSERT/UPDATE/DELETE/FOR ALL policies to client roles', { count: platformUserWritePolicies.rowCount, samples: platformUserWritePolicies.rows, }); } else { pass('db.data_api.platform_users_write_policy', 'platform_users exposes no client write policy'); } const platformAdminAuthorityRows = await pool.query(` select p.oid, p.prosecdef as security_definer, p.proconfig, pg_get_functiondef(p.oid) as definition from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'app' and p.proname = 'is_platform_admin' and pg_get_function_identity_arguments(p.oid) = '' `); const platformAdminAuthority = platformAdminAuthorityRows.rows[0]; const platformAdminDefinition = String(platformAdminAuthority?.definition || '').toLowerCase(); const platformAdminSearchPath = Array.isArray(platformAdminAuthority?.proconfig) ? platformAdminAuthority.proconfig.map(value => String(value).toLowerCase()) : []; const platformAdminAuthoritySafe = platformAdminAuthorityRows.rowCount === 1 && platformAdminAuthority.security_definer === true && platformAdminSearchPath.includes('search_path=""') && platformAdminDefinition.includes('from public.platform_users') && platformAdminDefinition.includes("status = 'active'") && !/current_role\(\)\s+in\s*\([^)]*platform_admin/.test(platformAdminDefinition); if (!platformAdminAuthoritySafe) { block('db.rls.platform_admin_authority', 'app.is_platform_admin() must resolve an active database identity and must not trust a platform_admin JWT claim', { functionCount: platformAdminAuthorityRows.rowCount, securityDefiner: platformAdminAuthority?.security_definer || false, searchPath: platformAdminSearchPath, }); } else { pass('db.rls.platform_admin_authority', 'RLS platform authority is backed by an active database identity'); } const tenantTablesWithoutRls = await pool.query(` select c.table_schema, c.table_name from information_schema.columns c join pg_class cls on cls.relname = c.table_name join pg_namespace ns on ns.oid = cls.relnamespace and ns.nspname = c.table_schema where c.column_name = 'tenant_id' and c.table_schema in ('public', 'app_private') and cls.relkind in ('r', 'p') and not cls.relrowsecurity order by c.table_schema, c.table_name `); if (tenantTablesWithoutRls.rowCount > 0) { block('db.rls.tenant_tables_enabled', 'Every tenant-scoped table must have row level security enabled', { count: tenantTablesWithoutRls.rowCount, samples: tenantTablesWithoutRls.rows.slice(0, 10).map(formatTableName), }); } else { pass('db.rls.tenant_tables_enabled', 'All tenant-scoped tables have RLS enabled'); } const tenantTablesWithoutPolicies = await pool.query(` select c.table_schema, c.table_name from information_schema.columns c join pg_class cls on cls.relname = c.table_name join pg_namespace ns on ns.oid = cls.relnamespace and ns.nspname = c.table_schema where c.column_name = 'tenant_id' and c.table_schema in ('public', 'app_private') and cls.relkind in ('r', 'p') and not exists ( select 1 from pg_policy p where p.polrelid = cls.oid ) order by c.table_schema, c.table_name `); if (tenantTablesWithoutPolicies.rowCount > 0) { block('db.rls.tenant_tables_policy', 'Every tenant-scoped table must have at least one RLS policy', { count: tenantTablesWithoutPolicies.rowCount, samples: tenantTablesWithoutPolicies.rows.slice(0, 10).map(formatTableName), }); } else { pass('db.rls.tenant_tables_policy', 'All tenant-scoped tables have at least one RLS policy'); } const publicTenantTablesWithoutTenantContextPolicy = await pool.query(` select c.table_schema, c.table_name from information_schema.columns c join pg_class cls on cls.relname = c.table_name join pg_namespace ns on ns.oid = cls.relnamespace and ns.nspname = c.table_schema where c.column_name = 'tenant_id' and c.table_schema = 'public' and cls.relkind in ('r', 'p') and not exists ( select 1 from pg_policy p where p.polrelid = cls.oid and ( lower(pg_get_expr(p.polqual, p.polrelid)) like '%app.current_tenant_id()%' or lower(pg_get_expr(p.polwithcheck, p.polrelid)) like '%app.current_tenant_id()%' ) ) and not ( exists ( select 1 from pg_policy platform_policy where platform_policy.polrelid = cls.oid and ( lower(coalesce(pg_get_expr(platform_policy.polqual, platform_policy.polrelid), '')) like '%app.is_platform_admin()%' or lower(coalesce(pg_get_expr(platform_policy.polwithcheck, platform_policy.polrelid), '')) like '%app.is_platform_admin()%' ) ) and not exists ( select 1 from pg_policy non_platform_policy where non_platform_policy.polrelid = cls.oid and ( ( non_platform_policy.polqual is not null and lower(pg_get_expr(non_platform_policy.polqual, non_platform_policy.polrelid)) not like '%app.is_platform_admin()%' ) or ( non_platform_policy.polwithcheck is not null and lower(pg_get_expr(non_platform_policy.polwithcheck, non_platform_policy.polrelid)) not like '%app.is_platform_admin()%' ) ) ) ) order by c.table_schema, c.table_name `); if (publicTenantTablesWithoutTenantContextPolicy.rowCount > 0) { block( 'db.rls.public_tenant_context', 'Every public tenant-scoped table must include app.current_tenant_id() in an RLS policy', { count: publicTenantTablesWithoutTenantContextPolicy.rowCount, samples: publicTenantTablesWithoutTenantContextPolicy.rows.slice(0, 10).map(formatTableName), }, ); } else { pass('db.rls.public_tenant_context', 'Public tenant-scoped tables include tenant context in RLS policies'); } const tenantForeignKeySchema = summarizeTenantForeignKeySchema( await loadTenantForeignKeyRelations(pool), ); if (!tenantForeignKeySchema.schemaMatches) { block( 'db.tenant_foreign_keys.schema', 'Tenant-scoped single-key foreign key schema changed or contains unvalidated constraints; review the relation and update the audited contract', tenantForeignKeySchema, ); } else { pass( 'db.tenant_foreign_keys.schema', 'Tenant-scoped single-key foreign key schema matches the reviewed contract', tenantForeignKeySchema, ); } } finally { await pool.end(); } } function printSummary() { const summary = checks.reduce( (acc, item) => { acc[item.status] += 1; return acc; }, { blocker: 0, warn: 0, pass: 0 }, ); if (jsonOutput) { console.log(JSON.stringify({ summary, checks }, null, 2)); return; } console.log('Production readiness check'); console.log(`Env file: ${fs.existsSync(envFile) ? envFile : '(not found, using process env)'}`); console.log(`Host: ${os.hostname()}`); console.log(`Summary: ${summary.blocker} blocker(s), ${summary.warn} warning(s), ${summary.pass} pass(es)`); for (const item of checks) { const marker = item.status === 'blocker' ? 'BLOCK' : item.status === 'warn' ? 'WARN ' : 'PASS '; console.log(`[${marker}] ${item.id}: ${item.message}`); } } async function main() { loadEnvFile(envFile); validateEnv(); if (providerConfigFixture) { const fixturePath = path.resolve(process.cwd(), providerConfigFixture); const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); const rows = Array.isArray(fixture) ? fixture : Array.isArray(fixture.rows) ? fixture.rows : []; validateProviderConfigRows(rows); } if (tenantConfigFixture) { const fixturePath = path.resolve(process.cwd(), tenantConfigFixture); const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); const rows = Array.isArray(fixture) ? fixture : Array.isArray(fixture.rows) ? fixture.rows : []; validateTenantConfigRows(rows); } if (environmentSafetyFixture) { const fixturePath = path.resolve(process.cwd(), environmentSafetyFixture); const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); const row = fixture && typeof fixture === 'object' && !Array.isArray(fixture) && 'row' in fixture ? fixture.row : fixture; validateEnvironmentSafetyMarker(row || null, 'fixture'); } if (migrationHistoryFixture) { const fixturePath = path.resolve(process.cwd(), migrationHistoryFixture); const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); const row = fixture && typeof fixture === 'object' && !Array.isArray(fixture) && 'row' in fixture ? fixture.row : fixture; validateMigrationHistory(row || null, 'fixture'); } await validateDatabase(); printSummary(); if (checks.some(item => item.status === 'blocker')) { process.exitCode = 1; } } main().catch(error => { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });