@mantiq/mail 0.5.21 → 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 +1 -1
- package/src/Message.ts +6 -0
- package/src/markdown/MarkdownRenderer.ts +36 -3
package/package.json
CHANGED
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
|
-
|
|
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,
|
|
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
|
}
|