{"version":3,"file":"securityScan.js","names":[],"sources":["esm/cli/securityScan.js"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nconst SECRET_PATTERNS = [\n  // api tokens / keys with known prefixes\n  [\"Anthropic API key\", /sk-ant-[A-Za-z0-9_-]{20,}/g],\n  [\"OpenAI API key\", /sk-proj-[A-Za-z0-9_-]{20,}/g],\n  [\"Stripe secret key\", /sk_live_[A-Za-z0-9]{20,}/g],\n  [\"Stripe webhook secret\", /whsec_[A-Za-z0-9]{20,}/g],\n  [\"GitHub token\", /gh[ps]_[A-Za-z0-9]{36,}/g],\n  [\"GitHub PAT\", /github_pat_[A-Za-z0-9_]{20,}/g],\n  [\"AWS access key\", /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g],\n  [\n    \"Postmark server token\",\n    /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g\n  ],\n  [\"Bearer token assignment\", /[\"']Bearer\\s+[A-Za-z0-9_.-]{20,}[\"']/g],\n  [\n    \"Generic secret assignment\",\n    /(?:secret|password|api_key|apikey)\\s*[:=]\\s*[\"'][A-Za-z0-9_-]{16,}[\"']/gi\n  ],\n  // known env var names that should never be inlined\n  [\"BETTER_AUTH_SECRET value\", /BETTER_AUTH_SECRET[\"']\\s*[:=]\\s*[\"'][^\"']+[\"']/g],\n  [\"POSTMARK_SERVER_TOKEN value\", /POSTMARK_SERVER_TOKEN[\"']\\s*[:=]\\s*[\"'][^\"']+[\"']/g],\n  [\"ANTHROPIC_API_KEY value\", /ANTHROPIC_API_KEY[\"']\\s*[:=]\\s*[\"'][^\"']+[\"']/g]\n];\nconst SAFE_UUIDS = /* @__PURE__ */ new Set([\n  \"00000000-0000-0000-0000-000000000000\",\n  \"09259e3b-7be8-46f6-9801-106bf1866e1c\",\n  // WebRTC SDP\n  \"4ad15a19-80e2-4105-bf43-48039fd2963e\"\n  // WebRTC SDP\n]);\nconst BUILTIN_SAFE_PATTERNS = [\n  /sk_live_your_/,\n  /sk_live_your_key/,\n  /rk_\\w+_\\w+/,\n  // tamagui theme tokens\n  /sk_all_element/,\n  // DOM property names\n  /sk_personal_data/\n  // analytics property names\n];\nfunction createSafeMatcher(userPatterns) {\n  const safeStrings = new Set(SAFE_UUIDS);\n  const safeRegexes = [...BUILTIN_SAFE_PATTERNS];\n  if (userPatterns) {\n    for (const p of userPatterns) {\n      if (typeof p === \"string\") {\n        safeStrings.add(p);\n      } else {\n        safeRegexes.push(p);\n      }\n    }\n  }\n  return (match) => {\n    if (safeStrings.has(match)) return true;\n    return safeRegexes.some((p) => p.test(match));\n  };\n}\nasync function collectJSFiles(dir) {\n  const files = [];\n  async function walk(currentDir) {\n    let entries;\n    try {\n      entries = await readdir(currentDir, { withFileTypes: true });\n    } catch {\n      return;\n    }\n    for (const entry of entries) {\n      const fullPath = join(currentDir, entry.name);\n      if (entry.isDirectory()) {\n        await walk(fullPath);\n      } else if (entry.name.endsWith(\".js\")) {\n        files.push(fullPath);\n      }\n    }\n  }\n  await walk(dir);\n  return files;\n}\nasync function scanBundleForSecrets(distDir, userSafePatterns) {\n  const isSafe = createSafeMatcher(userSafePatterns);\n  const files = await collectJSFiles(distDir);\n  const findings = [];\n  for (const fullPath of files) {\n    const relativePath = fullPath.slice(distDir.length + 1);\n    let content;\n    try {\n      content = await readFile(fullPath, \"utf-8\");\n    } catch {\n      continue;\n    }\n    for (const [label, pattern] of SECRET_PATTERNS) {\n      pattern.lastIndex = 0;\n      let match;\n      while ((match = pattern.exec(content)) !== null) {\n        const matched = match[0];\n        if (isSafe(matched)) continue;\n        const beforeMatch = content.slice(0, match.index);\n        const line = (beforeMatch.match(/\\n/g)?.length ?? 0) + 1;\n        findings.push({\n          file: relativePath,\n          label,\n          match: matched.length > 40 ? `${matched.slice(0, 20)}...${matched.slice(-10)}` : matched,\n          line\n        });\n      }\n    }\n  }\n  return { clean: findings.length === 0, findings };\n}\nasync function runSecurityScan(clientDir, level, safePatterns) {\n  console.info(`\n \\u{1F512} scanning client bundles for leaked secrets...\n`);\n  const { clean, findings } = await scanBundleForSecrets(clientDir, safePatterns);\n  if (clean) {\n    console.info(`\n \\u{1F512} security scan passed \\u2014 no secrets found\n`);\n    return true;\n  }\n  const icon = level === \"error\" ? \"\\u{1F6A8}\" : \"\\u26A0\\uFE0F\";\n  const header = level === \"error\" ? `${icon} ${findings.length} secret(s) leaked into client bundle:` : `${icon} ${findings.length} potential secret(s) found in client bundle:`;\n  console.error(`\n ${header}\n`);\n  for (const f of findings) {\n    console.error(`   ${f.label}`);\n    console.error(`     file: ${f.file}:${f.line}`);\n    console.error(`     match: ${f.match}\n`);\n  }\n  if (level === \"error\") {\n    console.error(\n      ` Set build.securityScan to 'warn' to continue building despite findings.\n`\n    );\n    return false;\n  }\n  console.warn(\n    ` Set build.securityScan to 'error' to fail builds when secrets are detected.\n`\n  );\n  return true;\n}\nexport {\n  runSecurityScan,\n  scanBundleForSecrets\n};\n//# sourceMappingURL=securityScan.js.map\n"],"mappings":"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"}