@agenticmail/core 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ var MailSender = class {
78
78
  const code = err?.responseCode ?? err?.code;
79
79
  const isTransient = typeof code === "number" && code >= 400 && code < 500 || code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ESOCKET";
80
80
  if (!isTransient || attempt === MAX_RETRIES) throw err;
81
- await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
81
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3 * (attempt + 1)));
82
82
  }
83
83
  }
84
84
  throw lastError;
@@ -205,7 +205,13 @@ var MailReceiver = class {
205
205
  async fetchMessage(uid, mailbox = "INBOX") {
206
206
  const lock = await this.client.getMailboxLock(mailbox);
207
207
  try {
208
- const { content } = await this.client.download(String(uid), void 0, { uid: true });
208
+ const result = await this.client.download(String(uid), void 0, { uid: true });
209
+ const content = result?.content;
210
+ if (!content) {
211
+ const err = new Error(`Message UID ${uid} not found in mailbox "${mailbox}"`);
212
+ err.code = "MESSAGE_NOT_FOUND";
213
+ throw err;
214
+ }
209
215
  const chunks = [];
210
216
  for await (const chunk of content) {
211
217
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -1004,20 +1010,20 @@ var StalwartAdmin = class {
1004
1010
  }
1005
1011
  try {
1006
1012
  const net = await import("net");
1007
- return await new Promise((resolve) => {
1013
+ return await new Promise((resolve2) => {
1008
1014
  const smtpPort = parseInt(process.env.SMTP_PORT || "25", 10);
1009
1015
  const smtpHost = process.env.SMTP_HOST || "localhost";
1010
1016
  const socket = net.createConnection({ host: smtpHost, port: smtpPort, timeout: 3e3 }, () => {
1011
1017
  socket.destroy();
1012
- resolve(true);
1018
+ resolve2(true);
1013
1019
  });
1014
1020
  socket.on("error", () => {
1015
1021
  socket.destroy();
1016
- resolve(false);
1022
+ resolve2(false);
1017
1023
  });
1018
1024
  socket.on("timeout", () => {
1019
1025
  socket.destroy();
1020
- resolve(false);
1026
+ resolve2(false);
1021
1027
  });
1022
1028
  });
1023
1029
  } catch {
@@ -1089,8 +1095,8 @@ var StalwartAdmin = class {
1089
1095
  }
1090
1096
  const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
1091
1097
  const { homedir: homedir11 } = await import("os");
1092
- const { join: join12 } = await import("path");
1093
- const configPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
1098
+ const { join: join13 } = await import("path");
1099
+ const configPath = join13(homedir11(), ".agenticmail", "stalwart.toml");
1094
1100
  try {
1095
1101
  let config = readFileSync7(configPath, "utf-8");
1096
1102
  config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${escapeTomlString(domain)}"`);
@@ -1104,14 +1110,14 @@ var StalwartAdmin = class {
1104
1110
  /** Path to the host-side stalwart.toml (mounted read-only into container) */
1105
1111
  get configPath() {
1106
1112
  const { homedir: homedir11 } = __require("os");
1107
- const { join: join12 } = __require("path");
1108
- return join12(homedir11(), ".agenticmail", "stalwart.toml");
1113
+ const { join: join13 } = __require("path");
1114
+ return join13(homedir11(), ".agenticmail", "stalwart.toml");
1109
1115
  }
1110
1116
  /** Path to host-side DKIM key directory */
1111
1117
  get dkimDir() {
1112
1118
  const { homedir: homedir11 } = __require("os");
1113
- const { join: join12 } = __require("path");
1114
- return join12(homedir11(), ".agenticmail");
1119
+ const { join: join13 } = __require("path");
1120
+ return join13(homedir11(), ".agenticmail");
1115
1121
  }
1116
1122
  /**
1117
1123
  * Create/reuse a DKIM signing key for a domain.
@@ -1214,9 +1220,9 @@ var StalwartAdmin = class {
1214
1220
  async configureOutboundRelay(config) {
1215
1221
  const { readFileSync: readFileSync7, writeFileSync: writeFileSync8 } = await import("fs");
1216
1222
  const { homedir: homedir11 } = await import("os");
1217
- const { join: join12 } = await import("path");
1223
+ const { join: join13 } = await import("path");
1218
1224
  const routeName = config.routeName ?? "gmail";
1219
- const tomlPath = join12(homedir11(), ".agenticmail", "stalwart.toml");
1225
+ const tomlPath = join13(homedir11(), ".agenticmail", "stalwart.toml");
1220
1226
  let toml = readFileSync7(tomlPath, "utf-8");
1221
1227
  toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
1222
1228
  toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
@@ -2321,7 +2327,8 @@ var OUTBOUND_TEXT_RULES = [
2321
2327
  var HIGH_RISK_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx", ".env", ".credentials", ".keystore", ".jks", ".p8"]);
2322
2328
  var MEDIUM_RISK_EXTENSIONS = /* @__PURE__ */ new Set([".db", ".sqlite", ".sqlite3", ".sql", ".csv", ".tsv", ".json", ".yml", ".yaml", ".conf", ".config", ".ini"]);
2323
2329
  function stripHtmlTags(html) {
2324
- return html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0?39;/g, "'").replace(/&nbsp;/g, " ").replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
2330
+ const bounded = html.length > 1048576 ? html.slice(0, 1048576) : html;
2331
+ return bounded.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0?39;/g, "'").replace(/&nbsp;/g, " ").replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
2325
2332
  }
2326
2333
  var TEXT_SCANNABLE_TYPES = /* @__PURE__ */ new Set([
2327
2334
  "text/plain",
@@ -3853,7 +3860,7 @@ var CloudflareClient = class {
3853
3860
  // --- Workers methods ---
3854
3861
  /** Deploy an Email Worker script (ES module format) */
3855
3862
  async deployEmailWorker(scriptName, scriptContent, envVars = {}) {
3856
- const url = `${CF_API_BASE}/accounts/${this.accountId}/workers/scripts/${scriptName}`;
3863
+ const url = `${CF_API_BASE}/accounts/${encodeURIComponent(this.accountId)}/workers/scripts/${encodeURIComponent(scriptName)}`;
3857
3864
  const bindings = Object.entries(envVars).map(([name, text2]) => ({
3858
3865
  type: "plain_text",
3859
3866
  name,
@@ -4271,7 +4278,7 @@ var TunnelManager = class {
4271
4278
  detached: false,
4272
4279
  env: { ...process.env, TUNNEL_TOKEN: tunnelToken }
4273
4280
  });
4274
- await new Promise((resolve, reject) => {
4281
+ await new Promise((resolve2, reject) => {
4275
4282
  let resolved = false;
4276
4283
  const timeout = setTimeout(() => {
4277
4284
  if (!resolved) {
@@ -4285,7 +4292,7 @@ var TunnelManager = class {
4285
4292
  resolved = true;
4286
4293
  clearTimeout(timeout);
4287
4294
  this.running = true;
4288
- resolve();
4295
+ resolve2();
4289
4296
  }
4290
4297
  };
4291
4298
  this.process.stderr?.on("data", onData);
@@ -4324,8 +4331,8 @@ var TunnelManager = class {
4324
4331
  this.process = null;
4325
4332
  p.kill("SIGTERM");
4326
4333
  await Promise.race([
4327
- new Promise((resolve) => p.on("exit", () => resolve())),
4328
- new Promise((resolve) => setTimeout(resolve, 5e3))
4334
+ new Promise((resolve2) => p.on("exit", () => resolve2())),
4335
+ new Promise((resolve2) => setTimeout(resolve2, 5e3))
4329
4336
  ]);
4330
4337
  this.running = false;
4331
4338
  }
@@ -4380,7 +4387,10 @@ function parseGoogleVoiceSms(emailBody, emailFrom) {
4380
4387
  if (!emailBody || typeof emailBody !== "string") return null;
4381
4388
  if (!emailFrom || typeof emailFrom !== "string") return null;
4382
4389
  const fromLower = emailFrom.toLowerCase();
4383
- const isGoogleVoice = fromLower.includes("voice-noreply@google.com") || fromLower.includes("@txt.voice.google.com") || fromLower.includes("voice.google.com") || fromLower.includes("google.com/voice") || fromLower.includes("google") && fromLower.includes("voice");
4390
+ const atIdx = fromLower.lastIndexOf("@");
4391
+ const domain = atIdx >= 0 ? fromLower.slice(atIdx + 1).replace(/[>"'\s].*$/, "") : "";
4392
+ const isGoogleDomain = domain === "google.com" || domain.endsWith(".google.com");
4393
+ const isGoogleVoice = isGoogleDomain && (fromLower.startsWith("voice-noreply@") || domain === "txt.voice.google.com" || domain === "voice.google.com" || domain.endsWith(".voice.google.com") || fromLower.includes("voice"));
4384
4394
  if (!isGoogleVoice) return null;
4385
4395
  let text = emailBody.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/div>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").trim();
4386
4396
  let from = "";
@@ -5411,6 +5421,15 @@ var GatewayManager = class {
5411
5421
  bcc: mail.bcc ? Array.isArray(mail.bcc) ? mail.bcc.join(", ") : mail.bcc : void 0,
5412
5422
  subject: mail.subject,
5413
5423
  text: mail.text || void 0,
5424
+ // The `html` field is the literal HTML body of the outbound
5425
+ // mail — by design it is whatever the sender chose to compose.
5426
+ // CodeQL `js/xss` flags this because the value flows from user
5427
+ // input, but nodemailer is the SMTP serializer, not an HTML
5428
+ // renderer; XSS would only occur if the recipient's MUA
5429
+ // executed the body, which is outside our trust boundary.
5430
+ // The outbound-guard (packages/core/src/mail/outbound-guard.ts)
5431
+ // already scores HTML bodies for suspicious patterns at the
5432
+ // pre-send step. lgtm[js/xss]
5414
5433
  html: mail.html || void 0,
5415
5434
  replyTo: mail.replyTo || from,
5416
5435
  inReplyTo: mail.inReplyTo || void 0,
@@ -5772,11 +5791,11 @@ var RelayBridge = class {
5772
5791
  this.options = options;
5773
5792
  }
5774
5793
  async start() {
5775
- return new Promise((resolve, reject) => {
5794
+ return new Promise((resolve2, reject) => {
5776
5795
  this.server = createServer((req, res) => this.handleRequest(req, res));
5777
5796
  this.server.listen(this.options.port, "127.0.0.1", () => {
5778
5797
  console.log(`[RelayBridge] Listening on 127.0.0.1:${this.options.port}`);
5779
- resolve();
5798
+ resolve2();
5780
5799
  });
5781
5800
  this.server.on("error", reject);
5782
5801
  });
@@ -5978,16 +5997,161 @@ try {
5978
5997
  } catch {
5979
5998
  }
5980
5999
 
6000
+ // src/util/safe-path.ts
6001
+ import { isAbsolute, join as join6, resolve, sep } from "path";
6002
+ var PathTraversalError = class extends Error {
6003
+ constructor(baseDir, parts) {
6004
+ super(
6005
+ `path traversal attempt: ${JSON.stringify(parts)} resolves outside ${baseDir}`
6006
+ );
6007
+ this.baseDir = baseDir;
6008
+ this.parts = parts;
6009
+ this.name = "PathTraversalError";
6010
+ }
6011
+ };
6012
+ function safeJoin(baseDir, ...partsAndOpts) {
6013
+ let opts = {};
6014
+ const parts = [];
6015
+ for (const p of partsAndOpts) {
6016
+ if (typeof p === "string") {
6017
+ parts.push(p);
6018
+ } else if (p && typeof p === "object") {
6019
+ opts = p;
6020
+ }
6021
+ }
6022
+ if (!opts.allowAbsolute) {
6023
+ for (const part of parts) {
6024
+ if (isAbsolute(part)) {
6025
+ throw new PathTraversalError(baseDir, parts);
6026
+ }
6027
+ }
6028
+ }
6029
+ const resolvedBase = resolve(baseDir);
6030
+ const resolved = resolve(resolvedBase, ...parts);
6031
+ if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + sep)) {
6032
+ throw new PathTraversalError(baseDir, parts);
6033
+ }
6034
+ return resolved;
6035
+ }
6036
+ function tryJoin(baseDir, ...parts) {
6037
+ try {
6038
+ return safeJoin(baseDir, ...parts);
6039
+ } catch (err) {
6040
+ if (err instanceof PathTraversalError) return null;
6041
+ throw err;
6042
+ }
6043
+ }
6044
+ function assertWithinBase(baseDir, candidate) {
6045
+ const resolvedBase = resolve(baseDir);
6046
+ const resolvedCandidate = resolve(candidate);
6047
+ if (resolvedCandidate !== resolvedBase && !resolvedCandidate.startsWith(resolvedBase + sep)) {
6048
+ throw new PathTraversalError(baseDir, [candidate]);
6049
+ }
6050
+ return resolvedCandidate;
6051
+ }
6052
+
6053
+ // src/util/redact.ts
6054
+ var REDACTED = "***";
6055
+ function redactSecret(value) {
6056
+ if (typeof value !== "string") return REDACTED;
6057
+ if (value.length === 0) return REDACTED;
6058
+ for (const prefix of ["mk_", "ak_", "sk-", "pk_", "tok_"]) {
6059
+ if (value.startsWith(prefix)) return `${prefix}${REDACTED}`;
6060
+ }
6061
+ return REDACTED;
6062
+ }
6063
+ var SENSITIVE_KEY_PATTERNS = [
6064
+ /key$/i,
6065
+ /secret/i,
6066
+ /password/i,
6067
+ /passwd/i,
6068
+ /token/i,
6069
+ /authorization/i,
6070
+ /bearer/i,
6071
+ /\bapikey\b/i,
6072
+ /\bapi_key\b/i,
6073
+ /\bmasterkey\b/i,
6074
+ /\bmaster_key\b/i
6075
+ ];
6076
+ function looksSensitive(key) {
6077
+ return SENSITIVE_KEY_PATTERNS.some((p) => p.test(key));
6078
+ }
6079
+ function redactObject(input, _depth = 0) {
6080
+ if (_depth > 12) return input;
6081
+ if (input === null || input === void 0) return input;
6082
+ if (typeof input !== "object") return input;
6083
+ if (Array.isArray(input)) {
6084
+ return input.map((v) => redactObject(v, _depth + 1));
6085
+ }
6086
+ const proto = Object.getPrototypeOf(input);
6087
+ if (proto !== Object.prototype && proto !== null) return input;
6088
+ const out = {};
6089
+ for (const [k, v] of Object.entries(input)) {
6090
+ if (looksSensitive(k)) {
6091
+ out[k] = typeof v === "string" ? redactSecret(v) : REDACTED;
6092
+ } else {
6093
+ out[k] = redactObject(v, _depth + 1);
6094
+ }
6095
+ }
6096
+ return out;
6097
+ }
6098
+
6099
+ // src/util/safe-url.ts
6100
+ var UnsafeApiUrlError = class extends Error {
6101
+ constructor(raw, reason) {
6102
+ super(`unsafe AgenticMail API URL ${JSON.stringify(raw)}: ${reason}`);
6103
+ this.raw = raw;
6104
+ this.reason = reason;
6105
+ this.name = "UnsafeApiUrlError";
6106
+ }
6107
+ };
6108
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
6109
+ "169.254.169.254",
6110
+ // AWS / Azure / GCP IPv4 metadata
6111
+ "fd00:ec2::254",
6112
+ // AWS IPv6 metadata
6113
+ "metadata.google.internal",
6114
+ // GCP DNS
6115
+ "metadata.azure.internal"
6116
+ // Azure DNS
6117
+ ]);
6118
+ function validateApiUrl(raw) {
6119
+ if (typeof raw !== "string" || raw.length === 0) {
6120
+ throw new UnsafeApiUrlError(String(raw), "must be a non-empty string");
6121
+ }
6122
+ let parsed;
6123
+ try {
6124
+ parsed = new URL(raw);
6125
+ } catch (err) {
6126
+ throw new UnsafeApiUrlError(raw, `unparseable: ${err.message}`);
6127
+ }
6128
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6129
+ throw new UnsafeApiUrlError(raw, `unsupported protocol: ${parsed.protocol}`);
6130
+ }
6131
+ const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
6132
+ if (BLOCKED_HOSTS.has(host)) {
6133
+ throw new UnsafeApiUrlError(raw, `blocked metadata host: ${host}`);
6134
+ }
6135
+ if (parsed.username || parsed.password) {
6136
+ throw new UnsafeApiUrlError(raw, "embedded credentials are not supported");
6137
+ }
6138
+ return parsed.origin;
6139
+ }
6140
+ function buildApiUrl(baseOrigin, pathAndQuery) {
6141
+ const path = pathAndQuery.startsWith("/") ? pathAndQuery : `/${pathAndQuery}`;
6142
+ return new URL(path, baseOrigin + "/").toString();
6143
+ }
6144
+
5981
6145
  // src/setup/index.ts
5982
6146
  import { randomBytes as randomBytes3 } from "crypto";
5983
6147
  import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, chmodSync as chmodSync2 } from "fs";
5984
- import { join as join9 } from "path";
6148
+ import { join as join10 } from "path";
5985
6149
  import { homedir as homedir8 } from "os";
5986
6150
 
5987
6151
  // src/setup/deps.ts
5988
6152
  import { execFileSync } from "child_process";
5989
6153
  import { existsSync as existsSync4 } from "fs";
5990
- import { join as join6 } from "path";
6154
+ import { join as join7 } from "path";
5991
6155
  import { homedir as homedir5 } from "os";
5992
6156
  var DependencyChecker = class {
5993
6157
  async checkAll() {
@@ -6038,7 +6202,7 @@ var DependencyChecker = class {
6038
6202
  }
6039
6203
  }
6040
6204
  async checkCloudflared() {
6041
- const binPath = join6(homedir5(), ".agenticmail", "bin", "cloudflared");
6205
+ const binPath = join7(homedir5(), ".agenticmail", "bin", "cloudflared");
6042
6206
  if (existsSync4(binPath)) {
6043
6207
  let version;
6044
6208
  try {
@@ -6063,12 +6227,12 @@ var DependencyChecker = class {
6063
6227
  import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
6064
6228
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, symlinkSync } from "fs";
6065
6229
  import { writeFile, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
6066
- import { join as join7 } from "path";
6230
+ import { join as join8 } from "path";
6067
6231
  import { homedir as homedir6, platform as platform3, arch as arch2 } from "os";
6068
6232
  function runShellWithRollingOutput(cmd, opts = {}) {
6069
6233
  const maxLines = opts.maxLines ?? 20;
6070
6234
  const timeout = opts.timeout ?? 3e5;
6071
- return new Promise((resolve, reject) => {
6235
+ return new Promise((resolve2, reject) => {
6072
6236
  const child = spawnChild("sh", ["-c", cmd], {
6073
6237
  stdio: ["ignore", "pipe", "pipe"],
6074
6238
  timeout
@@ -6110,7 +6274,7 @@ function runShellWithRollingOutput(cmd, opts = {}) {
6110
6274
  }
6111
6275
  process.stdout.write(`\x1B[${displayedCount}A`);
6112
6276
  }
6113
- resolve({ exitCode: code ?? 1, fullOutput });
6277
+ resolve2({ exitCode: code ?? 1, fullOutput });
6114
6278
  });
6115
6279
  child.on("error", (err) => {
6116
6280
  if (displayedCount > 0) {
@@ -6126,7 +6290,7 @@ function runShellWithRollingOutput(cmd, opts = {}) {
6126
6290
  }
6127
6291
  function runSilent(command, args, opts = {}) {
6128
6292
  const timeout = opts.timeout ?? 3e5;
6129
- return new Promise((resolve, reject) => {
6293
+ return new Promise((resolve2, reject) => {
6130
6294
  const child = spawnChild(command, args, {
6131
6295
  stdio: ["ignore", "pipe", "pipe"],
6132
6296
  timeout
@@ -6138,7 +6302,7 @@ function runSilent(command, args, opts = {}) {
6138
6302
  child.stderr?.on("data", (d) => {
6139
6303
  fullOutput += d.toString();
6140
6304
  });
6141
- child.on("close", (code) => resolve({ exitCode: code ?? 1, fullOutput }));
6305
+ child.on("close", (code) => resolve2({ exitCode: code ?? 1, fullOutput }));
6142
6306
  child.on("error", reject);
6143
6307
  });
6144
6308
  }
@@ -6240,8 +6404,8 @@ var DependencyInstaller = class {
6240
6404
  try {
6241
6405
  const composeBin = execFileSync2("which", ["docker-compose"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6242
6406
  if (!composeBin) return;
6243
- const pluginDir = join7(homedir6(), ".docker", "cli-plugins");
6244
- const pluginPath = join7(pluginDir, "docker-compose");
6407
+ const pluginDir = join8(homedir6(), ".docker", "cli-plugins");
6408
+ const pluginPath = join8(pluginDir, "docker-compose");
6245
6409
  if (existsSync5(pluginPath)) return;
6246
6410
  try {
6247
6411
  mkdirSync4(pluginDir, { recursive: true });
@@ -6307,9 +6471,9 @@ var DependencyInstaller = class {
6307
6471
  return;
6308
6472
  }
6309
6473
  this.onProgress("__progress__:5:Installing Docker Engine...");
6310
- const tmpDir = join7(homedir6(), ".agenticmail", "tmp");
6474
+ const tmpDir = join8(homedir6(), ".agenticmail", "tmp");
6311
6475
  await mkdir2(tmpDir, { recursive: true });
6312
- const scriptPath = join7(tmpDir, "install-docker.sh");
6476
+ const scriptPath = join8(tmpDir, "install-docker.sh");
6313
6477
  const dlResult = await runShellWithRollingOutput(
6314
6478
  `curl -fsSL https://get.docker.com -o "${scriptPath}" && sudo sh "${scriptPath}"`,
6315
6479
  { timeout: 3e5 }
@@ -6377,7 +6541,7 @@ var DependencyInstaller = class {
6377
6541
  }
6378
6542
  try {
6379
6543
  const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
6380
- const dockerExe = join7(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6544
+ const dockerExe = join8(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6381
6545
  if (existsSync5(dockerExe)) {
6382
6546
  execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
6383
6547
  }
@@ -6428,7 +6592,7 @@ var DependencyInstaller = class {
6428
6592
  this.onProgress("__progress__:70:Docker Desktop installed. Starting...");
6429
6593
  try {
6430
6594
  const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
6431
- const dockerExe = join7(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6595
+ const dockerExe = join8(programFiles, "Docker", "Docker", "Docker Desktop.exe");
6432
6596
  if (existsSync5(dockerExe)) {
6433
6597
  execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
6434
6598
  }
@@ -6525,9 +6689,9 @@ var DependencyInstaller = class {
6525
6689
  */
6526
6690
  async installCloudflared() {
6527
6691
  const os = platform3();
6528
- const binDir = join7(homedir6(), ".agenticmail", "bin");
6692
+ const binDir = join8(homedir6(), ".agenticmail", "bin");
6529
6693
  const binName = os === "win32" ? "cloudflared.exe" : "cloudflared";
6530
- const binPath = join7(binDir, binName);
6694
+ const binPath = join8(binDir, binName);
6531
6695
  if (existsSync5(binPath)) {
6532
6696
  return binPath;
6533
6697
  }
@@ -6557,7 +6721,7 @@ var DependencyInstaller = class {
6557
6721
  }
6558
6722
  const buffer = Buffer.from(await response.arrayBuffer());
6559
6723
  if (os === "darwin") {
6560
- const tgzPath = join7(binDir, "cloudflared.tgz");
6724
+ const tgzPath = join8(binDir, "cloudflared.tgz");
6561
6725
  await writeFile(tgzPath, buffer);
6562
6726
  try {
6563
6727
  execFileSync2("tar", ["-xzf", tgzPath, "-C", binDir, "cloudflared"], { timeout: 15e3, stdio: "ignore" });
@@ -6593,7 +6757,7 @@ var DependencyInstaller = class {
6593
6757
  // src/setup/service.ts
6594
6758
  import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
6595
6759
  import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync5, chmodSync } from "fs";
6596
- import { join as join8 } from "path";
6760
+ import { join as join9 } from "path";
6597
6761
  import { homedir as homedir7, platform as platform4 } from "os";
6598
6762
  import { createRequire as createRequire2 } from "module";
6599
6763
  var PLIST_LABEL = "com.agenticmail.server";
@@ -6605,9 +6769,9 @@ var ServiceManager = class {
6605
6769
  */
6606
6770
  getServicePath() {
6607
6771
  if (this.os === "darwin") {
6608
- return join8(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
6772
+ return join9(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
6609
6773
  } else {
6610
- return join8(homedir7(), ".config", "systemd", "user", SYSTEMD_UNIT);
6774
+ return join9(homedir7(), ".config", "systemd", "user", SYSTEMD_UNIT);
6611
6775
  }
6612
6776
  }
6613
6777
  /**
@@ -6645,33 +6809,33 @@ var ServiceManager = class {
6645
6809
  } catch {
6646
6810
  }
6647
6811
  const parentPackages = [
6648
- join8("@agenticmail", "cli"),
6812
+ join9("@agenticmail", "cli"),
6649
6813
  // current scoped package
6650
6814
  "agenticmail"
6651
6815
  // legacy unscoped package
6652
6816
  ];
6653
6817
  const baseDirs = [
6654
6818
  // user-local install
6655
- join8(homedir7(), "node_modules")
6819
+ join9(homedir7(), "node_modules")
6656
6820
  ];
6657
6821
  try {
6658
6822
  const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6659
- baseDirs.push(join8(prefix, "lib", "node_modules"));
6660
- baseDirs.push(join8(prefix, "node_modules"));
6823
+ baseDirs.push(join9(prefix, "lib", "node_modules"));
6824
+ baseDirs.push(join9(prefix, "node_modules"));
6661
6825
  } catch {
6662
6826
  }
6663
6827
  baseDirs.push("/opt/homebrew/lib/node_modules");
6664
6828
  baseDirs.push("/usr/local/lib/node_modules");
6665
6829
  for (const base of baseDirs) {
6666
- const sibling = join8(base, "@agenticmail", "api", "dist", "index.js");
6830
+ const sibling = join9(base, "@agenticmail", "api", "dist", "index.js");
6667
6831
  if (existsSync6(sibling)) return sibling;
6668
6832
  for (const parent of parentPackages) {
6669
- const nested = join8(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
6833
+ const nested = join9(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
6670
6834
  if (existsSync6(nested)) return nested;
6671
6835
  }
6672
6836
  }
6673
- const dataDir = join8(homedir7(), ".agenticmail");
6674
- const entryCache = join8(dataDir, "api-entry.path");
6837
+ const dataDir = join9(homedir7(), ".agenticmail");
6838
+ const entryCache = join9(dataDir, "api-entry.path");
6675
6839
  if (existsSync6(entryCache)) {
6676
6840
  const cached = readFileSync3(entryCache, "utf-8").trim();
6677
6841
  if (cached && existsSync6(cached)) return cached;
@@ -6682,9 +6846,9 @@ var ServiceManager = class {
6682
6846
  * Cache the API entry path so the service can find it later.
6683
6847
  */
6684
6848
  cacheApiEntryPath(entryPath) {
6685
- const dataDir = join8(homedir7(), ".agenticmail");
6849
+ const dataDir = join9(homedir7(), ".agenticmail");
6686
6850
  if (!existsSync6(dataDir)) mkdirSync5(dataDir, { recursive: true });
6687
- writeFileSync4(join8(dataDir, "api-entry.path"), entryPath);
6851
+ writeFileSync4(join9(dataDir, "api-entry.path"), entryPath);
6688
6852
  }
6689
6853
  /**
6690
6854
  * Get the current package version.
@@ -6705,7 +6869,7 @@ var ServiceManager = class {
6705
6869
  }
6706
6870
  try {
6707
6871
  const apiEntry = this.getApiEntryPath();
6708
- const apiPkg = join8(apiEntry, "..", "..", "package.json");
6872
+ const apiPkg = join9(apiEntry, "..", "..", "package.json");
6709
6873
  if (existsSync6(apiPkg)) {
6710
6874
  const pkg = JSON.parse(readFileSync3(apiPkg, "utf-8"));
6711
6875
  if (pkg.version) return pkg.version;
@@ -6713,14 +6877,14 @@ var ServiceManager = class {
6713
6877
  } catch {
6714
6878
  }
6715
6879
  const candidates = [
6716
- join8(homedir7(), "node_modules", "@agenticmail", "cli", "package.json"),
6717
- join8(homedir7(), "node_modules", "agenticmail", "package.json"),
6718
- join8(homedir7(), ".agenticmail", "package-version.json")
6880
+ join9(homedir7(), "node_modules", "@agenticmail", "cli", "package.json"),
6881
+ join9(homedir7(), "node_modules", "agenticmail", "package.json"),
6882
+ join9(homedir7(), ".agenticmail", "package-version.json")
6719
6883
  ];
6720
6884
  try {
6721
6885
  const prefix = execSync2("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
6722
- candidates.push(join8(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
6723
- candidates.push(join8(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6886
+ candidates.push(join9(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
6887
+ candidates.push(join9(prefix, "lib", "node_modules", "agenticmail", "package.json"));
6724
6888
  } catch {
6725
6889
  }
6726
6890
  for (const p of candidates) {
@@ -6739,8 +6903,8 @@ var ServiceManager = class {
6739
6903
  * This ensures AgenticMail doesn't fail on boot when Docker is still loading.
6740
6904
  */
6741
6905
  generateStartScript(nodePath, apiEntry) {
6742
- const scriptPath = join8(homedir7(), ".agenticmail", "bin", "start-server.sh");
6743
- const scriptDir = join8(homedir7(), ".agenticmail", "bin");
6906
+ const scriptPath = join9(homedir7(), ".agenticmail", "bin", "start-server.sh");
6907
+ const scriptDir = join9(homedir7(), ".agenticmail", "bin");
6744
6908
  if (!existsSync6(scriptDir)) mkdirSync5(scriptDir, { recursive: true });
6745
6909
  const script = [
6746
6910
  "#!/bin/bash",
@@ -6806,7 +6970,7 @@ var ServiceManager = class {
6806
6970
  */
6807
6971
  generatePlist(nodePath, apiEntry, configPath) {
6808
6972
  const config = JSON.parse(readFileSync3(configPath, "utf-8"));
6809
- const logDir = join8(homedir7(), ".agenticmail", "logs");
6973
+ const logDir = join9(homedir7(), ".agenticmail", "logs");
6810
6974
  if (!existsSync6(logDir)) mkdirSync5(logDir, { recursive: true });
6811
6975
  const version = this.getVersion();
6812
6976
  const startScript = this.generateStartScript(nodePath, apiEntry);
@@ -6830,7 +6994,7 @@ var ServiceManager = class {
6830
6994
  <key>HOME</key>
6831
6995
  <string>${homedir7()}</string>
6832
6996
  <key>AGENTICMAIL_DATA_DIR</key>
6833
- <string>${config.dataDir || join8(homedir7(), ".agenticmail")}</string>
6997
+ <string>${config.dataDir || join9(homedir7(), ".agenticmail")}</string>
6834
6998
  <key>PATH</key>
6835
6999
  <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
6836
7000
  <key>AGENTICMAIL_SERVICE_VERSION</key>
@@ -6884,7 +7048,7 @@ var ServiceManager = class {
6884
7048
  */
6885
7049
  generateSystemdUnit(nodePath, apiEntry, configPath) {
6886
7050
  const config = JSON.parse(readFileSync3(configPath, "utf-8"));
6887
- const dataDir = config.dataDir || join8(homedir7(), ".agenticmail");
7051
+ const dataDir = config.dataDir || join9(homedir7(), ".agenticmail");
6888
7052
  const version = this.getVersion();
6889
7053
  const startScript = this.generateStartScript(nodePath, apiEntry);
6890
7054
  return `[Unit]
@@ -6914,7 +7078,7 @@ WantedBy=default.target
6914
7078
  * Install the auto-start service.
6915
7079
  */
6916
7080
  install() {
6917
- const configPath = join8(homedir7(), ".agenticmail", "config.json");
7081
+ const configPath = join9(homedir7(), ".agenticmail", "config.json");
6918
7082
  if (!existsSync6(configPath)) {
6919
7083
  return { installed: false, message: "Config not found. Run agenticmail setup first." };
6920
7084
  }
@@ -6927,7 +7091,7 @@ WantedBy=default.target
6927
7091
  }
6928
7092
  const servicePath = this.getServicePath();
6929
7093
  if (this.os === "darwin") {
6930
- const dir = join8(homedir7(), "Library", "LaunchAgents");
7094
+ const dir = join9(homedir7(), "Library", "LaunchAgents");
6931
7095
  if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
6932
7096
  if (existsSync6(servicePath)) {
6933
7097
  try {
@@ -6945,7 +7109,7 @@ WantedBy=default.target
6945
7109
  }
6946
7110
  return { installed: true, message: `Service installed at ${servicePath}` };
6947
7111
  } else if (this.os === "linux") {
6948
- const dir = join8(homedir7(), ".config", "systemd", "user");
7112
+ const dir = join9(homedir7(), ".config", "systemd", "user");
6949
7113
  if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
6950
7114
  const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
6951
7115
  writeFileSync4(servicePath, unit);
@@ -7071,7 +7235,7 @@ WantedBy=default.target
7071
7235
  } catch {
7072
7236
  return { reason: "Service file unreadable" };
7073
7237
  }
7074
- const startScript = join8(homedir7(), ".agenticmail", "bin", "start-server.sh");
7238
+ const startScript = join9(homedir7(), ".agenticmail", "bin", "start-server.sh");
7075
7239
  if (serviceContent.includes(startScript)) {
7076
7240
  if (!existsSync6(startScript)) {
7077
7241
  return { reason: "start-server.sh is missing" };
@@ -7104,7 +7268,7 @@ WantedBy=default.target
7104
7268
  return { reason: `Service version drift (current CLI is v${currentVersion})` };
7105
7269
  }
7106
7270
  }
7107
- const entryCache = join8(homedir7(), ".agenticmail", "api-entry.path");
7271
+ const entryCache = join9(homedir7(), ".agenticmail", "api-entry.path");
7108
7272
  if (existsSync6(entryCache)) {
7109
7273
  try {
7110
7274
  const cached = readFileSync3(entryCache, "utf-8").trim();
@@ -7159,12 +7323,12 @@ var SetupManager = class {
7159
7323
  * falls back to monorepo location.
7160
7324
  */
7161
7325
  getComposePath() {
7162
- const standalonePath = join9(homedir8(), ".agenticmail", "docker-compose.yml");
7326
+ const standalonePath = join10(homedir8(), ".agenticmail", "docker-compose.yml");
7163
7327
  if (existsSync7(standalonePath)) return standalonePath;
7164
7328
  const cwd = process.cwd();
7165
- const candidates = [cwd, join9(cwd, "..")];
7329
+ const candidates = [cwd, join10(cwd, "..")];
7166
7330
  for (const dir of candidates) {
7167
- const p = join9(dir, "docker-compose.yml");
7331
+ const p = join10(dir, "docker-compose.yml");
7168
7332
  if (existsSync7(p)) return p;
7169
7333
  }
7170
7334
  return standalonePath;
@@ -7175,9 +7339,9 @@ var SetupManager = class {
7175
7339
  * Always regenerates Docker files to keep passwords in sync.
7176
7340
  */
7177
7341
  initConfig() {
7178
- const dataDir = join9(homedir8(), ".agenticmail");
7179
- const configPath = join9(dataDir, "config.json");
7180
- const envPath = join9(dataDir, ".env");
7342
+ const dataDir = join10(homedir8(), ".agenticmail");
7343
+ const configPath = join10(dataDir, "config.json");
7344
+ const envPath = join10(dataDir, ".env");
7181
7345
  if (existsSync7(configPath)) {
7182
7346
  try {
7183
7347
  const existing = JSON.parse(readFileSync4(configPath, "utf-8"));
@@ -7229,12 +7393,12 @@ IMAP_PORT=143
7229
7393
  * with the correct admin password from config.
7230
7394
  */
7231
7395
  generateDockerFiles(config) {
7232
- const dataDir = config.dataDir || join9(homedir8(), ".agenticmail");
7396
+ const dataDir = config.dataDir || join10(homedir8(), ".agenticmail");
7233
7397
  if (!existsSync7(dataDir)) {
7234
7398
  mkdirSync6(dataDir, { recursive: true });
7235
7399
  }
7236
7400
  const password = config.stalwart?.adminPassword || "changeme";
7237
- const composePath = join9(dataDir, "docker-compose.yml");
7401
+ const composePath = join10(dataDir, "docker-compose.yml");
7238
7402
  writeFileSync5(composePath, `services:
7239
7403
  stalwart:
7240
7404
  # Pinned to v0.15.5 \u2014 Stalwart 0.16+ moved its config to JSON
@@ -7265,7 +7429,7 @@ volumes:
7265
7429
  stalwart-data:
7266
7430
  `);
7267
7431
  chmodSync2(composePath, 384);
7268
- const tomlPath = join9(dataDir, "stalwart.toml");
7432
+ const tomlPath = join10(dataDir, "stalwart.toml");
7269
7433
  writeFileSync5(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
7270
7434
 
7271
7435
  [server]
@@ -7322,7 +7486,7 @@ secret = "${password}"
7322
7486
  * Check if config has already been initialized.
7323
7487
  */
7324
7488
  isInitialized() {
7325
- const configPath = join9(homedir8(), ".agenticmail", "config.json");
7489
+ const configPath = join10(homedir8(), ".agenticmail", "config.json");
7326
7490
  return existsSync7(configPath);
7327
7491
  }
7328
7492
  };
@@ -7330,7 +7494,7 @@ secret = "${password}"
7330
7494
  // src/threading/thread-id.ts
7331
7495
  import { createHash as createHash2 } from "crypto";
7332
7496
  function stripReplyPrefixes(subject) {
7333
- let s = subject;
7497
+ let s = subject.length > 1e3 ? subject.slice(0, 1e3) : subject;
7334
7498
  for (; ; ) {
7335
7499
  const next = s.replace(/^\s*(?:re|fwd?|fw)\s*(?:\[\d+\])?\s*:\s*/i, "");
7336
7500
  if (next === s) break;
@@ -7350,8 +7514,9 @@ function normalizeSubject(subject) {
7350
7514
  }
7351
7515
  function normalizeAddress(addr) {
7352
7516
  if (!addr) return "(unknown)";
7353
- const m = addr.match(/<([^>]+)>/);
7354
- const raw = m ? m[1] : addr;
7517
+ const bounded = addr.length > 500 ? addr.slice(0, 500) : addr;
7518
+ const m = bounded.match(/<([^>]+)>/);
7519
+ const raw = m ? m[1] : bounded;
7355
7520
  return raw.trim().toLowerCase();
7356
7521
  }
7357
7522
  function threadIdFor(input) {
@@ -7371,8 +7536,8 @@ import {
7371
7536
  renameSync
7372
7537
  } from "fs";
7373
7538
  import { homedir as homedir9 } from "os";
7374
- import { join as join10 } from "path";
7375
- var CACHE_DIR_DEFAULT = join10(homedir9(), ".agenticmail", "thread-cache");
7539
+ import { join as join11 } from "path";
7540
+ var CACHE_DIR_DEFAULT = join11(homedir9(), ".agenticmail", "thread-cache");
7376
7541
  var DEFAULT_K_MESSAGES = 10;
7377
7542
  var DEFAULT_LRU_CAP = 5e3;
7378
7543
  var PREVIEW_MAX_CHARS = 240;
@@ -7390,7 +7555,7 @@ var ThreadCache = class {
7390
7555
  }
7391
7556
  }
7392
7557
  pathFor(threadId) {
7393
- return join10(this.dir, `${threadId}.json`);
7558
+ return join11(this.dir, `${threadId}.json`);
7394
7559
  }
7395
7560
  read(threadId) {
7396
7561
  const p = this.pathFor(threadId);
@@ -7480,7 +7645,7 @@ var ThreadCache = class {
7480
7645
  }
7481
7646
  if (files.length <= this.lruCap) return;
7482
7647
  const stats = files.map((f) => {
7483
- const p = join10(this.dir, f);
7648
+ const p = join11(this.dir, f);
7484
7649
  try {
7485
7650
  return { p, mtime: statSync(p).mtimeMs };
7486
7651
  } catch {
@@ -7519,8 +7684,8 @@ import {
7519
7684
  renameSync as renameSync2
7520
7685
  } from "fs";
7521
7686
  import { homedir as homedir10 } from "os";
7522
- import { join as join11 } from "path";
7523
- var MEMORY_DIR_DEFAULT = join11(homedir10(), ".agenticmail", "agent-memory");
7687
+ import { join as join12 } from "path";
7688
+ var MEMORY_DIR_DEFAULT = join12(homedir10(), ".agenticmail", "agent-memory");
7524
7689
  var AgentMemoryStore = class {
7525
7690
  dir;
7526
7691
  constructor(opts = {}) {
@@ -7531,10 +7696,10 @@ var AgentMemoryStore = class {
7531
7696
  }
7532
7697
  }
7533
7698
  dirFor(agentId) {
7534
- return join11(this.dir, sanitizeId(agentId));
7699
+ return join12(this.dir, sanitizeId(agentId));
7535
7700
  }
7536
7701
  pathFor(agentId, threadId) {
7537
- return join11(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
7702
+ return join12(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
7538
7703
  }
7539
7704
  read(agentId, threadId) {
7540
7705
  const p = this.pathFor(agentId, threadId);
@@ -7635,6 +7800,8 @@ export {
7635
7800
  InboxWatcher,
7636
7801
  MailReceiver,
7637
7802
  MailSender,
7803
+ PathTraversalError,
7804
+ REDACTED,
7638
7805
  RELAY_PRESETS,
7639
7806
  RelayBridge,
7640
7807
  RelayGateway,
@@ -7646,7 +7813,10 @@ export {
7646
7813
  StalwartAdmin,
7647
7814
  ThreadCache,
7648
7815
  TunnelManager,
7816
+ UnsafeApiUrlError,
7649
7817
  WARNING_THRESHOLD,
7818
+ assertWithinBase,
7819
+ buildApiUrl,
7650
7820
  buildInboundSecurityAdvisory,
7651
7821
  classifyEmailRoute,
7652
7822
  closeDatabase,
@@ -7665,12 +7835,17 @@ export {
7665
7835
  parseEmail,
7666
7836
  parseGoogleVoiceSms,
7667
7837
  recordToolCall,
7838
+ redactObject,
7839
+ redactSecret,
7668
7840
  resolveConfig,
7841
+ safeJoin,
7669
7842
  sanitizeEmail,
7670
7843
  saveConfig,
7671
7844
  scanOutboundEmail,
7672
7845
  scoreEmail,
7673
7846
  setTelemetryVersion,
7674
7847
  startRelayBridge,
7675
- threadIdFor
7848
+ threadIdFor,
7849
+ tryJoin,
7850
+ validateApiUrl
7676
7851
  };