{"version":3,"file":"integrity.cjs","names":["fetchResponse","fetchBosConfigFromFastKv"],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fetchBosConfigFromFastKv } from \"./fastkv\";\nimport { fetchResponse } from \"./http-client\";\n\nconst DEFAULT_MAX_SRI_RESPONSE_BYTES = 20 * 1024 * 1024;\n\ninterface SriUrlOptions {\n  resolveEntryUrl?: boolean;\n  maxBytes?: number;\n}\n\nexport function computeSriHash(content: string | Buffer): string {\n  return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nfunction resolveSriTargetUrl(url: string, options?: SriUrlOptions): string {\n  return options?.resolveEntryUrl === false ? url : resolveEntryUrl(url);\n}\n\nfunction getMaxSriResponseBytes(options?: SriUrlOptions): number {\n  return options?.maxBytes ?? DEFAULT_MAX_SRI_RESPONSE_BYTES;\n}\n\nasync function computeSriHashFromResponse(\n  response: Response,\n  url: string,\n  options?: SriUrlOptions,\n): Promise<string> {\n  const maxBytes = getMaxSriResponseBytes(options);\n  const contentLengthHeader = response.headers.get(\"content-length\");\n\n  if (contentLengthHeader) {\n    const contentLength = Number(contentLengthHeader);\n    if (Number.isFinite(contentLength) && contentLength > maxBytes) {\n      throw new Error(\n        `[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${contentLength})`,\n      );\n    }\n  }\n\n  if (!response.body) {\n    throw new Error(`[SRI] Missing response body for ${url}`);\n  }\n\n  const hash = createHash(\"sha384\");\n  const reader = response.body.getReader();\n  let totalBytes = 0;\n\n  while (true) {\n    const { done, value } = await reader.read();\n    if (done) {\n      break;\n    }\n\n    totalBytes += value.byteLength;\n    if (totalBytes > maxBytes) {\n      await reader.cancel();\n      throw new Error(\n        `[SRI] Response for ${url} exceeds max size of ${maxBytes} bytes (${totalBytes})`,\n      );\n    }\n\n    hash.update(value);\n  }\n\n  return `sha384-${hash.digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(\n  url: string,\n  options?: SriUrlOptions,\n): Promise<string | null> {\n  try {\n    const entryUrl = resolveSriTargetUrl(url, options);\n\n    const response = await fetchResponse(entryUrl, { timeout: \"30 seconds\" });\n    if (!response.ok) {\n      console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n      return null;\n    }\n    return await computeSriHashFromResponse(response, entryUrl, options);\n  } catch (error) {\n    console.warn(\n      `[SRI] Error computing integrity for ${url}:`,\n      error instanceof Error ? error.message : error,\n    );\n    return null;\n  }\n}\n\nexport function resolveEntryUrl(url: string): string {\n  if (url.endsWith(\"/remoteEntry.js\")) return url;\n  if (url.endsWith(\"/mf-manifest.json\"))\n    return `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`;\n  return `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n}\n\nexport async function verifySriForUrl(\n  url: string,\n  expectedIntegrity: string,\n  options?: SriUrlOptions,\n): Promise<void> {\n  const entryUrl = resolveSriTargetUrl(url, options);\n\n  const response = await fetchResponse(entryUrl, { timeout: \"30 seconds\" });\n  if (!response.ok) {\n    console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n    return;\n  }\n\n  const computed = await computeSriHashFromResponse(response, entryUrl, options);\n\n  if (computed !== expectedIntegrity) {\n    throw new Error(\n      `[SRI] Integrity check failed for ${entryUrl}\\n  Expected: ${expectedIntegrity}\\n  Computed: ${computed}`,\n    );\n  }\n}\n\nexport class IntegrityRegistry {\n  private hashes = new Map<string, string>();\n\n  register(url: string, integrity: string): void {\n    this.hashes.set(url, integrity);\n  }\n\n  registerEntry(baseUrl: string, integrity: string): void {\n    this.hashes.set(resolveEntryUrl(baseUrl), integrity);\n  }\n\n  get(url: string): string | undefined {\n    return this.hashes.get(url);\n  }\n\n  has(url: string): boolean {\n    return this.hashes.has(url);\n  }\n\n  entries(): IterableIterator<[string, string]> {\n    return this.hashes.entries();\n  }\n}\n\nfunction extractIntegrityHashes(config: Record<string, unknown>): Map<string, string> {\n  const hashes = new Map<string, string>();\n  const app = config.app as Record<string, Record<string, unknown>> | undefined;\n  const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n\n  if (app) {\n    for (const [, entry] of Object.entries(app)) {\n      if (entry?.integrity && entry?.production) {\n        hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n      }\n    }\n  }\n\n  if (plugins) {\n    for (const [, entry] of Object.entries(plugins)) {\n      if (entry?.integrity && entry?.production) {\n        hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n      }\n    }\n  }\n\n  return hashes;\n}\n\nexport async function verifyConfigAgainstChain(\n  localConfig: Record<string, unknown>,\n  bosUrl: string,\n): Promise<{ verified: boolean; mismatches: string[] }> {\n  const mismatches: string[] = [];\n\n  let chainConfig: Record<string, unknown>;\n  try {\n    chainConfig = await fetchBosConfigFromFastKv<Record<string, unknown>>(bosUrl);\n  } catch (error) {\n    console.warn(\n      `[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`,\n    );\n    return { verified: false, mismatches: [\"chain-fetch-failed\"] };\n  }\n\n  const localHashes = extractIntegrityHashes(localConfig);\n  const chainHashes = extractIntegrityHashes(chainConfig);\n\n  for (const [url, chainHash] of chainHashes) {\n    const localHash = localHashes.get(url);\n    if (localHash && localHash !== chainHash) {\n      mismatches.push(url);\n      console.error(\n        `[Attestation] Integrity mismatch for ${url}\\n  Local: ${localHash}\\n  Chain: ${chainHash}`,\n      );\n    }\n  }\n\n  if (mismatches.length === 0 && localHashes.size > 0) {\n    console.log(\n      `[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`,\n    );\n  }\n\n  return { verified: mismatches.length === 0, mismatches };\n}\n\nexport interface DeployResultEntry {\n  url: string;\n  integrity?: string;\n  urlField: string;\n  integrityField?: string;\n}\n\nfunction setNestedPath(obj: Record<string, unknown>, dottedPath: string, value: unknown): void {\n  const keys = dottedPath.split(\".\");\n  let current = obj;\n  for (let i = 0; i < keys.length - 1; i += 1) {\n    const key = keys[i]!;\n    if (current[key] === undefined || current[key] === null || typeof current[key] !== \"object\") {\n      current[key] = {};\n    }\n    current = current[key] as Record<string, unknown>;\n  }\n  current[keys[keys.length - 1]!] = value;\n}\n\nfunction deleteNestedPath(obj: Record<string, unknown>, dottedPath: string): void {\n  const keys = dottedPath.split(\".\");\n  let current = obj;\n  for (let i = 0; i < keys.length - 1; i += 1) {\n    const key = keys[i]!;\n    if (current[key] === undefined || typeof current[key] !== \"object\") return;\n    current = current[key] as Record<string, unknown>;\n  }\n  delete current[keys[keys.length - 1]!];\n}\n\nexport function reportDeployResult(opts: {\n  url: string;\n  integrity?: string | null;\n  bosConfigPath: string;\n  urlField: string;\n  integrityField?: string;\n}): void {\n  console.log(\n    `[BOS_DEPLOY] url=${opts.url} urlField=${opts.urlField} integrityField=${opts.integrityField ?? \"\"} integrity=${opts.integrity ?? \"\"}`,\n  );\n\n  try {\n    const config = JSON.parse(readFileSync(opts.bosConfigPath, \"utf8\")) as Record<string, unknown>;\n    setNestedPath(config, opts.urlField, opts.url);\n    if (opts.integrityField) {\n      if (opts.integrity) {\n        setNestedPath(config, opts.integrityField, opts.integrity);\n      } else {\n        deleteNestedPath(config, opts.integrityField);\n      }\n    }\n    writeFileSync(opts.bosConfigPath, `${JSON.stringify(config, null, 2)}\\n`);\n    console.log(`   ✅ Updated bos.config.json: ${opts.urlField}`);\n    if (opts.integrityField && opts.integrity) {\n      console.log(`   ✅ Updated bos.config.json: ${opts.integrityField}`);\n    }\n  } catch (err) {\n    console.error(\"   ❌ Failed to update bos.config.json:\", (err as Error).message);\n  }\n}\n\nexport function parseDeployLines(output: string): DeployResultEntry[] {\n  const results: DeployResultEntry[] = [];\n  for (const line of output.split(\"\\n\")) {\n    if (!line.includes(\"[BOS_DEPLOY]\")) continue;\n    const urlMatch = line.match(/url=(\\S+)/);\n    const urlFieldMatch = line.match(/urlField=(\\S+)/);\n    if (!urlMatch || !urlFieldMatch) continue;\n    const integrityFieldMatch = line.match(/integrityField=(\\S+)/);\n    const integrityMatch = line.match(/integrity=(\\S+)/);\n    results.push({\n      url: urlMatch[1],\n      urlField: urlFieldMatch[1],\n      integrityField: integrityFieldMatch?.[1] || undefined,\n      integrity: integrityMatch?.[1] || undefined,\n    });\n  }\n  return results;\n}\n\nexport function applyDeployResults(\n  config: Record<string, unknown>,\n  results: DeployResultEntry[],\n): Record<string, unknown> {\n  const merged = structuredClone(config);\n  for (const result of results) {\n    setNestedPath(merged, result.urlField, result.url);\n    if (result.integrityField) {\n      if (result.integrity) {\n        setNestedPath(merged, result.integrityField, result.integrity);\n      } else {\n        deleteNestedPath(merged, result.integrityField);\n      }\n    }\n  }\n  return merged;\n}\n\nexport function findPluginKey(bosConfigPath: string, pluginDir: string): string | null {\n  const config = JSON.parse(readFileSync(bosConfigPath, \"utf8\")) as Record<string, unknown>;\n  const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n  if (!plugins) return null;\n  const configRoot = join(bosConfigPath, \"..\");\n  const normalizedPluginDir = pluginDir.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n  for (const [key, plugin] of Object.entries(plugins)) {\n    const dev = plugin?.development;\n    if (typeof dev !== \"string\" || !dev.startsWith(\"local:\")) continue;\n    const resolved = join(configRoot, dev.slice(\"local:\".length))\n      .replace(/\\\\/g, \"/\")\n      .replace(/\\/+$/, \"\");\n    if (resolved === normalizedPluginDir) return key;\n  }\n  return null;\n}\n"],"mappings":"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"}