{"version":3,"file":"reporter.cjs","names":[],"sources":["../src/reporter.ts"],"sourcesContent":["import type { BundleLeak, Violation } from '#types'\n\nconst bold = (text: string): string => `\\x1b[1m${text}\\x1b[22m`\nconst cyan = (text: string): string => `\\x1b[36m${text}\\x1b[39m`\nconst yellow = (text: string): string => `\\x1b[33m${text}\\x1b[39m`\nconst red = (text: string): string => `\\x1b[31m${text}\\x1b[39m`\nconst underline = (text: string): string => `\\x1b[4m${text}\\x1b[24m`\n\nconst PLUGIN_LABEL = bold(cyan('[vite-plugin-safe-env]'))\n\nconst CREDENTIAL_KEYWORDS = ['SECRET', 'KEY', 'TOKEN', 'PASSWORD', 'PASS', 'CREDENTIAL']\nconst LOCATION_KEYWORDS = ['URL', 'HOST', 'ENDPOINT', 'URI', 'ADDR']\n\n/**\n * Assembles the standard terminal output block used by all violation and leak\n * formatters. Accepts the headline, a list of field lines, and the body text\n * for the Risk and Fix sections.\n */\nfunction formatMessageSections(headline: string, fieldLines: string[], riskBody: string, fixBody: string): string {\n  return [\n    '',\n    `${PLUGIN_LABEL} ${headline}`,\n    '',\n    ...fieldLines,\n    '',\n    `  Risk`,\n    `  ${riskBody}`,\n    '',\n    `  Fix`,\n    `  ${fixBody}`,\n    '',\n  ].join('\\n')\n}\n\n/**\n * Generates a context-aware fix suggestion based on the environment variable\n * name and the import chain that makes it reachable from the client.\n * Variables matching credential keywords receive a security-focused warning.\n * Variables whose import chain passes through a server-named file receive\n * advice about import structure. All others receive a general relocation suggestion.\n *\n * @param envVarName - The name of the leaked environment variable.\n * @param importChain - The ordered list of module IDs from the client entry to the leak.\n * @returns A human-readable recommendation for resolving the violation.\n */\nexport function buildSuggestedFix(envVarName: string, importChain: string[]): string {\n  const nameSegments = envVarName.toUpperCase().split('_')\n\n  const importChainPassesThroughServerFile = importChain.some((modulePath) => {\n    const fileName = modulePath.split('/').pop() ?? ''\n    return (\n      modulePath.includes('/server/') ||\n      modulePath.includes('/api/') ||\n      modulePath.includes('/routes/') ||\n      fileName.startsWith('+server.') ||\n      /\\.server\\.(ts|tsx|js|jsx|mts|mjs)$/.test(modulePath)\n    )\n  })\n\n  if (importChainPassesThroughServerFile) {\n    return (\n      `The module containing this access is named correctly but is imported from ` +\n      `a client entry point. Remove the import from the client-side module, or extract ` +\n      `the shared logic into a file that does not access server environment variables.`\n    )\n  }\n\n  if (CREDENTIAL_KEYWORDS.some((keyword) => nameSegments.includes(keyword))) {\n    return (\n      `${envVarName} appears to be a credential. Exposing it in the client bundle ` +\n      `is a security vulnerability. Move all access into a server-only module that ` +\n      `is never imported from a client entry point.`\n    )\n  }\n\n  if (LOCATION_KEYWORDS.some((keyword) => nameSegments.includes(keyword))) {\n    return (\n      `${envVarName} is a server-side connection value. Move all database or service ` +\n      `connection logic into a server-only module. If this value is intentionally ` +\n      `public, add it to the allowClientAccess option in your Vite config.`\n    )\n  }\n\n  return (\n    `Move all access to ${envVarName} into a server-only module that is never ` +\n    `imported from a client entry point. If this variable is intentionally public, ` +\n    `add it to the allowClientAccess option in your Vite config.`\n  )\n}\n\n/**\n * Formats a potential violation as a human-readable multi-line terminal string.\n *\n * @param violation - The violation to format.\n * @returns A formatted string ready for terminal output.\n */\nexport function formatViolationForTerminal(violation: Violation): string {\n  return formatMessageSections(\n    'Server-only environment variable may leak to client bundle',\n    [\n      `  Variable    ${yellow(violation.envVarName)}`,\n      `  File        ${underline(violation.moduleId)}:${violation.line}`,\n      '',\n      `  Reachable via`,\n      ...violation.importChain.map((modulePath) => `              ${modulePath}`),\n    ],\n    `The value of ${violation.envVarName} will be visible in your production\\n  JavaScript bundle to anyone who opens browser DevTools.`,\n    violation.suggestedFix\n  )\n}\n\n/**\n * Formats a confirmed bundle leak as a human-readable multi-line terminal string.\n * This is the Phase 2 ground-truth error with precise source attribution from\n * sourcemap resolution.\n *\n * @param bundleLeak - The confirmed bundle leak to format.\n * @returns A formatted string ready for terminal output.\n */\nexport function formatBundleLeakForTerminal(bundleLeak: BundleLeak): string {\n  return formatMessageSections(\n    'Server-only environment variable confirmed in client bundle',\n    [\n      `  Variable    ${red(bundleLeak.envVarName)}`,\n      `  Chunk       ${bundleLeak.chunkFileName}`,\n      `  Origin      ${underline(bundleLeak.originalFile)}:${bundleLeak.originalLine}`,\n    ],\n    `The actual value of ${bundleLeak.envVarName} is present in the output bundle\\n  and is readable by anyone who downloads the file.`,\n    `Remove all server-only environment variable access from modules that are\\n  reachable from client entry points.`\n  )\n}\n\n/**\n * Formats a violation as a browser overlay error payload compatible with\n * Vite's built-in error overlay WebSocket protocol.\n *\n * @param violation - The violation to format for the browser overlay.\n * @returns A payload object ready to send via `server.ws.send`.\n */\nexport function formatViolationForOverlay(violation: Violation): {\n  type: 'error'\n  err: { message: string; stack: string; plugin: string }\n} {\n  const message = [\n    `Server-only environment variable may leak to client bundle`,\n    ``,\n    `Variable: ${violation.envVarName}`,\n    `File: ${violation.moduleId}:${violation.line}`,\n    ``,\n    `Reachable via: ${violation.importChain.join(' > ')}`,\n    ``,\n    violation.suggestedFix,\n  ].join('\\n')\n\n  return {\n    type: 'error',\n    err: {\n      message,\n      stack: `    at ${violation.moduleId}:${violation.line}:${violation.column}`,\n      plugin: 'vite-plugin-safe-env',\n    },\n  }\n}\n"],"mappings":";;AAEA,MAAM,QAAQ,SAAyB,UAAU,KAAK;AACtD,MAAM,QAAQ,SAAyB,WAAW,KAAK;AACvD,MAAM,UAAU,SAAyB,WAAW,KAAK;AACzD,MAAM,OAAO,SAAyB,WAAW,KAAK;AACtD,MAAM,aAAa,SAAyB,UAAU,KAAK;AAE3D,MAAM,eAAe,KAAK,KAAK,yBAAyB,CAAC;AAEzD,MAAM,sBAAsB;CAAC;CAAU;CAAO;CAAS;CAAY;CAAQ;CAAa;AACxF,MAAM,oBAAoB;CAAC;CAAO;CAAQ;CAAY;CAAO;CAAO;;;;;;AAOpE,SAAS,sBAAsB,UAAkB,YAAsB,UAAkB,SAAyB;AAChH,QAAO;EACL;EACA,GAAG,aAAa,GAAG;EACnB;EACA,GAAG;EACH;EACA;EACA,KAAK;EACL;EACA;EACA,KAAK;EACL;EACD,CAAC,KAAK,KAAK;;;;;;;;;;;;;AAcd,SAAgB,kBAAkB,YAAoB,aAA+B;CACnF,MAAM,eAAe,WAAW,aAAa,CAAC,MAAM,IAAI;AAaxD,KAX2C,YAAY,MAAM,eAAe;EAC1E,MAAM,WAAW,WAAW,MAAM,IAAI,CAAC,KAAK,IAAI;AAChD,SACE,WAAW,SAAS,WAAW,IAC/B,WAAW,SAAS,QAAQ,IAC5B,WAAW,SAAS,WAAW,IAC/B,SAAS,WAAW,WAAW,IAC/B,qCAAqC,KAAK,WAAW;GAInB,CACpC,QACE;AAMJ,KAAI,oBAAoB,MAAM,YAAY,aAAa,SAAS,QAAQ,CAAC,CACvE,QACE,GAAG,WAAW;AAMlB,KAAI,kBAAkB,MAAM,YAAY,aAAa,SAAS,QAAQ,CAAC,CACrE,QACE,GAAG,WAAW;AAMlB,QACE,sBAAsB,WAAW;;;;;;;;AAYrC,SAAgB,2BAA2B,WAA8B;AACvE,QAAO,sBACL,8DACA;EACE,iBAAiB,OAAO,UAAU,WAAW;EAC7C,iBAAiB,UAAU,UAAU,SAAS,CAAC,GAAG,UAAU;EAC5D;EACA;EACA,GAAG,UAAU,YAAY,KAAK,eAAe,iBAAiB,aAAa;EAC5E,EACD,gBAAgB,UAAU,WAAW,iGACrC,UAAU,aACX;;;;;;;;;;AAWH,SAAgB,4BAA4B,YAAgC;AAC1E,QAAO,sBACL,+DACA;EACE,iBAAiB,IAAI,WAAW,WAAW;EAC3C,iBAAiB,WAAW;EAC5B,iBAAiB,UAAU,WAAW,aAAa,CAAC,GAAG,WAAW;EACnE,EACD,uBAAuB,WAAW,WAAW,wFAC7C,kHACD;;;;;;;;;AAUH,SAAgB,0BAA0B,WAGxC;AAYA,QAAO;EACL,MAAM;EACN,KAAK;GACH,SAdY;IACd;IACA;IACA,aAAa,UAAU;IACvB,SAAS,UAAU,SAAS,GAAG,UAAU;IACzC;IACA,kBAAkB,UAAU,YAAY,KAAK,MAAM;IACnD;IACA,UAAU;IACX,CAAC,KAAK,KAKI;GACP,OAAO,UAAU,UAAU,SAAS,GAAG,UAAU,KAAK,GAAG,UAAU;GACnE,QAAQ;GACT;EACF"}