'use strict';

var zlib = require('zlib');
var http = require('http');
var crypto = require('crypto');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var zlib__default = /*#__PURE__*/_interopDefault(zlib);
var crypto__default = /*#__PURE__*/_interopDefault(crypto);

// src/runtime/lib/redaction.ts
function redactString(valStr, modeOrOpts, hideLength = true) {
  if (!valStr) return valStr;
  let mode;
  {
    mode = modeOrOpts;
  }
  const hiddenLength = hideLength ? 5 : valStr.length - 2;
  const hiddenStr = "\u2592".repeat(hiddenLength);
  if (mode === "show_last_2") {
    return `${hiddenStr}${valStr.substring(valStr.length - 2, valStr.length)}`;
  } else if (mode === "show_first_last") {
    return `${valStr.substring(0, 1)}${hiddenStr}${valStr.substring(valStr.length - 1, valStr.length)}`;
  } else {
    return `${valStr.substring(0, 2)}${hiddenStr}`;
  }
}

// src/lib/detect-runtime.ts
var versionsStr = "versions";
var processVersions = typeof process !== "undefined" && process[versionsStr];
processVersions && processVersions.node != null;
typeof window !== "undefined" && window.name === "nodejs" || typeof navigator !== "undefined" && "userAgent" in navigator && typeof navigator.userAgent === "string" && (navigator.userAgent.includes("Node.js") || navigator.userAgent.includes("jsdom"));
typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined";
processVersions && processVersions.bun != null;
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement === "function" && typeof navigator !== "undefined" && typeof navigator.userAgent === "string";

// src/runtime/lib/debug.ts
function debug(...args) {
  if (!globalThis.process?.env.DEBUG_VARLOCK) return;
  console.log(...args);
}

