{"version":3,"file":"credential-vault.mjs","names":[],"sources":["../../../src/services/credential-vault.ts"],"sourcesContent":["/**\n * Credential Vault — centralized secret access with leak scanning.\n *\n * Inspired by IronClaw's credential boundary injection pattern.\n * Instead of tools reading process.env directly, they request secrets\n * through this vault. The vault:\n * 1. Provides secrets on demand (single point of access)\n * 2. Tracks which tools accessed which secrets (audit log)\n * 3. Scans outbound strings for leaked credentials before they reach the LLM\n *\n * This does NOT encrypt secrets at rest (they're still in env vars / Fly secrets).\n * What it does is:\n * - Prevent accidental secret exposure in tool output\n * - Create an audit trail of secret access\n * - Provide a single place to rotate/revoke secrets\n * - Scan LLM-bound text for credential leaks\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\nexport interface SecretAccess {\n  key: string;\n  tool: string;\n  timestamp: number;\n}\n\nexport interface LeakScanResult {\n  clean: boolean;\n  leaks: Array<{\n    type: string;\n    pattern: string;\n    position: number;\n  }>;\n  redactedText: string;\n}\n\n// ─── Secret Registry ─────────────────────────────────────────────────────\n// Maps logical secret names to env var names.\n// Tools should use logical names, never raw env vars.\n\nconst SECRET_REGISTRY: Record<string, {\n  envVar: string;\n  description: string;\n  sensitive: 'critical' | 'high' | 'medium';\n}> = {\n  // ── Critical: private keys and auth tokens that control funds ─────\n  'wallet.privateKey': {\n    envVar: 'CLAWNCHER_PRIVATE_KEY',\n    description: 'Wallet private key for auto-signing',\n    sensitive: 'critical',\n  },\n  'wallet.keychainMnemonic': {\n    envVar: 'CLAWNCHER_WALLET_PASSWORD',\n    description: 'Password for Keychain-encrypted wallet mnemonic',\n    sensitive: 'critical',\n  },\n  'walletconnect.projectId': {\n    envVar: 'WALLETCONNECT_PROJECT_ID',\n    description: 'WalletConnect cloud project ID',\n    sensitive: 'high',\n  },\n  'bankr.apiKey': {\n    envVar: 'BANKR_API_KEY',\n    description: 'Bankr Agent API key',\n    sensitive: 'high',\n  },\n\n  // ── High: API keys for financial services ─────────────────────────\n  'dex.0x.apiKey': {\n    envVar: 'ZEROX_API_KEY',\n    description: '0x DEX aggregator API key',\n    sensitive: 'high',\n  },\n  'dex.1inch.apiKey': {\n    envVar: 'ONEINCH_API_KEY',\n    description: '1inch DEX aggregator API key',\n    sensitive: 'high',\n  },\n  'bridge.lifi.apiKey': {\n    envVar: 'LIFI_API_KEY',\n    description: 'LI.FI bridge aggregator API key',\n    sensitive: 'high',\n  },\n\n  // ── Medium: read-only API keys ────────────────────────────────────\n  'rpc.alchemy.apiKey': {\n    envVar: 'ALCHEMY_API_KEY',\n    description: 'Alchemy RPC provider API key',\n    sensitive: 'medium',\n  },\n  'price.coingecko.apiKey': {\n    envVar: 'COINGECKO_API_KEY',\n    description: 'CoinGecko price feed API key',\n    sensitive: 'medium',\n  },\n  'price.cmc.apiKey': {\n    envVar: 'CMC_API_KEY',\n    description: 'CoinMarketCap price feed API key',\n    sensitive: 'medium',\n  },\n  'price.birdeye.apiKey': {\n    envVar: 'BIRDEYE_API_KEY',\n    description: 'Birdeye price feed API key',\n    sensitive: 'medium',\n  },\n  'explorer.basescan.apiKey': {\n    envVar: 'BASESCAN_API_KEY',\n    description: 'Basescan block explorer API key',\n    sensitive: 'medium',\n  },\n  'explorer.etherscan.apiKey': {\n    envVar: 'ETHERSCAN_API_KEY',\n    description: 'Etherscan block explorer API key',\n    sensitive: 'medium',\n  },\n  'intel.herd.accessToken': {\n    envVar: 'HERD_ACCESS_TOKEN',\n    description: 'Herd Intelligence access token',\n    sensitive: 'medium',\n  },\n  'social.x.apiKey': {\n    envVar: 'X_API_KEY',\n    description: 'X/Twitter API key',\n    sensitive: 'high',\n  },\n  'social.x.apiSecret': {\n    envVar: 'X_API_SECRET',\n    description: 'X/Twitter API secret',\n    sensitive: 'high',\n  },\n  'social.x.accessToken': {\n    envVar: 'X_ACCESS_TOKEN',\n    description: 'X/Twitter access token',\n    sensitive: 'high',\n  },\n  'social.x.accessTokenSecret': {\n    envVar: 'X_ACCESS_TOKEN_SECRET',\n    description: 'X/Twitter access token secret',\n    sensitive: 'high',\n  },\n  'llm.anthropic.apiKey': {\n    envVar: 'ANTHROPIC_API_KEY',\n    description: 'Anthropic LLM API key',\n    sensitive: 'high',\n  },\n  'llm.bankr.key': {\n    envVar: 'BANKR_LLM_KEY',\n    description: 'Bankr LLM gateway key',\n    sensitive: 'high',\n  },\n  'llm.openrouter.apiKey': {\n    envVar: 'OPENROUTER_API_KEY',\n    description: 'OpenRouter LLM API key',\n    sensitive: 'high',\n  },\n  'llm.openai.apiKey': {\n    envVar: 'OPENAI_API_KEY',\n    description: 'OpenAI LLM API key',\n    sensitive: 'high',\n  },\n  'deploy.fly.apiToken': {\n    envVar: 'FLY_API_TOKEN',\n    description: 'Fly.io deployment API token',\n    sensitive: 'high',\n  },\n  'bot.molten.apiKey': {\n    envVar: 'MOLTEN_API_KEY',\n    description: 'Molten agent matching API key',\n    sensitive: 'medium',\n  },\n  'bot.wayfinder.apiKey': {\n    envVar: 'WAYFINDER_API_KEY',\n    description: 'Wayfinder routing API key',\n    sensitive: 'medium',\n  },\n  'clawnch.apiKey': {\n    envVar: 'CLAWNCH_API_KEY',\n    description: 'Clawnch platform API key',\n    sensitive: 'high',\n  },\n  'clawnch.launcherApiKey': {\n    envVar: 'CLAWNCHER_API_KEY',\n    description: 'Clawnch token launcher API key',\n    sensitive: 'high',\n  },\n  'bot.hummingbot.username': {\n    envVar: 'HUMMINGBOT_USERNAME',\n    description: 'Hummingbot gateway username',\n    sensitive: 'high',\n  },\n  'bot.hummingbot.password': {\n    envVar: 'HUMMINGBOT_PASSWORD',\n    description: 'Hummingbot gateway password',\n    sensitive: 'critical',\n  },\n  'bot.telegram.botToken': {\n    envVar: 'TELEGRAM_BOT_TOKEN',\n    description: 'Telegram bot API token',\n    sensitive: 'high',\n  },\n  'nft.reservoir.apiKey': {\n    envVar: 'RESERVOIR_API_KEY',\n    description: 'Reservoir NFT API key (free tier: 4 req/sec)',\n    sensitive: 'medium',\n  },\n  'social.neynar.apiKey': {\n    envVar: 'NEYNAR_API_KEY',\n    description: 'Neynar Farcaster API key',\n    sensitive: 'high',\n  },\n  'social.neynar.signerUuid': {\n    envVar: 'NEYNAR_SIGNER_UUID',\n    description: 'Neynar managed signer UUID (write operations)',\n    sensitive: 'high',\n  },\n};\n\n// ─── Credential Vault ────────────────────────────────────────────────────\n\nclass CredentialVault {\n  private accessLog: SecretAccess[] = [];\n  private readonly MAX_LOG_SIZE = 1000;\n\n  /**\n   * Get a secret value by its logical name.\n   * Returns null if not configured.\n   */\n  getSecret(name: string, tool: string): string | null {\n    const entry = SECRET_REGISTRY[name];\n    if (!entry) return null;\n\n    const value = process.env[entry.envVar] ?? null;\n\n    // Log access (even if value is null — tracks attempts)\n    this.logAccess(name, tool);\n\n    return value;\n  }\n\n  /**\n   * Check if a secret is configured (without revealing its value).\n   */\n  hasSecret(name: string): boolean {\n    const entry = SECRET_REGISTRY[name];\n    if (!entry) return false;\n    return !!process.env[entry.envVar];\n  }\n\n  /**\n   * Get the raw env var for a logical secret name.\n   * Use this only when you need to pass the env var name (not value) to something.\n   */\n  getEnvVarName(name: string): string | null {\n    return SECRET_REGISTRY[name]?.envVar ?? null;\n  }\n\n  /**\n   * Scan text for credential leaks.\n   * Returns a result with any detected leaks and a redacted version of the text.\n   */\n  scanForLeaks(text: string): LeakScanResult {\n    const leaks: LeakScanResult['leaks'] = [];\n    let redacted = text;\n\n    // 1. Check for actual secret values appearing in text\n    for (const [name, entry] of Object.entries(SECRET_REGISTRY)) {\n      const value = process.env[entry.envVar];\n      if (!value || value.length < 8) continue; // Skip short/missing values\n\n      const idx = text.indexOf(value);\n      if (idx !== -1) {\n        leaks.push({\n          type: `secret:${name}`,\n          pattern: `${entry.envVar} value`,\n          position: idx,\n        });\n        // Redact the value\n        redacted = redacted.replaceAll(value, `[REDACTED:${entry.envVar}]`);\n      }\n    }\n\n    // 2. Pattern-based detection (catches secrets not in our registry)\n    //\n    // IMPORTANT: The private_key pattern uses a \"context-positive\" approach:\n    // In a crypto application, 64-hex-char strings are everywhere (tx hashes,\n    // block hashes, ABI-encoded data, Merkle proofs, event topics, etc.).\n    // Instead of matching all 64-hex strings and trying to exclude safe ones,\n    // we only flag them when the surrounding context indicates a secret.\n    const LEAK_PATTERNS: Array<{ type: string; regex: RegExp }> = [\n      // Private keys: only match 64-hex strings near danger-context words.\n      // The regex itself matches any 64-hex string; filtering is below.\n      { type: 'private_key', regex: /\\b(0x)?[0-9a-fA-F]{64}\\b/g },\n      // WalletConnect secrets\n      { type: 'wc_secret', regex: /wc:[0-9a-f]{32}@/gi },\n      // Generic API keys (long alphanumeric strings that look like keys)\n      { type: 'api_key_pattern', regex: /\\b(sk-|bk_|xai-|pk_|sk_live_|rk_live_)[a-zA-Z0-9_\\-]{20,}\\b/g },\n      // BIP-39 mnemonic sequences: 12 or 24 consecutive lowercase words (3-8 chars each)\n      // Heuristic: a sequence of 12+ short lowercase words is very likely a seed phrase\n      // Case-insensitive to catch mixed-case mnemonics (e.g. \"Abandon Ability ...\")\n      { type: 'bip39_mnemonic', regex: /\\b([a-zA-Z]{3,8}\\s+){11,}[a-zA-Z]{3,8}\\b/gi },\n    ];\n\n    // Collect all pattern matches first, then apply redactions in reverse\n    // order to preserve string offsets.\n    const patternMatches: Array<{ type: string; matchStr: string; index: number }> = [];\n\n    for (const { type, regex } of LEAK_PATTERNS) {\n      let match: RegExpExecArray | null;\n      regex.lastIndex = 0;\n      while ((match = regex.exec(text)) !== null) {\n        // Don't flag if already caught as a known secret value\n        if (leaks.some(l => l.position === match!.index)) continue;\n\n        // For private key pattern: apply strict false-positive filters.\n        // A crypto app routinely outputs 64-hex strings (tx hashes, event\n        // topics, ABI-encoded values) that are NOT secrets.\n        if (type === 'private_key') {\n          const hexPart = match[0].replace(/^0x/, '');\n\n          // Filter 1: Low entropy — ABI-padded values like 0x000...001\n          // Real private keys have high entropy (many distinct nibbles).\n          const uniqueNibbles = new Set(hexPart.toLowerCase().split('')).size;\n          if (uniqueNibbles < 8) continue;\n\n          // Filter 2: ABI-encoded address (24 leading zeros + 40-char addr)\n          if (/^0{24}[0-9a-fA-F]{40}$/.test(hexPart)) continue;\n\n          // Filter 3: Context-positive — only flag if surrounding text\n          // indicates this is actually a secret/key. Use a wide window\n          // (80 chars each side) for context.\n          const contextStart = Math.max(0, match.index - 80);\n          const contextEnd = Math.min(text.length, match.index + match[0].length + 80);\n          const surrounding = text.slice(contextStart, contextEnd);\n\n          // Danger-context: words that suggest a secret is being disclosed\n          const DANGER_CONTEXT = /\\b(private\\s*key|secret\\s*key|priv\\s*key|mnemonic|seed\\s*phrase|signing\\s*key|wallet\\s*key|export\\s*key|my\\s*key|your\\s*key)\\b/i;\n          // Safe-context: words that indicate a normal crypto data type\n          const SAFE_CONTEXT = /\\b(tx|transaction|hash|block|receipt|data|calldata|input|encoded|abi|selector|topics|event|log|proof|merkle|root|salt|create2|slot|storage|token[_\\s]?id|nft|signature|sig\\b|nonce|commitment|returndata|output|result|param|arg|swap|transfer|balance|0x[0-9a-fA-F]{40})\\b/i;\n\n          // Only flag if danger context is present AND safe context is absent\n          if (!DANGER_CONTEXT.test(surrounding)) continue;\n          if (SAFE_CONTEXT.test(surrounding)) continue;\n        }\n\n        patternMatches.push({\n          type,\n          matchStr: match[0],\n          index: match.index,\n        });\n      }\n    }\n\n    // Sort by position descending so we can apply redactions from the end\n    // of the string backward, preserving earlier offsets.\n    patternMatches.sort((a, b) => b.index - a.index);\n\n    for (const pm of patternMatches) {\n      leaks.push({\n        type: pm.type,\n        pattern: pm.matchStr.slice(0, 20) + '...',\n        position: pm.index,\n      });\n      redacted = redacted.slice(0, pm.index) + `[REDACTED:${pm.type}]` + redacted.slice(pm.index + pm.matchStr.length);\n    }\n\n    return {\n      clean: leaks.length === 0,\n      leaks,\n      redactedText: redacted,\n    };\n  }\n\n  /**\n   * Get recent access log entries (for diagnostics).\n   */\n  getAccessLog(limit = 50): SecretAccess[] {\n    return this.accessLog.slice(-limit);\n  }\n\n  /**\n   * Get a summary of configured vs unconfigured secrets.\n   */\n  getConfigurationSummary(): Array<{\n    name: string;\n    envVar: string;\n    description: string;\n    configured: boolean;\n    sensitive: string;\n  }> {\n    return Object.entries(SECRET_REGISTRY).map(([name, entry]) => ({\n      name,\n      envVar: entry.envVar,\n      description: entry.description,\n      configured: !!process.env[entry.envVar],\n      sensitive: entry.sensitive,\n    }));\n  }\n\n  // ── Internal ──────────────────────────────────────────────────────────\n\n  private logAccess(key: string, tool: string): void {\n    this.accessLog.push({ key, tool, timestamp: Date.now() });\n    // Trim log\n    if (this.accessLog.length > this.MAX_LOG_SIZE) {\n      this.accessLog = this.accessLog.slice(-this.MAX_LOG_SIZE / 2);\n    }\n  }\n}\n\n// ─── Singleton ───────────────────────────────────────────────────────────\n\nlet _instance: CredentialVault | null = null;\n\nexport function getCredentialVault(): CredentialVault {\n  if (!_instance) {\n    _instance = new CredentialVault();\n  }\n  return _instance;\n}\n\nexport function resetCredentialVault(): void {\n  _instance = null;\n}\n"],"mappings":"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"}