/** * Capability Secret Access Helpers * Functions for checking if fields are secrets and retrieving secret values */ import type { Database } from 'bun:sqlite'; import type { ModuleManifest } from '../manifest/schema'; import { decryptSecret } from '../secrets/encryption'; import { getOrCreateMasterKey } from '../secrets/master-key'; import { checkAllowlist, getProviderManifest } from './validation'; /** * Check if a capability field is marked as secret in the provider manifest * * Execution function (Rule 10.1) - performs database query * * @param capabilityName - Name of the capability (e.g., dns_registrar) * @param fieldPath - Path to the field (e.g., tsig or server.ip.primary) * @param db - Database connection * @returns True if field is a secret */ export function isCapabilityFieldSecret( capabilityName: string, fieldPath: string, db: Database, ): boolean { const providerManifest = getProviderManifest(capabilityName, db); if (!providerManifest) { return false; } const capabilityDef = providerManifest.provides?.capabilities?.find( (cap) => cap.name === capabilityName, ); if (!capabilityDef?.secrets) { return false; } // Check if fieldPath matches any secret name return capabilityDef.secrets.some((secret) => secret.name === fieldPath); } /** * Check if consumer module can access a capability secret * * Execution function (Rule 10.1) - performs database queries * * @param consumerModuleId - Module ID requesting access * @param capabilityName - Name of the capability * @param secretName - Name of the secret * @param db - Database connection * @returns True if access is allowed */ export function checkCapabilitySecretAccess( consumerModuleId: string, capabilityName: string, secretName: string, db: Database, ): boolean { // Get provider manifest const providerManifest = getProviderManifest(capabilityName, db); if (!providerManifest) { return false; } // Get consumer manifest const consumerResult = db .prepare('SELECT manifest_data FROM modules WHERE id = ?') .get(consumerModuleId) as { manifest_data: string } | undefined; if (!consumerResult) { return false; } let consumerManifest: ModuleManifest; try { consumerManifest = JSON.parse(consumerResult.manifest_data) as ModuleManifest; } catch (error) { throw new Error( `Failed to parse manifest for ${consumerModuleId}: ${error instanceof Error ? error.message : 'Invalid JSON'}`, ); } // Get list of capabilities provided by consumer const consumerCapabilities = (consumerManifest.provides?.capabilities || []).map( (cap) => cap.name, ); // Find secret definition in provider manifest const capabilityDef = providerManifest.provides?.capabilities?.find( (cap) => cap.name === capabilityName, ); if (!capabilityDef?.secrets) { return false; } const secret = capabilityDef.secrets.find((s) => s.name === secretName); if (!secret) { return false; } // Check if secret has readable_by restriction if (!secret.readable_by || secret.readable_by.length === 0) { // No restriction - accessible to all return true; } // Check if consumer provides any capability in the allowlist return checkAllowlist(consumerCapabilities, secret.readable_by); } /** * Get and decrypt capability secret * * Execution function (Rule 10.1) - performs database query and decryption * * @param capabilityName - Name of the capability * @param secretName - Name of the secret * @param db - Database connection * @returns Decrypted secret value * @throws Error if secret not found or decryption fails */ export async function getCapabilitySecret( capabilityName: string, secretName: string, db: Database, ): Promise { // Get provider manifest to check for secret_ref const providerManifest = getProviderManifest(capabilityName, db); if (!providerManifest) { throw new Error(`Provider manifest not found for capability '${capabilityName}'`); } // Find capability and secret definition const capabilityDef = providerManifest.provides?.capabilities?.find( (cap) => cap.name === capabilityName, ); if (!capabilityDef?.secrets) { throw new Error(`No secrets defined in capability '${capabilityName}'`); } const secretDef = capabilityDef.secrets.find((s) => s.name === secretName); if (!secretDef) { throw new Error(`Secret '${secretName}' not defined in capability '${capabilityName}'`); } // Check if secret uses secret_ref (reference to provider module's own secret) if (secretDef.secret_ref) { // Parse secret_ref (format: $secret:secret_name) const match = secretDef.secret_ref.match(/^\$secret:(.+)$/); if (!match) { throw new Error( `Invalid secret_ref format in capability '${capabilityName}': ${secretDef.secret_ref}`, ); } const moduleSecretName = match[1]; // Get provider module ID const moduleResult = db .prepare( `SELECT p.id FROM modules p JOIN capabilities c ON p.id = c.module_id WHERE c.capability_name = ? LIMIT 1`, ) .get(capabilityName) as { id: string } | undefined; if (!moduleResult) { throw new Error(`Provider module not found for capability '${capabilityName}'`); } // Get module secret const secretResult = db .prepare('SELECT encrypted_value, iv, auth_tag FROM secrets WHERE module_id = ? AND name = ?') .get(moduleResult.id, moduleSecretName) as | { encrypted_value: string; iv: string; auth_tag: string } | undefined; if (!secretResult) { throw new Error( `Module secret '${moduleSecretName}' not found for capability '${capabilityName}' (referenced by secret_ref)`, ); } // Decrypt and return module secret const masterKey = await getOrCreateMasterKey(); return decryptSecret( { encryptedValue: secretResult.encrypted_value, iv: secretResult.iv, authTag: secretResult.auth_tag, }, masterKey, ); } // No secret_ref - read directly from capability_secrets table const capabilityResult = db .prepare('SELECT id FROM capabilities WHERE capability_name = ? LIMIT 1') .get(capabilityName) as { id: number } | undefined; if (!capabilityResult) { throw new Error(`Capability '${capabilityName}' not found`); } const secretResult = db .prepare( 'SELECT encrypted_value, iv, auth_tag FROM capability_secrets WHERE capability_id = ? AND name = ?', ) .get(capabilityResult.id, secretName) as | { encrypted_value: string; iv: string; auth_tag: string } | undefined; if (!secretResult) { throw new Error(`Secret '${secretName}' not found in capability '${capabilityName}'`); } if (!secretResult.encrypted_value || !secretResult.iv || !secretResult.auth_tag) { throw new Error( `Secret '${secretName}' in capability '${capabilityName}' has not been set. Run module configuration to set this secret.`, ); } // Decrypt secret const masterKey = await getOrCreateMasterKey(); return decryptSecret( { encryptedValue: secretResult.encrypted_value, iv: secretResult.iv, authTag: secretResult.auth_tag, }, masterKey, ); }