// Reads the single account-wide Cloudflare credential from the house-only // config directory. This file is the one place the account-wide capability // lives. House-only is a location convention plus the cf-token.sh caller gate, // NOT an OS permission: every account's session runs as the same unix user, so // a sub-account agent holding Bash can read this path directly. That is a // permanent property, not a pending gap: OS user separation was costed and // declined as disproportionate (Task 1690, archived undone), so nothing tracks // it. Do not cite this file's location or mode as a boundary. Parses // KEY=value lines without polluting process.env (same discipline as // reconcile-bookings' readEnvFile). import { readFileSync } from "node:fs"; import { join } from "node:path"; export interface HouseCredential { apiToken: string; accountId: string; } export function readHouseCredential(platformRoot: string): HouseCredential { const path = join(platformRoot, "config", "cloudflare-house.env"); let raw: string; try { raw = readFileSync(path, "utf8"); } catch { throw new Error(`storage-broker: house credential not found at ${path}`); } const env: Record = {}; for (const line of raw.split("\n")) { const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)$/); if (m) env[m[1]] = m[2].trim(); } const apiToken = env.CLOUDFLARE_API_TOKEN; const accountId = env.CLOUDFLARE_ACCOUNT_ID; if (!apiToken) throw new Error(`storage-broker: CLOUDFLARE_API_TOKEN missing in ${path}`); if (!accountId) throw new Error(`storage-broker: CLOUDFLARE_ACCOUNT_ID missing in ${path}`); return { apiToken, accountId }; }