rimecms 0.23.9 → 0.23.11

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.
@@ -87,6 +87,5 @@ export declare const isValidSlug: (str: string) => boolean;
87
87
  * Sanitizes user input by removing dangerous HTML tags while preserving allowed formatting tags,
88
88
  * this doesn't encode/decode HTML entities.
89
89
  * allowed tags : ['strong', 'b', 'em', 'i', 'u', 'br', 'a']
90
- * allowedAttributes : ['href', '_target']
91
90
  */
92
91
  export declare const sanitize: (value?: string) => string;
@@ -1,5 +1,6 @@
1
+ import { Parser } from '@thednp/domparser';
1
2
  import camelCase from 'camelcase';
2
- import sanitizeHtml from 'sanitize-html';
3
+ let parser;
3
4
  /**
4
5
  * Capitalizes the first letter of a string.
5
6
  *
@@ -148,11 +149,12 @@ export const isValidSlug = (str) => /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(str);
148
149
  * Sanitizes user input by removing dangerous HTML tags while preserving allowed formatting tags,
149
150
  * this doesn't encode/decode HTML entities.
150
151
  * allowed tags : ['strong', 'b', 'em', 'i', 'u', 'br', 'a']
151
- * allowedAttributes : ['href', '_target']
152
152
  */
153
153
  export const sanitize = (value) => {
154
154
  if (!value)
155
155
  return value || '';
156
+ if (!parser)
157
+ parser = Parser();
156
158
  const decode = (value) => value
157
159
  .replace(/&/g, '&')
158
160
  .replace(/"/g, '"')
@@ -166,12 +168,78 @@ export const sanitize = (value) => {
166
168
  while (decodedValue.match(/&|"|<|>|'|'|&/)) {
167
169
  decodedValue = decode(decodedValue);
168
170
  }
169
- const sanitized = sanitizeHtml(decodedValue, {
170
- allowedTags: ['strong', 'b', 'em', 'i', 'u', 'br', 'a'],
171
- allowedAttributes: {
172
- a: ['href', '_target']
173
- },
174
- disallowedTagsMode: 'discard'
175
- });
171
+ const { root } = parser.parseFromString(decodedValue);
172
+ const allowedTags = new Set(['strong', 'b', 'em', 'i', 'u', 'br', 'a']);
173
+ const dangerousTags = new Set(['script', 'style', 'iframe', 'object', 'embed', 'svg']);
174
+ const eventHandlers = /^on[a-z]+$/i;
175
+ const dangerousUrls = /^(javascript:|data:text\/html)/i;
176
+ const processNode = (node) => {
177
+ if (!node)
178
+ return '';
179
+ // Handle text nodes
180
+ if (node.nodeName === '#text') {
181
+ return node.nodeValue || '';
182
+ }
183
+ // Handle comment nodes - remove them
184
+ if (node.nodeName === '#comment') {
185
+ return '';
186
+ }
187
+ // Handle element nodes
188
+ if (node.tagName) {
189
+ const tagName = node.tagName.toLowerCase();
190
+ // Remove dangerous tags entirely (including their content)
191
+ if (dangerousTags.has(tagName)) {
192
+ return '';
193
+ }
194
+ // Process children first
195
+ const childContent = node.children ? node.children.map(processNode).join('') : '';
196
+ // If not an allowed tag, return only the child content (strip the tag)
197
+ if (!allowedTags.has(tagName)) {
198
+ return childContent;
199
+ }
200
+ // For allowed tags, filter attributes
201
+ const safeAttributes = [];
202
+ if (node.attributes) {
203
+ for (const [key, value] of Object.entries(node.attributes)) {
204
+ const attrName = key.toLowerCase();
205
+ const attrValue = value;
206
+ // Remove event handlers
207
+ if (eventHandlers.test(attrName)) {
208
+ continue;
209
+ }
210
+ // For 'a' tags, only allow specific attributes
211
+ if (tagName === 'a') {
212
+ if (attrName === 'href') {
213
+ // Remove dangerous URLs
214
+ if (dangerousUrls.test(attrValue)) {
215
+ continue;
216
+ }
217
+ safeAttributes.push(`${attrName}="${attrValue}"`);
218
+ }
219
+ else if (attrName === '_target') {
220
+ safeAttributes.push(`${attrName}="${attrValue}"`);
221
+ }
222
+ // Skip all other attributes for 'a' tags
223
+ continue;
224
+ }
225
+ // For other allowed tags, remove dangerous attributes
226
+ if (attrName === 'href' || attrName === 'src') {
227
+ if (dangerousUrls.test(attrValue)) {
228
+ continue;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ // Build the tag
234
+ const attributeString = safeAttributes.length > 0 ? ' ' + safeAttributes.join(' ') : '';
235
+ // Handle self-closing tags
236
+ if (tagName === 'br') {
237
+ return `<${tagName}${attributeString}>`;
238
+ }
239
+ return `<${tagName}${attributeString}>${childContent}</${tagName}>`;
240
+ }
241
+ return '';
242
+ };
243
+ const sanitized = root.children ? root.children.map(processNode).join('') : '';
176
244
  return decode(sanitized);
177
245
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimecms",
3
- "version": "0.23.9",
3
+ "version": "0.23.11",
4
4
  "homepage": "https://github.com/bienbiendev/rime",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -173,6 +173,7 @@
173
173
  "@libsql/client": "^0.15.15",
174
174
  "@lucide/svelte": "^0.540.0",
175
175
  "@playwright/test": "^1.52.0",
176
+ "@thednp/domparser": "^0.1.6",
176
177
  "@tiptap/core": "3.11.0",
177
178
  "@tiptap/extension-blockquote": "3.11.0",
178
179
  "@tiptap/extension-bold": "^3.11.0",
@@ -213,7 +214,6 @@
213
214
  "polka": "^0.5.2",
214
215
  "qs": "^6.14.0",
215
216
  "runed": "^0.23.4",
216
- "sanitize-html": "^2.17.0",
217
217
  "sharp": "^0.34.0",
218
218
  "sortablejs": "^1.15.6",
219
219
  "svelte-sonner": "^1.0.5"