// src/runtime/env.ts
function isString(s) {
  return Object.prototype.toString.call(s) === "[object String]";
}
var UNMASK_STR = "\u{1F441}";
var REDACTION_STATE_KEY = "__varlockRedactionState";
function getRedactionState() {
  if (!globalThis[REDACTION_STATE_KEY]) {
    globalThis[REDACTION_STATE_KEY] = {
      sensitiveSecretsMap: {},
      redactorFindReplace: void 0
    };
  }
  return globalThis[REDACTION_STATE_KEY];
}
function resetRedactionMap(graph) {
  const state = getRedactionState();
  state.sensitiveSecretsMap = {};
  for (const itemKey in graph.config) {
    const item = graph.config[itemKey];
    if (item.isSensitive && item.value && isString(item.value)) {
      const redacted = redactString(item.value);
      if (redacted) {
        state.sensitiveSecretsMap[item.value] = {
          key: itemKey,
          redacted,
          preventLeaks: item.preventLeaks !== false
        };
      }
    }
  }
  if (!Object.keys(state.sensitiveSecretsMap).length) {
    state.redactorFindReplace = void 0;
    return;
  }
  const findRegex = new RegExp(
    [
      `(${UNMASK_STR} )?`,
      "(",
      Object.keys(state.sensitiveSecretsMap).map((s) => s.replace(/[()[\]{}*+?^$|#.,/\\\s-]/g, "\\$&")).sort((a, b) => b.length - a.length).join("|"),
      ")",
      `( ${UNMASK_STR})?`
    ].join(""),
    "g"
  );
  const replaceFn = (match, pre, val, post) => {
    if (pre && post) return match;
    return state.sensitiveSecretsMap[val].redacted;
  };
  state.redactorFindReplace = { find: findRegex, replace: replaceFn };
}
function redactSensitiveConfig(o) {
  const { redactorFindReplace } = getRedactionState();
  if (!redactorFindReplace) return o;
  if (!o) return o;
  if (Array.isArray(o)) {
    return o.map(redactSensitiveConfig);
  }
  if (o && typeof o === "object" && Object.getPrototypeOf(o) === Object.prototype) {
    try {
      return JSON.parse(redactSensitiveConfig(JSON.stringify(o)));
    } catch (err) {
      return o;
    }
  }
  const type = typeof o;
  if (type === "string" || type === "object" && Object.prototype.toString.call(o) === "[object String]") {
    return o.replaceAll(redactorFindReplace.find, redactorFindReplace.replace);
  }
  return o;
}
function revealSensitiveConfig(secretStr) {
  if (!globalThis._varlockOrigWriteToConsoleFn) return secretStr;
  return `${UNMASK_STR} ${secretStr} ${UNMASK_STR}`;
}
function scanForLeaks(toScan, meta) {
  debug("\u26A1\uFE0F varlock scanning for leaks");
  if (!toScan) return toScan;
  function scanStrForLeaks(strToScan) {
    const { sensitiveSecretsMap } = getRedactionState();
    for (const sensitiveValue in sensitiveSecretsMap) {
      if (!sensitiveSecretsMap[sensitiveValue].preventLeaks) continue;
      if (strToScan.includes(sensitiveValue)) {
        const itemKey = sensitiveSecretsMap[sensitiveValue].key;
        console.error([
          "",
          `\u{1F6A8} ${"DETECTED LEAKED SENSITIVE CONFIG"} \u{1F6A8}`,
          `> Config item key: ${itemKey}`,
          ...meta?.method ? [`> Scan method: ${meta.method}`] : [],
          ...meta?.file ? [`> File: ${meta.file}`] : [],
          ""
        ].join("\n"));
        throw new Error(`\u{1F6A8} DETECTED LEAKED SENSITIVE CONFIG - ${itemKey}`);
      }
    }
  }
  if (isString(toScan)) {
    scanStrForLeaks(toScan);
    return toScan;
  } else if (typeof Buffer !== "undefined" && toScan instanceof Buffer) {
    scanStrForLeaks(toScan.toString());
    return toScan;
  } else if (ArrayBuffer.isView(toScan) || toScan instanceof ArrayBuffer) {
    const decoder = new TextDecoder();
    scanStrForLeaks(decoder.decode(toScan));
    return toScan;
  } else if (toScan instanceof ReadableStream) {
    if (toScan.locked) {
      return toScan;
    }
    const chunkDecoder = new TextDecoder();
    return toScan.pipeThrough(
      new TransformStream({
        transform(chunk, controller) {
          const chunkStr = chunkDecoder.decode(chunk);
          scanStrForLeaks(chunkStr);
          controller.enqueue(chunk);
        }
      })
    );
  }
  return toScan;
}
var initializedEnv = false;
var configHasErrors = false;
var envValues = {};
var varlockSettings = {};
var processExists = !!globalThis.process;
var originalProcessEnv = { ...processExists && process.env };
var varlockInjectedProcessEnvKeys;
function initVarlockEnv(opts) {
  debug("\u26A1\uFE0F INIT VARLOCK ENV!", initializedEnv, !!globalThis.__varlockLoadedEnv, !!globalThis.process?.env.__VARLOCK_ENV);
  if (isBrowser && !globalThis.process?.env.__VARLOCK_ENV) {
    initializedEnv = true;
    return;
  }
  let serializedEnvData;
  if (globalThis.__varlockLoadedEnv) {
    serializedEnvData = globalThis.__varlockLoadedEnv;
  } else if (processExists && process.env.__VARLOCK_ENV) {
    serializedEnvData = JSON.parse(process.env.__VARLOCK_ENV);
  } else {
    if (opts?.allowFail) return;
    console.error([
      "",
      "\u{1F6A8} initVarlockEnv failed  \u{1F6A8}",
      "try rerunning your command via `varlock run`",
      ""
    ].join("\n"));
    throw new Error("initVarlockEnv failed");
  }
  Object.assign(varlockSettings, serializedEnvData.settings);
  configHasErrors = !!serializedEnvData.errors;
  resetRedactionMap(serializedEnvData);
  const setProcessEnv = processExists && !serializedEnvData.settings?.disableProcessEnvInjection;
  if (setProcessEnv) {
    if (varlockInjectedProcessEnvKeys) {
      for (const key of varlockInjectedProcessEnvKeys) delete process.env[key];
      for (const key of Object.keys(originalProcessEnv)) process.env[key] = originalProcessEnv[key];
    }
    varlockInjectedProcessEnvKeys = [];
  }
  for (const itemKey in serializedEnvData.config) {
    const itemValue = serializedEnvData.config[itemKey].value;
    envValues[itemKey] = itemValue;
    if (setProcessEnv) {
      varlockInjectedProcessEnvKeys?.push(itemKey);
      process.env[itemKey] = itemValue === void 0 ? "" : String(itemValue);
    }
  }
  initializedEnv = true;
}
try {
  if (!initializedEnv) {
    initVarlockEnv({ allowFail: true });
  }
} catch (err) {
}
var IGNORED_PROXY_KEYS = [
  // vue - see https://github.com/vuejs/core/blob/70773d00985135a50556c61fb9855ed6b930cb82/packages/reactivity/src/ref.ts#L101
  "__v_isRef"
];
var EnvProxy = new Proxy({}, {
  get(target, prop) {
    if (typeof prop === "symbol") return;
    if (IGNORED_PROXY_KEYS.includes(prop)) return;
    if (!initializedEnv) {
      throw new Error(
        "varlock ENV not initialized \u2014 make sure varlock is set up correctly.\nSee https://varlock.dev/docs/get-started for setup instructions."
      );
    }
    if (configHasErrors) {
      console.error(`[varlock] \u26A0\uFE0F ENV.${prop} accessed but config has errors \u2014 values may be missing or incorrect`);
      return void 0;
    }
    if (prop in envValues) return envValues[prop];
    if (globalThis.__varlockThrowOnMissingKeys) {
      if (globalThis.__varlockValidKeys && globalThis.__varlockValidKeys.includes(prop)) {
        throw new Error(`\`ENV.${prop}\` exists, but is not available in this environment`);
      } else {
        throw new Error(`\`ENV.${prop}\` does not exist`);
      }
    }
    return void 0;
  }
});
var ENV = EnvProxy;

// src/runtime/patch-console.ts
function patchGlobalConsole() {
  debug("\u26A1\uFE0F PATCHING global console methods");
  if (console.log._varlockPatchedFn) {
    debug("> already patched");
    return;
  }
  if (varlockSettings.redactLogs === false) {
    debug("> disabled by settings");
    return;
  }
  const kWriteToConsoleSymbol = Object.getOwnPropertySymbols(globalThis.console).find((s) => s.description === "kWriteToConsole");
  globalThis._varlockOrigWriteToConsoleFn ||= globalThis.console[kWriteToConsoleSymbol];
  globalThis.console[kWriteToConsoleSymbol] = function() {
    globalThis._varlockOrigWriteToConsoleFn.apply(this, [
      arguments[0],
      redactSensitiveConfig(arguments[1]),
      arguments[2]
    ]);
  };
  for (const logMethodName of ["trace", "debug", "info", "log", "info", "warn", "error"]) {
    const originalLogMethod = globalThis.console[logMethodName];
    const patchedFn = function() {
      originalLogMethod.apply(this, Array.from(arguments).map(redactSensitiveConfig));
    };
    patchedFn._varlockPatchedFn = true;
    globalThis.console[logMethodName] = patchedFn;
  }
}
var patchedKey = "_patchedByVarlock";
function patchGlobalServerResponse(opts) {
  debug("\u26A1\uFE0F PATCHING global ServerResponse");
  if (Object.getOwnPropertyDescriptor(http.ServerResponse.prototype, patchedKey)) {
    debug("> already patched");
    return;
  }
  if (varlockSettings.preventLeaks === false) {
    debug("> disabled by settings");
    return;
  }
  Object.defineProperty(http.ServerResponse.prototype, patchedKey, { value: true });
  const serverResponseWrite = http.ServerResponse.prototype.write;
  http.ServerResponse.prototype.write = function varlockPatchedServerResponseWrite(...args) {
    const rawChunk = args[0];
    const contentType = this.getHeader("content-type")?.toString() || "";
    let runScan = contentType.startsWith("text/") || contentType.startsWith("application/json") || !contentType && typeof rawChunk === "string";
    this.req.url;
    if (!runScan) {
      return serverResponseWrite.apply(this, args);
    }
    const compressionType = this.getHeader("Content-Encoding");
    let chunkStr;
    if (typeof rawChunk === "string") {
      chunkStr = rawChunk;
    } else if (!compressionType) {
      const decoder = new TextDecoder();
      chunkStr = decoder.decode(rawChunk);
    } else if (compressionType === "gzip") {
      if (!this._zlibChunks) {
        this._zlibChunks = [rawChunk];
      } else {
        this._zlibChunks?.push(rawChunk);
        try {
          const unzippedChunk = zlib__default.default.unzipSync(Buffer.concat(this._zlibChunks || []), {
            flush: zlib__default.default.constants.Z_SYNC_FLUSH,
            finishFlush: zlib__default.default.constants.Z_SYNC_FLUSH
          });
          const fullUnzippedData = unzippedChunk.toString("utf-8");
          chunkStr = fullUnzippedData.substring(this._lastChunkEndIndex || 0);
          this._lastChunkEndIndex = fullUnzippedData.length;
        } catch (err) {
        }
      }
    }
    if (chunkStr) {
      try {
        scanForLeaks(chunkStr, { method: "patched ServerResponse.write", file: this.req.url });
      } catch (err) {
        {
          throw err;
        }
      }
    }
    return serverResponseWrite.apply(this, args);
  };
  const serverResponseEnd = http.ServerResponse.prototype.end;
  http.ServerResponse.prototype.end = function patchedServerResponseEnd(...args) {
    const endChunk = args[0];
    if (endChunk && typeof endChunk === "string") {
      scanForLeaks(endChunk, { method: "patched ServerResponse.end" });
    }
    return serverResponseEnd.apply(this, args);
  };
}

// src/runtime/patch-response.ts
function patchGlobalResponse() {
  debug("\u26A1\uFE0F PATCHING global Response");
  if (globalThis.Response._patchedByVarlock) {
    debug("> already patched");
    return;
  }
  if (varlockSettings.preventLeaks === false) {
    debug("> disabled by settings");
    return;
  }
  const _UnpatchedResponse = globalThis.Response;
  globalThis.Response = class VarlockPatchedResponse extends _UnpatchedResponse {
    static _patchedByVarlock = true;
    // Make native fetch() responses (which are instances of the original Response)
    // pass instanceof checks against the patched globalThis.Response.
    static [Symbol.hasInstance](instance) {
      return instance instanceof _UnpatchedResponse;
    }
    constructor(body, init) {
      debug("\u26A1\uFE0F patched Response constructor");
      super(scanForLeaks(body, { method: "patched Response constructor" }), init);
    }
    static json(data, init) {
      debug("\u26A1\uFE0F patched Response.json");
      scanForLeaks(JSON.stringify(data), { method: "patched Response.json" });
      const r = _UnpatchedResponse.json(data, init);
      Object.setPrototypeOf(r, Response.prototype);
      return r;
    }
  };
}
var ENCRYPTED_PREFIX = "varlock:v1:";
var IV_LENGTH = 12;
var AUTH_TAG_LENGTH = 16;
var KEY_LENGTH_HEX = 64;
function isEncryptedBlob(value) {
  return value.startsWith(ENCRYPTED_PREFIX);
}
function validateHexKey(hexKey) {
  if (hexKey.length !== KEY_LENGTH_HEX || !/^[0-9a-f]+$/i.test(hexKey)) {
    throw new Error(`[varlock] _VARLOCK_ENV_KEY must be a ${KEY_LENGTH_HEX}-character hex string (256 bits)`);
  }
}
function hexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
  }
  return bytes;
}
function decryptEnvBlobSync(encrypted, hexKey) {
  validateHexKey(hexKey);
  if (!isEncryptedBlob(encrypted)) {
    throw new Error("[varlock] expected encrypted blob with varlock:v1: prefix");
  }
  const combined = Buffer.from(encrypted.slice(ENCRYPTED_PREFIX.length), "base64");
  const iv = combined.subarray(0, IV_LENGTH);
  const authTag = combined.subarray(combined.length - AUTH_TAG_LENGTH);
  const ciphertext = combined.subarray(IV_LENGTH, combined.length - AUTH_TAG_LENGTH);
  const decipher = crypto__default.default.createDecipheriv("aes-256-gcm", hexToBytes(hexKey), iv);
  decipher.setAuthTag(authTag);
  return decipher.update(ciphertext, void 0, "utf8") + decipher.final("utf8");
}

// src/runtime/init-server.ts
if (process.env.__VARLOCK_ENV && isEncryptedBlob(process.env.__VARLOCK_ENV)) {
  const key = process.env._VARLOCK_ENV_KEY;
  if (!key) throw new Error("[varlock] __VARLOCK_ENV is encrypted but _VARLOCK_ENV_KEY is not set");
  process.env.__VARLOCK_ENV = decryptEnvBlobSync(process.env.__VARLOCK_ENV, key);
}
initVarlockEnv();
patchGlobalConsole();
patchGlobalServerResponse();
patchGlobalResponse();
globalThis.__varlockPatchConsole = patchGlobalConsole;

exports.ENV = ENV;
exports.redactSensitiveConfig = redactSensitiveConfig;
exports.revealSensitiveConfig = revealSensitiveConfig;
exports.scanForLeaks = scanForLeaks;
