@mantiq/mail 0.5.20 → 0.5.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantiq/mail",
3
- "version": "0.5.20",
3
+ "version": "0.5.22",
4
4
  "description": "Transactional email — SMTP, Resend, SendGrid, Mailgun, Postmark, SES, markdown templates",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/Message.ts CHANGED
@@ -64,6 +64,12 @@ export class Message {
64
64
  }
65
65
 
66
66
  setHeader(key: string, value: string): this {
67
+ // Security: reject CRLF characters to prevent email header injection.
68
+ // An attacker could inject \r\n to add arbitrary headers (e.g. BCC, CC)
69
+ // which would allow sending copies of emails to unintended recipients.
70
+ if (/[\r\n]/.test(key) || /[\r\n]/.test(value)) {
71
+ throw new Error('Header key and value must not contain CR or LF characters')
72
+ }
67
73
  this.headers[key] = value
68
74
  return this
69
75
  }
@@ -34,7 +34,14 @@ function markdownToHtml(md: string): string {
34
34
  const buttonMatch = trimmed.match(/^\[button\s+url="([^"]+)"\](.*?)\[\/button\]$/)
35
35
  if (buttonMatch) {
36
36
  if (inList) { html += `</${inList}>`; inList = null }
37
- html += renderButton(buttonMatch[1]!, buttonMatch[2]!)
37
+ // Security: sanitize button URL to block javascript:/data:/vbscript: schemes
38
+ const safeButtonUrl = sanitizeLinkUrl(buttonMatch[1]!)
39
+ if (safeButtonUrl) {
40
+ html += renderButton(safeButtonUrl, buttonMatch[2]!)
41
+ } else {
42
+ // Dangerous scheme — render as plain text, not a clickable button
43
+ html += `<p class="email-text" style="margin:12px 0;line-height:1.65;color:#1a1a1a;">${inlineFormatting(buttonMatch[2]!)}</p>`
44
+ }
38
45
  i++
39
46
  continue
40
47
  }
@@ -122,6 +129,25 @@ function markdownToHtml(md: string): string {
122
129
  return html
123
130
  }
124
131
 
132
+ /**
133
+ * Security: strip links with dangerous URI schemes (javascript:, data:, vbscript:)
134
+ * to prevent XSS in rendered email HTML. Only http:, https:, mailto:, and
135
+ * relative URLs are allowed.
136
+ */
137
+ function sanitizeLinkUrl(url: string): string | null {
138
+ const trimmed = url.trim()
139
+ // Reject dangerous schemes — case-insensitive, ignoring leading whitespace
140
+ const lower = trimmed.toLowerCase()
141
+ if (
142
+ lower.startsWith('javascript:') ||
143
+ lower.startsWith('data:') ||
144
+ lower.startsWith('vbscript:')
145
+ ) {
146
+ return null
147
+ }
148
+ return trimmed
149
+ }
150
+
125
151
  /** Process inline markdown: bold, italic, code, links */
126
152
  function inlineFormatting(text: string): string {
127
153
  return text
@@ -131,6 +157,13 @@ function inlineFormatting(text: string): string {
131
157
  .replace(/\*(.+?)\*/g, '<em>$1</em>')
132
158
  // Inline code
133
159
  .replace(/`([^`]+)`/g, '<code class="email-code" style="background:#f3f4f6;padding:2px 6px;border-radius:3px;font-size:13px;font-family:\'SF Mono\',ui-monospace,Menlo,monospace;color:#10b981;">$1</code>')
134
- // Links
135
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="email-link" style="color:#10b981;text-decoration:underline;">$1</a>')
160
+ // Links — sanitize URL to prevent XSS via javascript:/data:/vbscript: schemes
161
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label: string, href: string) => {
162
+ const safeUrl = sanitizeLinkUrl(href)
163
+ if (!safeUrl) {
164
+ // Security: strip dangerous link, render as plain text only
165
+ return label
166
+ }
167
+ return `<a href="${safeUrl}" class="email-link" style="color:#10b981;text-decoration:underline;">${label}</a>`
168
+ })
136
169
  }