@n8n/utils 1.19.0 → 1.20.1
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/files/sanitize.cjs +3 -0
- package/dist/files/sanitize.d.cts +2 -0
- package/dist/files/sanitize.d.mts +2 -0
- package/dist/files/sanitize.mjs +3 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/sanitize.cjs +67 -0
- package/dist/sanitize.cjs.map +1 -0
- package/dist/sanitize.d.cts +5 -0
- package/dist/sanitize.d.mts +5 -0
- package/dist/sanitize.mjs +61 -0
- package/dist/sanitize.mjs.map +1 -0
- package/dist/scrub-secrets.cjs +44 -0
- package/dist/scrub-secrets.cjs.map +1 -0
- package/dist/scrub-secrets.d.cts +7 -0
- package/dist/scrub-secrets.d.mts +7 -0
- package/dist/scrub-secrets.mjs +41 -0
- package/dist/scrub-secrets.mjs.map +1 -0
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,7 @@ const require_reRankSearchResults = require('./reRankSearchResults.cjs');
|
|
|
7
7
|
const require_sublimeSearch = require('./sublimeSearch.cjs');
|
|
8
8
|
const require_sortByProperty = require('./sortByProperty.cjs');
|
|
9
9
|
const require_truncate = require('./truncate.cjs');
|
|
10
|
+
const require_sanitize = require('./sanitize.cjs');
|
|
10
11
|
|
|
11
12
|
exports.DEFAULT_KEYS = require_sublimeSearch.DEFAULT_KEYS;
|
|
12
13
|
exports.assert = require_assert.assert;
|
|
@@ -14,6 +15,7 @@ exports.createEventBus = require_event_bus.createEventBus;
|
|
|
14
15
|
exports.createEventQueue = require_event_queue.createEventQueue;
|
|
15
16
|
exports.reRankSearchResults = require_reRankSearchResults.reRankSearchResults;
|
|
16
17
|
exports.retry = require_retry.retry;
|
|
18
|
+
exports.sanitizeFilename = require_sanitize.sanitizeFilename;
|
|
17
19
|
exports.smartDecimal = require_smartDecimal.smartDecimal;
|
|
18
20
|
exports.sortByProperty = require_sortByProperty.sortByProperty;
|
|
19
21
|
exports.sublimeSearch = require_sublimeSearch.sublimeSearch;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { t as assert } from "./assert2.cjs";
|
|
2
2
|
import { n as EventBus, r as createEventBus, t as CallbackFn } from "./event-bus2.cjs";
|
|
3
3
|
import { t as createEventQueue } from "./event-queue2.cjs";
|
|
4
|
+
import { t as sanitizeFilename } from "./sanitize.cjs";
|
|
4
5
|
import { t as retry } from "./retry2.cjs";
|
|
5
6
|
import { t as smartDecimal } from "./smartDecimal.cjs";
|
|
6
7
|
import { t as reRankSearchResults } from "./reRankSearchResults.cjs";
|
|
7
8
|
import { n as sublimeSearch, t as DEFAULT_KEYS } from "./sublimeSearch.cjs";
|
|
8
9
|
import { t as sortByProperty } from "./sortByProperty.cjs";
|
|
9
10
|
import { n as truncateBeforeLast, t as truncate } from "./truncate.cjs";
|
|
10
|
-
export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
|
|
11
|
+
export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { t as assert } from "./assert2.mjs";
|
|
2
2
|
import { n as EventBus, r as createEventBus, t as CallbackFn } from "./event-bus2.mjs";
|
|
3
3
|
import { t as createEventQueue } from "./event-queue2.mjs";
|
|
4
|
+
import { t as sanitizeFilename } from "./sanitize.mjs";
|
|
4
5
|
import { t as retry } from "./retry2.mjs";
|
|
5
6
|
import { t as smartDecimal } from "./smartDecimal.mjs";
|
|
6
7
|
import { t as reRankSearchResults } from "./reRankSearchResults.mjs";
|
|
7
8
|
import { n as sublimeSearch, t as DEFAULT_KEYS } from "./sublimeSearch.mjs";
|
|
8
9
|
import { t as sortByProperty } from "./sortByProperty.mjs";
|
|
9
10
|
import { n as truncateBeforeLast, t as truncate } from "./truncate.mjs";
|
|
10
|
-
export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
|
|
11
|
+
export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
|
package/dist/index.mjs
CHANGED
|
@@ -7,5 +7,6 @@ import { t as reRankSearchResults } from "./reRankSearchResults.mjs";
|
|
|
7
7
|
import { n as sublimeSearch, t as DEFAULT_KEYS } from "./sublimeSearch.mjs";
|
|
8
8
|
import { t as sortByProperty } from "./sortByProperty.mjs";
|
|
9
9
|
import { n as truncateBeforeLast, t as truncate } from "./truncate.mjs";
|
|
10
|
+
import { t as sanitizeFilename } from "./sanitize.mjs";
|
|
10
11
|
|
|
11
|
-
export { DEFAULT_KEYS, assert, createEventBus, createEventQueue, reRankSearchResults, retry, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
|
|
12
|
+
export { DEFAULT_KEYS, assert, createEventBus, createEventQueue, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/files/sanitize.ts
|
|
3
|
+
const INVALID_CHARS_REGEX = /[<>:"/\\|?*\u0000-\u001F\u007F-\u009F]/g;
|
|
4
|
+
const ZERO_WIDTH_CHARS_REGEX = /[\u200B-\u200D\u2060\uFEFF]/g;
|
|
5
|
+
const UNICODE_SPACES_REGEX = /[\u00A0\u2000-\u200A]/g;
|
|
6
|
+
const LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\s.]+|[\s.]+$/g;
|
|
7
|
+
const WINDOWS_RESERVED_NAMES = new Set([
|
|
8
|
+
"CON",
|
|
9
|
+
"PRN",
|
|
10
|
+
"AUX",
|
|
11
|
+
"NUL",
|
|
12
|
+
"COM1",
|
|
13
|
+
"COM2",
|
|
14
|
+
"COM3",
|
|
15
|
+
"COM4",
|
|
16
|
+
"COM5",
|
|
17
|
+
"COM6",
|
|
18
|
+
"COM7",
|
|
19
|
+
"COM8",
|
|
20
|
+
"COM9",
|
|
21
|
+
"LPT1",
|
|
22
|
+
"LPT2",
|
|
23
|
+
"LPT3",
|
|
24
|
+
"LPT4",
|
|
25
|
+
"LPT5",
|
|
26
|
+
"LPT6",
|
|
27
|
+
"LPT7",
|
|
28
|
+
"LPT8",
|
|
29
|
+
"LPT9"
|
|
30
|
+
]);
|
|
31
|
+
const DEFAULT_FALLBACK_NAME = "untitled";
|
|
32
|
+
const MAX_FILENAME_LENGTH = 200;
|
|
33
|
+
/**
|
|
34
|
+
* Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems
|
|
35
|
+
*
|
|
36
|
+
* Main features:
|
|
37
|
+
* - Replace invalid characters (e.g. ":" in hello:world)
|
|
38
|
+
* - Handle Windows reserved names
|
|
39
|
+
* - Limit filename length
|
|
40
|
+
* - Normalize Unicode characters
|
|
41
|
+
*
|
|
42
|
+
* @param filename - The filename to sanitize (without extension)
|
|
43
|
+
* @param maxLength - Maximum filename length (default: 200)
|
|
44
|
+
* @returns A sanitized filename (without extension)
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* sanitizeFilename('hello:world') // returns 'hello_world'
|
|
48
|
+
* sanitizeFilename('CON') // returns '_CON'
|
|
49
|
+
* sanitizeFilename('') // returns 'untitled'
|
|
50
|
+
*/
|
|
51
|
+
const sanitizeFilename = (filename, maxLength = MAX_FILENAME_LENGTH) => {
|
|
52
|
+
if (!filename) return DEFAULT_FALLBACK_NAME;
|
|
53
|
+
let baseName = filename.trim().replace(INVALID_CHARS_REGEX, "_").replace(ZERO_WIDTH_CHARS_REGEX, "").replace(UNICODE_SPACES_REGEX, " ").replace(LEADING_TRAILING_DOTS_SPACES_REGEX, "");
|
|
54
|
+
if (!baseName) baseName = DEFAULT_FALLBACK_NAME;
|
|
55
|
+
if (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) baseName = `_${baseName}`;
|
|
56
|
+
if (baseName.length > maxLength) baseName = baseName.slice(0, maxLength);
|
|
57
|
+
return baseName;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
Object.defineProperty(exports, 'sanitizeFilename', {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
get: function () {
|
|
64
|
+
return sanitizeFilename;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=sanitize.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.cjs","names":[],"sources":["../src/files/sanitize.ts"],"sourcesContent":["// Constants definition\n/* eslint-disable no-control-regex */\nconst INVALID_CHARS_REGEX = /[<>:\"/\\\\|?*\\u0000-\\u001F\\u007F-\\u009F]/g;\nconst ZERO_WIDTH_CHARS_REGEX = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst UNICODE_SPACES_REGEX = /[\\u00A0\\u2000-\\u200A]/g;\nconst LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\\s.]+|[\\s.]+$/g;\n/* eslint-enable no-control-regex */\n\nconst WINDOWS_RESERVED_NAMES = new Set([\n\t'CON',\n\t'PRN',\n\t'AUX',\n\t'NUL',\n\t'COM1',\n\t'COM2',\n\t'COM3',\n\t'COM4',\n\t'COM5',\n\t'COM6',\n\t'COM7',\n\t'COM8',\n\t'COM9',\n\t'LPT1',\n\t'LPT2',\n\t'LPT3',\n\t'LPT4',\n\t'LPT5',\n\t'LPT6',\n\t'LPT7',\n\t'LPT8',\n\t'LPT9',\n]);\n\nconst DEFAULT_FALLBACK_NAME = 'untitled';\nconst MAX_FILENAME_LENGTH = 200;\n\n/**\n * Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems\n *\n * Main features:\n * - Replace invalid characters (e.g. \":\" in hello:world)\n * - Handle Windows reserved names\n * - Limit filename length\n * - Normalize Unicode characters\n *\n * @param filename - The filename to sanitize (without extension)\n * @param maxLength - Maximum filename length (default: 200)\n * @returns A sanitized filename (without extension)\n *\n * @example\n * sanitizeFilename('hello:world') // returns 'hello_world'\n * sanitizeFilename('CON') // returns '_CON'\n * sanitizeFilename('') // returns 'untitled'\n */\nexport const sanitizeFilename = (\n\tfilename: string,\n\tmaxLength: number = MAX_FILENAME_LENGTH,\n): string => {\n\t// Input validation\n\tif (!filename) {\n\t\treturn DEFAULT_FALLBACK_NAME;\n\t}\n\n\tlet baseName = filename\n\t\t.trim()\n\t\t.replace(INVALID_CHARS_REGEX, '_')\n\t\t.replace(ZERO_WIDTH_CHARS_REGEX, '')\n\t\t.replace(UNICODE_SPACES_REGEX, ' ')\n\t\t.replace(LEADING_TRAILING_DOTS_SPACES_REGEX, '');\n\n\t// Handle empty or invalid filenames after cleaning\n\tif (!baseName) {\n\t\tbaseName = DEFAULT_FALLBACK_NAME;\n\t}\n\n\t// Handle Windows reserved names\n\tif (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) {\n\t\tbaseName = `_${baseName}`;\n\t}\n\n\t// Truncate if too long\n\tif (baseName.length > maxLength) {\n\t\tbaseName = baseName.slice(0, maxLength);\n\t}\n\n\treturn baseName;\n};\n"],"mappings":";;AAEA,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAC7B,MAAM,qCAAqC;AAG3C,MAAM,yBAAyB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;AAEF,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;AAoB5B,MAAa,oBACZ,UACA,YAAoB,wBACR;AAEZ,KAAI,CAAC,SACJ,QAAO;CAGR,IAAI,WAAW,SACb,MAAM,CACN,QAAQ,qBAAqB,IAAI,CACjC,QAAQ,wBAAwB,GAAG,CACnC,QAAQ,sBAAsB,IAAI,CAClC,QAAQ,oCAAoC,GAAG;AAGjD,KAAI,CAAC,SACJ,YAAW;AAIZ,KAAI,uBAAuB,IAAI,SAAS,aAAa,CAAC,CACrD,YAAW,IAAI;AAIhB,KAAI,SAAS,SAAS,UACrB,YAAW,SAAS,MAAM,GAAG,UAAU;AAGxC,QAAO"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/files/sanitize.ts
|
|
2
|
+
const INVALID_CHARS_REGEX = /[<>:"/\\|?*\u0000-\u001F\u007F-\u009F]/g;
|
|
3
|
+
const ZERO_WIDTH_CHARS_REGEX = /[\u200B-\u200D\u2060\uFEFF]/g;
|
|
4
|
+
const UNICODE_SPACES_REGEX = /[\u00A0\u2000-\u200A]/g;
|
|
5
|
+
const LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\s.]+|[\s.]+$/g;
|
|
6
|
+
const WINDOWS_RESERVED_NAMES = new Set([
|
|
7
|
+
"CON",
|
|
8
|
+
"PRN",
|
|
9
|
+
"AUX",
|
|
10
|
+
"NUL",
|
|
11
|
+
"COM1",
|
|
12
|
+
"COM2",
|
|
13
|
+
"COM3",
|
|
14
|
+
"COM4",
|
|
15
|
+
"COM5",
|
|
16
|
+
"COM6",
|
|
17
|
+
"COM7",
|
|
18
|
+
"COM8",
|
|
19
|
+
"COM9",
|
|
20
|
+
"LPT1",
|
|
21
|
+
"LPT2",
|
|
22
|
+
"LPT3",
|
|
23
|
+
"LPT4",
|
|
24
|
+
"LPT5",
|
|
25
|
+
"LPT6",
|
|
26
|
+
"LPT7",
|
|
27
|
+
"LPT8",
|
|
28
|
+
"LPT9"
|
|
29
|
+
]);
|
|
30
|
+
const DEFAULT_FALLBACK_NAME = "untitled";
|
|
31
|
+
const MAX_FILENAME_LENGTH = 200;
|
|
32
|
+
/**
|
|
33
|
+
* Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems
|
|
34
|
+
*
|
|
35
|
+
* Main features:
|
|
36
|
+
* - Replace invalid characters (e.g. ":" in hello:world)
|
|
37
|
+
* - Handle Windows reserved names
|
|
38
|
+
* - Limit filename length
|
|
39
|
+
* - Normalize Unicode characters
|
|
40
|
+
*
|
|
41
|
+
* @param filename - The filename to sanitize (without extension)
|
|
42
|
+
* @param maxLength - Maximum filename length (default: 200)
|
|
43
|
+
* @returns A sanitized filename (without extension)
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* sanitizeFilename('hello:world') // returns 'hello_world'
|
|
47
|
+
* sanitizeFilename('CON') // returns '_CON'
|
|
48
|
+
* sanitizeFilename('') // returns 'untitled'
|
|
49
|
+
*/
|
|
50
|
+
const sanitizeFilename = (filename, maxLength = MAX_FILENAME_LENGTH) => {
|
|
51
|
+
if (!filename) return DEFAULT_FALLBACK_NAME;
|
|
52
|
+
let baseName = filename.trim().replace(INVALID_CHARS_REGEX, "_").replace(ZERO_WIDTH_CHARS_REGEX, "").replace(UNICODE_SPACES_REGEX, " ").replace(LEADING_TRAILING_DOTS_SPACES_REGEX, "");
|
|
53
|
+
if (!baseName) baseName = DEFAULT_FALLBACK_NAME;
|
|
54
|
+
if (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) baseName = `_${baseName}`;
|
|
55
|
+
if (baseName.length > maxLength) baseName = baseName.slice(0, maxLength);
|
|
56
|
+
return baseName;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { sanitizeFilename as t };
|
|
61
|
+
//# sourceMappingURL=sanitize.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.mjs","names":[],"sources":["../src/files/sanitize.ts"],"sourcesContent":["// Constants definition\n/* eslint-disable no-control-regex */\nconst INVALID_CHARS_REGEX = /[<>:\"/\\\\|?*\\u0000-\\u001F\\u007F-\\u009F]/g;\nconst ZERO_WIDTH_CHARS_REGEX = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst UNICODE_SPACES_REGEX = /[\\u00A0\\u2000-\\u200A]/g;\nconst LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\\s.]+|[\\s.]+$/g;\n/* eslint-enable no-control-regex */\n\nconst WINDOWS_RESERVED_NAMES = new Set([\n\t'CON',\n\t'PRN',\n\t'AUX',\n\t'NUL',\n\t'COM1',\n\t'COM2',\n\t'COM3',\n\t'COM4',\n\t'COM5',\n\t'COM6',\n\t'COM7',\n\t'COM8',\n\t'COM9',\n\t'LPT1',\n\t'LPT2',\n\t'LPT3',\n\t'LPT4',\n\t'LPT5',\n\t'LPT6',\n\t'LPT7',\n\t'LPT8',\n\t'LPT9',\n]);\n\nconst DEFAULT_FALLBACK_NAME = 'untitled';\nconst MAX_FILENAME_LENGTH = 200;\n\n/**\n * Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems\n *\n * Main features:\n * - Replace invalid characters (e.g. \":\" in hello:world)\n * - Handle Windows reserved names\n * - Limit filename length\n * - Normalize Unicode characters\n *\n * @param filename - The filename to sanitize (without extension)\n * @param maxLength - Maximum filename length (default: 200)\n * @returns A sanitized filename (without extension)\n *\n * @example\n * sanitizeFilename('hello:world') // returns 'hello_world'\n * sanitizeFilename('CON') // returns '_CON'\n * sanitizeFilename('') // returns 'untitled'\n */\nexport const sanitizeFilename = (\n\tfilename: string,\n\tmaxLength: number = MAX_FILENAME_LENGTH,\n): string => {\n\t// Input validation\n\tif (!filename) {\n\t\treturn DEFAULT_FALLBACK_NAME;\n\t}\n\n\tlet baseName = filename\n\t\t.trim()\n\t\t.replace(INVALID_CHARS_REGEX, '_')\n\t\t.replace(ZERO_WIDTH_CHARS_REGEX, '')\n\t\t.replace(UNICODE_SPACES_REGEX, ' ')\n\t\t.replace(LEADING_TRAILING_DOTS_SPACES_REGEX, '');\n\n\t// Handle empty or invalid filenames after cleaning\n\tif (!baseName) {\n\t\tbaseName = DEFAULT_FALLBACK_NAME;\n\t}\n\n\t// Handle Windows reserved names\n\tif (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) {\n\t\tbaseName = `_${baseName}`;\n\t}\n\n\t// Truncate if too long\n\tif (baseName.length > maxLength) {\n\t\tbaseName = baseName.slice(0, maxLength);\n\t}\n\n\treturn baseName;\n};\n"],"mappings":";AAEA,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAC7B,MAAM,qCAAqC;AAG3C,MAAM,yBAAyB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;AAEF,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;AAoB5B,MAAa,oBACZ,UACA,YAAoB,wBACR;AAEZ,KAAI,CAAC,SACJ,QAAO;CAGR,IAAI,WAAW,SACb,MAAM,CACN,QAAQ,qBAAqB,IAAI,CACjC,QAAQ,wBAAwB,GAAG,CACnC,QAAQ,sBAAsB,IAAI,CAClC,QAAQ,oCAAoC,GAAG;AAGjD,KAAI,CAAC,SACJ,YAAW;AAIZ,KAAI,uBAAuB,IAAI,SAAS,aAAa,CAAC,CACrD,YAAW,IAAI;AAIhB,KAAI,SAAS,SAAS,UACrB,YAAW,SAAS,MAAM,GAAG,UAAU;AAGxC,QAAO"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/scrub-secrets.ts
|
|
3
|
+
/**
|
|
4
|
+
* Replace common credential patterns in free-form text with `[REDACTED]`.
|
|
5
|
+
*
|
|
6
|
+
* Used before persisting or transmitting user-supplied text (telemetry
|
|
7
|
+
* excerpts, eval report HTML, free-form feedback) where keys/tokens
|
|
8
|
+
* accidentally pasted into prompts or command lines could otherwise leak
|
|
9
|
+
* downstream.
|
|
10
|
+
*
|
|
11
|
+
* Conservative by design: matches well-known prefixed tokens, explicit
|
|
12
|
+
* `key=value` pairs, and quoted JSON/JS-object fields with sensitive
|
|
13
|
+
* names. We don't attempt to redact arbitrary long opaque strings — false
|
|
14
|
+
* positives on file paths, IDs, or base64 payloads would make the output
|
|
15
|
+
* unreadable.
|
|
16
|
+
*/
|
|
17
|
+
const SECRET_KEYS = "password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token";
|
|
18
|
+
const SECRET_VALUE_PATTERNS = [
|
|
19
|
+
/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,
|
|
20
|
+
/\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
21
|
+
/\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
|
|
22
|
+
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,
|
|
23
|
+
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
24
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
25
|
+
/\bxox[abprso]-[A-Za-z0-9-]{10,}/g,
|
|
26
|
+
/\bgh[psoru]_[A-Za-z0-9]{20,}/g,
|
|
27
|
+
/\bgithub_pat_[A-Za-z0-9_]{22,}/g,
|
|
28
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
29
|
+
/(?<=:\/\/)[^\s:/@]+:[^\s:/@]+(?=@)/g,
|
|
30
|
+
new RegExp(`"(?:${SECRET_KEYS})"\\s*:\\s*"(?!\\[(?:redacted|REDACTED)\\]")(?:[^"\\\\\\r\\n]|\\\\.)*"`, "gi"),
|
|
31
|
+
new RegExp(`'(?:${SECRET_KEYS})'\\s*:\\s*'(?!\\[(?:redacted|REDACTED)\\]')(?:[^'\\\\\\r\\n]|\\\\.)*'`, "gi"),
|
|
32
|
+
new RegExp(`\\b(?:${SECRET_KEYS})\\s*[:=]\\s*\\S+`, "gi")
|
|
33
|
+
];
|
|
34
|
+
function scrubSecretsInText(input) {
|
|
35
|
+
let out = input;
|
|
36
|
+
for (const pattern of SECRET_VALUE_PATTERNS) out = out.replace(pattern, "[REDACTED]");
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
exports.SECRET_KEYS = SECRET_KEYS;
|
|
42
|
+
exports.SECRET_VALUE_PATTERNS = SECRET_VALUE_PATTERNS;
|
|
43
|
+
exports.scrubSecretsInText = scrubSecretsInText;
|
|
44
|
+
//# sourceMappingURL=scrub-secrets.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-secrets.cjs","names":["SECRET_VALUE_PATTERNS: readonly RegExp[]"],"sources":["../src/scrub-secrets.ts"],"sourcesContent":["/**\n * Replace common credential patterns in free-form text with `[REDACTED]`.\n *\n * Used before persisting or transmitting user-supplied text (telemetry\n * excerpts, eval report HTML, free-form feedback) where keys/tokens\n * accidentally pasted into prompts or command lines could otherwise leak\n * downstream.\n *\n * Conservative by design: matches well-known prefixed tokens, explicit\n * `key=value` pairs, and quoted JSON/JS-object fields with sensitive\n * names. We don't attempt to redact arbitrary long opaque strings — false\n * positives on file paths, IDs, or base64 payloads would make the output\n * unreadable.\n */\nexport const SECRET_KEYS =\n\t'password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token';\n\nexport const SECRET_VALUE_PATTERNS: readonly RegExp[] = [\n\t// PEM private-key blocks (RSA/EC/DSA/OpenSSH/PGP). Whole block, multiline.\n\t/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\\s\\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,\n\t// JWTs: `eyJ<header>.eyJ<payload>.<signature>` (both leading segments are\n\t// base64url of a `{\"` object, which makes this highly distinctive).\n\t/\\beyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/g,\n\t// Authorization-header substrings: `Bearer <token>`, `Basic <token>`, `Token <token>`\n\t/\\b(?:Bearer|Basic|Token)\\s+[A-Za-z0-9._~+/=-]{12,}/gi,\n\t// OpenAI / Anthropic API keys\n\t/\\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,\n\t// Stripe secret/restricted/publishable keys (`sk_live_…`, `rk_test_…`, …)\n\t/\\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,\n\t// Google API keys\n\t/\\bAIza[0-9A-Za-z_-]{35}\\b/g,\n\t// Slack tokens (xoxb, xoxp, xoxa, xoxr, xoxs, xoxo)\n\t/\\bxox[abprso]-[A-Za-z0-9-]{10,}/g,\n\t// GitHub tokens (ghp, ghs, gho, ghr, ghu)\n\t/\\bgh[psoru]_[A-Za-z0-9]{20,}/g,\n\t// GitHub fine-grained personal access tokens\n\t/\\bgithub_pat_[A-Za-z0-9_]{22,}/g,\n\t// AWS access key id\n\t/\\bAKIA[0-9A-Z]{16}\\b/g,\n\t// Credentials embedded in a URL: `scheme://user:password@` — redact the userinfo.\n\t/(?<=:\\/\\/)[^\\s:/@]+:[^\\s:/@]+(?=@)/g,\n\t// JSON-shaped `\"key\": \"value\"` — matches the quoted field as a whole.\n\t// Run before the loose pattern so nested objects like\n\t// `{\"credentials\": {\"apiKey\": \"...\"}}` don't have the outer key consume\n\t// the inner key on its way to a non-quoted (object) value. The value\n\t// body uses the unrolled JSON-string idiom `(?:[^\"\\\\\\r\\n]|\\\\.)*`: the\n\t// negated class excludes the backslash so a backslash can only be consumed\n\t// by the `\\\\.` escape branch. Keep the two alternatives disjoint (don't\n\t// fold `\\\\` back into the negated class) — that keeps every run of\n\t// backslashes to a single, unambiguous parse, so matching stays fast on any\n\t// input. An escaped quote inside the value (`\"abc\\\"def\"`) still doesn't end\n\t// the match early, via the escape branch. The negative lookahead skips\n\t// values that are already a `[redacted]` / `[REDACTED]` placeholder so this\n\t// stays idempotent when chained behind upstream object-walking redaction\n\t// (e.g. langsmith trace payloads).\n\tnew RegExp(\n\t\t`\"(?:${SECRET_KEYS})\"\\\\s*:\\\\s*\"(?!\\\\[(?:redacted|REDACTED)\\\\]\")(?:[^\"\\\\\\\\\\\\r\\\\n]|\\\\\\\\.)*\"`,\n\t\t'gi',\n\t),\n\t// JS-object-shaped `'key': 'value'`\n\tnew RegExp(\n\t\t`'(?:${SECRET_KEYS})'\\\\s*:\\\\s*'(?!\\\\[(?:redacted|REDACTED)\\\\]')(?:[^'\\\\\\\\\\\\r\\\\n]|\\\\\\\\.)*'`,\n\t\t'gi',\n\t),\n\t// Generic `password=...` / `api_key=...` / `secret=...` style assignments\n\tnew RegExp(`\\\\b(?:${SECRET_KEYS})\\\\s*[:=]\\\\s*\\\\S+`, 'gi'),\n];\n\nexport function scrubSecretsInText(input: string): string {\n\tlet out = input;\n\tfor (const pattern of SECRET_VALUE_PATTERNS) {\n\t\tout = out.replace(pattern, '[REDACTED]');\n\t}\n\treturn out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,MAAa,cACZ;AAED,MAAaA,wBAA2C;CAEvD;CAGA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAeA,IAAI,OACH,OAAO,YAAY,yEACnB,KACA;CAED,IAAI,OACH,OAAO,YAAY,yEACnB,KACA;CAED,IAAI,OAAO,SAAS,YAAY,oBAAoB,KAAK;CACzD;AAED,SAAgB,mBAAmB,OAAuB;CACzD,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,sBACrB,OAAM,IAAI,QAAQ,SAAS,aAAa;AAEzC,QAAO"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/scrub-secrets.d.ts
|
|
2
|
+
declare const SECRET_KEYS = "password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token";
|
|
3
|
+
declare const SECRET_VALUE_PATTERNS: readonly RegExp[];
|
|
4
|
+
declare function scrubSecretsInText(input: string): string;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { SECRET_KEYS, SECRET_VALUE_PATTERNS, scrubSecretsInText };
|
|
7
|
+
//# sourceMappingURL=scrub-secrets.d.cts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/scrub-secrets.d.ts
|
|
2
|
+
declare const SECRET_KEYS = "password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token";
|
|
3
|
+
declare const SECRET_VALUE_PATTERNS: readonly RegExp[];
|
|
4
|
+
declare function scrubSecretsInText(input: string): string;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { SECRET_KEYS, SECRET_VALUE_PATTERNS, scrubSecretsInText };
|
|
7
|
+
//# sourceMappingURL=scrub-secrets.d.mts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/scrub-secrets.ts
|
|
2
|
+
/**
|
|
3
|
+
* Replace common credential patterns in free-form text with `[REDACTED]`.
|
|
4
|
+
*
|
|
5
|
+
* Used before persisting or transmitting user-supplied text (telemetry
|
|
6
|
+
* excerpts, eval report HTML, free-form feedback) where keys/tokens
|
|
7
|
+
* accidentally pasted into prompts or command lines could otherwise leak
|
|
8
|
+
* downstream.
|
|
9
|
+
*
|
|
10
|
+
* Conservative by design: matches well-known prefixed tokens, explicit
|
|
11
|
+
* `key=value` pairs, and quoted JSON/JS-object fields with sensitive
|
|
12
|
+
* names. We don't attempt to redact arbitrary long opaque strings — false
|
|
13
|
+
* positives on file paths, IDs, or base64 payloads would make the output
|
|
14
|
+
* unreadable.
|
|
15
|
+
*/
|
|
16
|
+
const SECRET_KEYS = "password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token";
|
|
17
|
+
const SECRET_VALUE_PATTERNS = [
|
|
18
|
+
/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,
|
|
19
|
+
/\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
20
|
+
/\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
|
|
21
|
+
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,
|
|
22
|
+
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
23
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
24
|
+
/\bxox[abprso]-[A-Za-z0-9-]{10,}/g,
|
|
25
|
+
/\bgh[psoru]_[A-Za-z0-9]{20,}/g,
|
|
26
|
+
/\bgithub_pat_[A-Za-z0-9_]{22,}/g,
|
|
27
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
28
|
+
/(?<=:\/\/)[^\s:/@]+:[^\s:/@]+(?=@)/g,
|
|
29
|
+
new RegExp(`"(?:${SECRET_KEYS})"\\s*:\\s*"(?!\\[(?:redacted|REDACTED)\\]")(?:[^"\\\\\\r\\n]|\\\\.)*"`, "gi"),
|
|
30
|
+
new RegExp(`'(?:${SECRET_KEYS})'\\s*:\\s*'(?!\\[(?:redacted|REDACTED)\\]')(?:[^'\\\\\\r\\n]|\\\\.)*'`, "gi"),
|
|
31
|
+
new RegExp(`\\b(?:${SECRET_KEYS})\\s*[:=]\\s*\\S+`, "gi")
|
|
32
|
+
];
|
|
33
|
+
function scrubSecretsInText(input) {
|
|
34
|
+
let out = input;
|
|
35
|
+
for (const pattern of SECRET_VALUE_PATTERNS) out = out.replace(pattern, "[REDACTED]");
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { SECRET_KEYS, SECRET_VALUE_PATTERNS, scrubSecretsInText };
|
|
41
|
+
//# sourceMappingURL=scrub-secrets.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-secrets.mjs","names":["SECRET_VALUE_PATTERNS: readonly RegExp[]"],"sources":["../src/scrub-secrets.ts"],"sourcesContent":["/**\n * Replace common credential patterns in free-form text with `[REDACTED]`.\n *\n * Used before persisting or transmitting user-supplied text (telemetry\n * excerpts, eval report HTML, free-form feedback) where keys/tokens\n * accidentally pasted into prompts or command lines could otherwise leak\n * downstream.\n *\n * Conservative by design: matches well-known prefixed tokens, explicit\n * `key=value` pairs, and quoted JSON/JS-object fields with sensitive\n * names. We don't attempt to redact arbitrary long opaque strings — false\n * positives on file paths, IDs, or base64 payloads would make the output\n * unreadable.\n */\nexport const SECRET_KEYS =\n\t'password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token';\n\nexport const SECRET_VALUE_PATTERNS: readonly RegExp[] = [\n\t// PEM private-key blocks (RSA/EC/DSA/OpenSSH/PGP). Whole block, multiline.\n\t/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\\s\\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,\n\t// JWTs: `eyJ<header>.eyJ<payload>.<signature>` (both leading segments are\n\t// base64url of a `{\"` object, which makes this highly distinctive).\n\t/\\beyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/g,\n\t// Authorization-header substrings: `Bearer <token>`, `Basic <token>`, `Token <token>`\n\t/\\b(?:Bearer|Basic|Token)\\s+[A-Za-z0-9._~+/=-]{12,}/gi,\n\t// OpenAI / Anthropic API keys\n\t/\\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,\n\t// Stripe secret/restricted/publishable keys (`sk_live_…`, `rk_test_…`, …)\n\t/\\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,\n\t// Google API keys\n\t/\\bAIza[0-9A-Za-z_-]{35}\\b/g,\n\t// Slack tokens (xoxb, xoxp, xoxa, xoxr, xoxs, xoxo)\n\t/\\bxox[abprso]-[A-Za-z0-9-]{10,}/g,\n\t// GitHub tokens (ghp, ghs, gho, ghr, ghu)\n\t/\\bgh[psoru]_[A-Za-z0-9]{20,}/g,\n\t// GitHub fine-grained personal access tokens\n\t/\\bgithub_pat_[A-Za-z0-9_]{22,}/g,\n\t// AWS access key id\n\t/\\bAKIA[0-9A-Z]{16}\\b/g,\n\t// Credentials embedded in a URL: `scheme://user:password@` — redact the userinfo.\n\t/(?<=:\\/\\/)[^\\s:/@]+:[^\\s:/@]+(?=@)/g,\n\t// JSON-shaped `\"key\": \"value\"` — matches the quoted field as a whole.\n\t// Run before the loose pattern so nested objects like\n\t// `{\"credentials\": {\"apiKey\": \"...\"}}` don't have the outer key consume\n\t// the inner key on its way to a non-quoted (object) value. The value\n\t// body uses the unrolled JSON-string idiom `(?:[^\"\\\\\\r\\n]|\\\\.)*`: the\n\t// negated class excludes the backslash so a backslash can only be consumed\n\t// by the `\\\\.` escape branch. Keep the two alternatives disjoint (don't\n\t// fold `\\\\` back into the negated class) — that keeps every run of\n\t// backslashes to a single, unambiguous parse, so matching stays fast on any\n\t// input. An escaped quote inside the value (`\"abc\\\"def\"`) still doesn't end\n\t// the match early, via the escape branch. The negative lookahead skips\n\t// values that are already a `[redacted]` / `[REDACTED]` placeholder so this\n\t// stays idempotent when chained behind upstream object-walking redaction\n\t// (e.g. langsmith trace payloads).\n\tnew RegExp(\n\t\t`\"(?:${SECRET_KEYS})\"\\\\s*:\\\\s*\"(?!\\\\[(?:redacted|REDACTED)\\\\]\")(?:[^\"\\\\\\\\\\\\r\\\\n]|\\\\\\\\.)*\"`,\n\t\t'gi',\n\t),\n\t// JS-object-shaped `'key': 'value'`\n\tnew RegExp(\n\t\t`'(?:${SECRET_KEYS})'\\\\s*:\\\\s*'(?!\\\\[(?:redacted|REDACTED)\\\\]')(?:[^'\\\\\\\\\\\\r\\\\n]|\\\\\\\\.)*'`,\n\t\t'gi',\n\t),\n\t// Generic `password=...` / `api_key=...` / `secret=...` style assignments\n\tnew RegExp(`\\\\b(?:${SECRET_KEYS})\\\\s*[:=]\\\\s*\\\\S+`, 'gi'),\n];\n\nexport function scrubSecretsInText(input: string): string {\n\tlet out = input;\n\tfor (const pattern of SECRET_VALUE_PATTERNS) {\n\t\tout = out.replace(pattern, '[REDACTED]');\n\t}\n\treturn out;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAa,cACZ;AAED,MAAaA,wBAA2C;CAEvD;CAGA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAeA,IAAI,OACH,OAAO,YAAY,yEACnB,KACA;CAED,IAAI,OACH,OAAO,YAAY,yEACnB,KACA;CAED,IAAI,OAAO,SAAS,YAAY,oBAAoB,KAAK;CACzD;AAED,SAAgB,mBAAmB,OAAuB;CACzD,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,sBACrB,OAAM,IAAI,QAAQ,SAAS,aAAa;AAEzC,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n8n/utils",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.20.1",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"LICENSE.md",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"@testing-library/user-event": "^14.6.1",
|
|
33
33
|
"tsdown": "^0.16.5",
|
|
34
34
|
"typescript": "5.9.2",
|
|
35
|
-
"vite": "npm:rolldown-vite@
|
|
35
|
+
"vite": "npm:rolldown-vite@7.1.16",
|
|
36
36
|
"vitest": "^3.1.3",
|
|
37
37
|
"@n8n/eslint-config": "0.0.1",
|
|
38
|
-
"@n8n/
|
|
39
|
-
"@n8n/
|
|
38
|
+
"@n8n/vitest-config": "1.5.1",
|
|
39
|
+
"@n8n/typescript-config": "1.3.1"
|
|
40
40
|
},
|
|
41
41
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
42
42
|
"homepage": "https://n8n.io",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"preview": "vite preview",
|
|
55
55
|
"typecheck": "tsc --noEmit",
|
|
56
56
|
"test": "vitest run",
|
|
57
|
+
"test:unit": "vitest run",
|
|
57
58
|
"test:dev": "vitest --silent=false",
|
|
58
59
|
"lint": "eslint src --quiet",
|
|
59
60
|
"lint:fix": "eslint src --fix",
|