@resq-systems/security 2.1.0 → 2.1.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/README.md +1 -1
- package/lib/controls/address.d.mts +8 -8
- package/lib/controls/address.d.mts.map +1 -1
- package/lib/controls/address.mjs.map +1 -1
- package/lib/controls/csrf.d.mts +7 -7
- package/lib/controls/csrf.d.mts.map +1 -1
- package/lib/controls/csrf.mjs +5 -2
- package/lib/controls/csrf.mjs.map +1 -1
- package/lib/controls/origin.d.mts +6 -6
- package/lib/controls/origin.d.mts.map +1 -1
- package/lib/controls/origin.mjs +1 -0
- package/lib/controls/origin.mjs.map +1 -1
- package/lib/controls/payload.d.mts +4 -4
- package/lib/controls/payload.d.mts.map +1 -1
- package/lib/controls/payload.mjs.map +1 -1
- package/lib/controls/query.d.mts +8 -8
- package/lib/controls/query.d.mts.map +1 -1
- package/lib/controls/query.mjs +1 -0
- package/lib/controls/query.mjs.map +1 -1
- package/lib/controls/redirect.d.mts +5 -5
- package/lib/controls/redirect.d.mts.map +1 -1
- package/lib/controls/redirect.mjs.map +1 -1
- package/lib/controls/upload.d.mts +7 -7
- package/lib/controls/upload.d.mts.map +1 -1
- package/lib/controls/upload.mjs.map +1 -1
- package/lib/crypto.d.mts +20 -21
- package/lib/crypto.d.mts.map +1 -1
- package/lib/crypto.mjs +3 -1
- package/lib/crypto.mjs.map +1 -1
- package/lib/hash.d.mts +5 -5
- package/lib/hash.d.mts.map +1 -1
- package/lib/hash.mjs +1 -0
- package/lib/hash.mjs.map +1 -1
- package/lib/paths.d.mts +5 -5
- package/lib/paths.d.mts.map +1 -1
- package/lib/paths.mjs +1 -0
- package/lib/paths.mjs.map +1 -1
- package/lib/sanitize.d.mts +36 -37
- package/lib/sanitize.d.mts.map +1 -1
- package/lib/sanitize.mjs.map +1 -1
- package/lib/threats/capec.generated.d.mts +4 -4
- package/lib/threats/capec.generated.d.mts.map +1 -1
- package/lib/threats/capec.generated.mjs.map +1 -1
- package/lib/threats/engine.d.mts +4 -5
- package/lib/threats/engine.d.mts.map +1 -1
- package/lib/threats/engine.mjs +1 -0
- package/lib/threats/engine.mjs.map +1 -1
- package/lib/threats/rules/datastore.d.mts +4 -5
- package/lib/threats/rules/datastore.d.mts.map +1 -1
- package/lib/threats/rules/datastore.mjs.map +1 -1
- package/lib/threats/rules/index.d.mts +5 -5
- package/lib/threats/rules/index.d.mts.map +1 -1
- package/lib/threats/rules/index.mjs.map +1 -1
- package/lib/threats/rules/markup.d.mts +4 -5
- package/lib/threats/rules/markup.d.mts.map +1 -1
- package/lib/threats/rules/markup.mjs +1 -0
- package/lib/threats/rules/markup.mjs.map +1 -1
- package/lib/threats/rules/protocol.d.mts +4 -5
- package/lib/threats/rules/protocol.d.mts.map +1 -1
- package/lib/threats/rules/protocol.mjs.map +1 -1
- package/lib/threats/rules/system.d.mts +4 -5
- package/lib/threats/rules/system.d.mts.map +1 -1
- package/lib/threats/rules/system.mjs.map +1 -1
- package/lib/threats/rules/web.d.mts +5 -6
- package/lib/threats/rules/web.d.mts.map +1 -1
- package/lib/threats/rules/web.mjs +1 -1
- package/lib/threats/rules/web.mjs.map +1 -1
- package/lib/threats/scoring.d.mts +5 -6
- package/lib/threats/scoring.d.mts.map +1 -1
- package/lib/threats/scoring.mjs +1 -0
- package/lib/threats/scoring.mjs.map +1 -1
- package/lib/threats/types.d.mts +18 -18
- package/lib/threats/types.d.mts.map +1 -1
- package/lib/threats/types.mjs.map +1 -1
- package/lib/threats/variants.d.mts +3 -4
- package/lib/threats/variants.d.mts.map +1 -1
- package/lib/threats/variants.mjs.map +1 -1
- package/lib/unicode/confusables.d.mts +5 -5
- package/lib/unicode/confusables.d.mts.map +1 -1
- package/lib/unicode/confusables.mjs +1 -0
- package/lib/unicode/confusables.mjs.map +1 -1
- package/lib/unicode/index.d.mts +11 -11
- package/lib/unicode/index.d.mts.map +1 -1
- package/lib/unicode/index.mjs +1 -0
- package/lib/unicode/index.mjs.map +1 -1
- package/lib/validators.d.mts +89 -39
- package/lib/validators.d.mts.map +1 -1
- package/lib/validators.mjs +285 -21
- package/lib/validators.mjs.map +1 -1
- package/package.json +7 -7
package/lib/hash.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//#region src/hash.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Copyright 2026 ResQ Systems, Inc.
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
5
|
*
|
|
5
6
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
7
|
* you may not use this file except in compliance with the License.
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
* getHashForString("hello") === getHashForString("hello"); // → true
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
|
-
declare function getHashForString(string: string): string;
|
|
41
|
+
export declare function getHashForString(string: string): string;
|
|
41
42
|
/**
|
|
42
43
|
* Hash an arbitrary value by serializing it to JSON and hashing the result.
|
|
43
44
|
*
|
|
@@ -51,7 +52,7 @@ declare function getHashForString(string: string): string;
|
|
|
51
52
|
* that stringifies to `undefined` (a bare function, `symbol`, or
|
|
52
53
|
* `undefined`) makes the downstream `.length` access throw.
|
|
53
54
|
*/
|
|
54
|
-
declare function getHashForObject(obj: unknown): string;
|
|
55
|
+
export declare function getHashForObject(obj: unknown): string;
|
|
55
56
|
/**
|
|
56
57
|
* Compute a deterministic non-cryptographic 32-bit hash of an `ArrayBuffer`.
|
|
57
58
|
*
|
|
@@ -60,7 +61,7 @@ declare function getHashForObject(obj: unknown): string;
|
|
|
60
61
|
* @param buffer - The bytes to hash.
|
|
61
62
|
* @returns The signed 32-bit hash rendered as a decimal string.
|
|
62
63
|
*/
|
|
63
|
-
declare function getHashForBuffer(buffer: ArrayBuffer): string;
|
|
64
|
+
export declare function getHashForBuffer(buffer: ArrayBuffer): string;
|
|
64
65
|
/**
|
|
65
66
|
* Reversibly scramble a string by rotating character blocks and shifting digits.
|
|
66
67
|
*
|
|
@@ -77,7 +78,6 @@ declare function getHashForBuffer(buffer: ArrayBuffer): string;
|
|
|
77
78
|
* @param str - The string to transform.
|
|
78
79
|
* @returns The transformed string, same length as `str`.
|
|
79
80
|
*/
|
|
80
|
-
declare function lns(str: string): string;
|
|
81
|
+
export declare function lns(str: string): string;
|
|
81
82
|
//#endregion
|
|
82
|
-
export { getHashForBuffer, getHashForObject, getHashForString, lns };
|
|
83
83
|
//# sourceMappingURL=hash.d.mts.map
|
package/lib/hash.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.mts","names":[],"sources":["../src/hash.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"hash.d.mts","names":[],"sources":["../src/hash.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAyCgB,iBAAiB;;;;;;;;;;;;;;wBAsBjB,iBAAiB;;;;;;;;;wBAYjB,iBAAiB,QAAQ;;;;;;;;;;;;;;;;;wBA0BzB,IAAI"}
|
package/lib/hash.mjs
CHANGED
package/lib/hash.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.mjs","names":[],"sources":["../src/hash.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview Fast, deterministic, non-cryptographic hashing helpers — a 32-bit\n * string/buffer hash for cache keys and change detection, plus a lightweight string\n * obfuscation transform. Not suitable for security-sensitive digests; use the\n * SHA-256 helper in {@link @resq-systems/security/crypto} for those.\n *\n * @module @resq-systems/security/hash\n */\n\n/**\n * Compute a deterministic non-cryptographic 32-bit hash of a string.\n *\n * Uses the classic `hash * 31 + charCode` accumulation (expressed as\n * `(hash << 5) - hash`), yielding the same value for the same input across runs.\n * Intended for cache keys and change detection, not for security.\n *\n * @param string - The input to hash.\n * @returns The signed 32-bit hash rendered as a decimal string.\n *\n * @example\n * ```ts\n * getHashForString(\"hello\") === getHashForString(\"hello\"); // → true\n * ```\n */\nexport function getHashForString(string: string): string {\n\tlet hash = 0;\n\tfor (let i = 0; i < string.length; i++) {\n\t\thash = (hash << 5) - hash + string.charCodeAt(i);\n\t\thash |= 0; // Coerce the accumulator back to a signed 32-bit integer.\n\t}\n\treturn `${hash}`;\n}\n\n/**\n * Hash an arbitrary value by serializing it to JSON and hashing the result.\n *\n * Key ordering follows `JSON.stringify`, so two structurally equal objects with\n * differently ordered keys can hash differently.\n *\n * @param obj - Any JSON-serializable value.\n * @returns The 32-bit hash of the serialized form, as a decimal string.\n * @throws {TypeError} When `obj` cannot be serialized: a circular\n * reference or a `BigInt` makes `JSON.stringify` throw, and a value\n * that stringifies to `undefined` (a bare function, `symbol`, or\n * `undefined`) makes the downstream `.length` access throw.\n */\nexport function getHashForObject(obj: unknown): string {\n\treturn getHashForString(JSON.stringify(obj));\n}\n\n/**\n * Compute a deterministic non-cryptographic 32-bit hash of an `ArrayBuffer`.\n *\n * Applies the same accumulation as {@link getHashForString} over each byte.\n *\n * @param buffer - The bytes to hash.\n * @returns The signed 32-bit hash rendered as a decimal string.\n */\nexport function getHashForBuffer(buffer: ArrayBuffer): string {\n\tconst view = new DataView(buffer);\n\tlet hash = 0;\n\tfor (let i = 0; i < view.byteLength; i++) {\n\t\thash = (hash << 5) - hash + view.getUint8(i);\n\t\thash |= 0; // Coerce the accumulator back to a signed 32-bit integer.\n\t}\n\treturn `${hash}`;\n}\n\n/**\n * Reversibly scramble a string by rotating character blocks and shifting digits.\n *\n * A lightweight, non-cryptographic obfuscation: it reorders characters via a fixed\n * sequence of block rotations, reverses the result, and maps each digit `d` to\n * `d < 5 ? d + 5 : d > 5 ? d - 5 : d`. Provides obscurity, not security — do not\n * use it to protect secrets.\n *\n * Despite \"scramble\", the transform is **not a true inverse**: the digit map sends\n * both `0` and `5` to `5`, so any input containing those digits cannot be\n * recovered unambiguously. Non-digit characters (including whitespace) are only\n * reordered, never substituted. Deterministic and free of side effects.\n *\n * @param str - The string to transform.\n * @returns The transformed string, same length as `str`.\n */\nexport function lns(str: string): string {\n\tconst result = str.split(\"\");\n\tresult.push(...result.splice(0, Math.round(result.length / 5)));\n\tresult.push(...result.splice(0, Math.round(result.length / 4)));\n\tresult.push(...result.splice(0, Math.round(result.length / 3)));\n\tresult.push(...result.splice(0, Math.round(result.length / 2)));\n\treturn result\n\t\t.reverse()\n\t\t.map((n) => {\n\t\t\tconst num = Number(n);\n\t\t\treturn Number.isNaN(num) || n.trim() === \"\"\n\t\t\t\t? n\n\t\t\t\t: num < 5\n\t\t\t\t\t? String(5 + num)\n\t\t\t\t\t: num > 5\n\t\t\t\t\t\t? String(num - 5)\n\t\t\t\t\t\t: n;\n\t\t})\n\t\t.join(\"\");\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"hash.mjs","names":[],"sources":["../src/hash.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview Fast, deterministic, non-cryptographic hashing helpers — a 32-bit\n * string/buffer hash for cache keys and change detection, plus a lightweight string\n * obfuscation transform. Not suitable for security-sensitive digests; use the\n * SHA-256 helper in {@link @resq-systems/security/crypto} for those.\n *\n * @module @resq-systems/security/hash\n */\n\n/**\n * Compute a deterministic non-cryptographic 32-bit hash of a string.\n *\n * Uses the classic `hash * 31 + charCode` accumulation (expressed as\n * `(hash << 5) - hash`), yielding the same value for the same input across runs.\n * Intended for cache keys and change detection, not for security.\n *\n * @param string - The input to hash.\n * @returns The signed 32-bit hash rendered as a decimal string.\n *\n * @example\n * ```ts\n * getHashForString(\"hello\") === getHashForString(\"hello\"); // → true\n * ```\n */\nexport function getHashForString(string: string): string {\n\tlet hash = 0;\n\tfor (let i = 0; i < string.length; i++) {\n\t\thash = (hash << 5) - hash + string.charCodeAt(i);\n\t\thash |= 0; // Coerce the accumulator back to a signed 32-bit integer.\n\t}\n\treturn `${hash}`;\n}\n\n/**\n * Hash an arbitrary value by serializing it to JSON and hashing the result.\n *\n * Key ordering follows `JSON.stringify`, so two structurally equal objects with\n * differently ordered keys can hash differently.\n *\n * @param obj - Any JSON-serializable value.\n * @returns The 32-bit hash of the serialized form, as a decimal string.\n * @throws {TypeError} When `obj` cannot be serialized: a circular\n * reference or a `BigInt` makes `JSON.stringify` throw, and a value\n * that stringifies to `undefined` (a bare function, `symbol`, or\n * `undefined`) makes the downstream `.length` access throw.\n */\nexport function getHashForObject(obj: unknown): string {\n\treturn getHashForString(JSON.stringify(obj));\n}\n\n/**\n * Compute a deterministic non-cryptographic 32-bit hash of an `ArrayBuffer`.\n *\n * Applies the same accumulation as {@link getHashForString} over each byte.\n *\n * @param buffer - The bytes to hash.\n * @returns The signed 32-bit hash rendered as a decimal string.\n */\nexport function getHashForBuffer(buffer: ArrayBuffer): string {\n\tconst view = new DataView(buffer);\n\tlet hash = 0;\n\tfor (let i = 0; i < view.byteLength; i++) {\n\t\thash = (hash << 5) - hash + view.getUint8(i);\n\t\thash |= 0; // Coerce the accumulator back to a signed 32-bit integer.\n\t}\n\treturn `${hash}`;\n}\n\n/**\n * Reversibly scramble a string by rotating character blocks and shifting digits.\n *\n * A lightweight, non-cryptographic obfuscation: it reorders characters via a fixed\n * sequence of block rotations, reverses the result, and maps each digit `d` to\n * `d < 5 ? d + 5 : d > 5 ? d - 5 : d`. Provides obscurity, not security — do not\n * use it to protect secrets.\n *\n * Despite \"scramble\", the transform is **not a true inverse**: the digit map sends\n * both `0` and `5` to `5`, so any input containing those digits cannot be\n * recovered unambiguously. Non-digit characters (including whitespace) are only\n * reordered, never substituted. Deterministic and free of side effects.\n *\n * @param str - The string to transform.\n * @returns The transformed string, same length as `str`.\n */\nexport function lns(str: string): string {\n\tconst result = str.split(\"\");\n\tresult.push(...result.splice(0, Math.round(result.length / 5)));\n\tresult.push(...result.splice(0, Math.round(result.length / 4)));\n\tresult.push(...result.splice(0, Math.round(result.length / 3)));\n\tresult.push(...result.splice(0, Math.round(result.length / 2)));\n\treturn result\n\t\t.reverse()\n\t\t.map((n) => {\n\t\t\tconst num = Number(n);\n\t\t\treturn Number.isNaN(num) || n.trim() === \"\"\n\t\t\t\t? n\n\t\t\t\t: num < 5\n\t\t\t\t\t? String(5 + num)\n\t\t\t\t\t: num > 5\n\t\t\t\t\t\t? String(num - 5)\n\t\t\t\t\t\t: n;\n\t\t})\n\t\t.join(\"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,iBAAiB,QAAwB;CACxD,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACvC,QAAQ,QAAQ,KAAK,OAAO,OAAO,WAAW,CAAC;EAC/C,QAAQ;CACT;CACA,OAAO,GAAG;AACX;;;;;;;;;;;;;;AAeA,SAAgB,iBAAiB,KAAsB;CACtD,OAAO,iBAAiB,KAAK,UAAU,GAAG,CAAC;AAC5C;;;;;;;;;AAUA,SAAgB,iBAAiB,QAA6B;CAC7D,MAAM,OAAO,IAAI,SAAS,MAAM;CAChC,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,YAAY,KAAK;EACzC,QAAQ,QAAQ,KAAK,OAAO,KAAK,SAAS,CAAC;EAC3C,QAAQ;CACT;CACA,OAAO,GAAG;AACX;;;;;;;;;;;;;;;;;AAkBA,SAAgB,IAAI,KAAqB;CACxC,MAAM,SAAS,IAAI,MAAM,EAAE;CAC3B,OAAO,KAAK,GAAG,OAAO,OAAO,GAAG,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC,CAAC;CAC9D,OAAO,KAAK,GAAG,OAAO,OAAO,GAAG,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC,CAAC;CAC9D,OAAO,KAAK,GAAG,OAAO,OAAO,GAAG,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC,CAAC;CAC9D,OAAO,KAAK,GAAG,OAAO,OAAO,GAAG,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC,CAAC;CAC9D,OAAO,OACL,QAAQ,CAAC,CACT,KAAK,MAAM;EACX,MAAM,MAAM,OAAO,CAAC;EACpB,OAAO,OAAO,MAAM,GAAG,KAAK,EAAE,KAAK,MAAM,KACtC,IACA,MAAM,IACL,OAAO,IAAI,GAAG,IACd,MAAM,IACL,OAAO,MAAM,CAAC,IACd;CACN,CAAC,CAAC,CACD,KAAK,EAAE;AACV"}
|
package/lib/paths.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//#region src/paths.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Copyright 2026 ResQ Systems, Inc.
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
5
|
*
|
|
5
6
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
7
|
* you may not use this file except in compliance with the License.
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
* limitations under the License.
|
|
16
17
|
*/
|
|
17
18
|
/** Options for {@link resolveContainedPath}. */
|
|
18
|
-
interface ContainmentOptions {
|
|
19
|
+
export interface ContainmentOptions {
|
|
19
20
|
/**
|
|
20
21
|
* Treat the base directory itself as an acceptable result. Defaults to `true`.
|
|
21
22
|
* Pass `false` when the caller requires a path strictly *inside* the base — an
|
|
@@ -53,7 +54,7 @@ interface ContainmentOptions {
|
|
|
53
54
|
* resolveContainedPath(base, "/etc/passwd"); // null
|
|
54
55
|
* ```
|
|
55
56
|
*/
|
|
56
|
-
declare function resolveContainedPath(baseDirectory: string, untrustedPath: string, options?: ContainmentOptions): string | null;
|
|
57
|
+
export declare function resolveContainedPath(baseDirectory: string, untrustedPath: string, options?: ContainmentOptions): string | null;
|
|
57
58
|
/**
|
|
58
59
|
* Boolean form of {@link resolveContainedPath}.
|
|
59
60
|
*
|
|
@@ -62,7 +63,7 @@ declare function resolveContainedPath(baseDirectory: string, untrustedPath: stri
|
|
|
62
63
|
* @param options - See {@link ContainmentOptions}.
|
|
63
64
|
* @returns `true` when the candidate resolves inside the base.
|
|
64
65
|
*/
|
|
65
|
-
declare function isPathContained(baseDirectory: string, candidatePath: string, options?: ContainmentOptions): boolean;
|
|
66
|
+
export declare function isPathContained(baseDirectory: string, candidatePath: string, options?: ContainmentOptions): boolean;
|
|
66
67
|
/**
|
|
67
68
|
* Reduce an untrusted string to a single safe path segment.
|
|
68
69
|
*
|
|
@@ -86,7 +87,6 @@ declare function isPathContained(baseDirectory: string, candidatePath: string, o
|
|
|
86
87
|
* sanitizeFilename("CON.txt"); // "file"
|
|
87
88
|
* ```
|
|
88
89
|
*/
|
|
89
|
-
declare function sanitizeFilename(filename: string, fallback?: string): string;
|
|
90
|
+
export declare function sanitizeFilename(filename: string, fallback?: string): string;
|
|
90
91
|
//#endregion
|
|
91
|
-
export { ContainmentOptions, isPathContained, resolveContainedPath, sanitizeFilename };
|
|
92
92
|
//# sourceMappingURL=paths.d.mts.map
|
package/lib/paths.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.mts","names":[],"sources":["../src/paths.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.d.mts","names":[],"sources":["../src/paths.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAuCiB;;;;;;WAMP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAoCM,qBACf,uBACA,uBACA,UAAS;;;;;;;;;wBAuCM,gBACf,uBACA,uBACA,UAAU;;;;;;;;;;;;;;;;;;;;;;;;wBAmDK,iBAAiB,kBAAkB"}
|
package/lib/paths.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
//#region src/paths.ts
|
|
3
3
|
/**
|
|
4
4
|
* Copyright 2026 ResQ Systems, Inc.
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
6
|
*
|
|
6
7
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
8
|
* you may not use this file except in compliance with the License.
|
package/lib/paths.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.mjs","names":[],"sources":["../src/paths.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview Path containment — the *prevention* half of CWE-22, as distinct from\n * the detection rules in `@resq-systems/security/threats`.\n *\n * Detecting `../` is not the control. CWE-22 describes the failure to keep a\n * constructed path inside its restricted directory, so the control is to resolve the\n * candidate against the base and verify the result still lives beneath it. That check\n * holds for inputs no signature catches: an absolute path, a Windows drive-relative\n * path, an alternate separator, or a name whose `..` only appears after normalization.\n *\n * **Node-only.** This module statically imports `node:path` and is therefore published\n * exclusively under the `@resq-systems/security/paths` subpath, never through the\n * package root, so browser bundles of the root barrel stay free of Node builtins.\n *\n * @module @resq-systems/security/paths\n */\n\nimport path from \"node:path\";\n\n//#region Containment\n\n/** Options for {@link resolveContainedPath}. */\nexport interface ContainmentOptions {\n\t/**\n\t * Treat the base directory itself as an acceptable result. Defaults to `true`.\n\t * Pass `false` when the caller requires a path strictly *inside* the base — an\n\t * upload target, say, rather than a listing root.\n\t */\n\treadonly allowBaseItself?: boolean;\n}\n\n/** NUL truncates paths in some syscall layers, so it is rejected before resolution. */\nconst NUL = \"\\u0000\";\n\n/**\n * Resolve an untrusted path against a base directory and verify containment.\n *\n * The candidate is resolved against the base — which also normalizes `.`, `..`, and\n * duplicate separators — and accepted only if the result is the base or sits beneath\n * it. An absolute `untrustedPath` overrides the base under `path.resolve` semantics,\n * so it too is caught by the containment check rather than silently trusted.\n *\n * **This function performs no filesystem I/O**, so it cannot see symlinks. A path that\n * is textually contained may still resolve on disk to a target outside the base. When\n * the target may exist and may be a link, follow up with `fs.promises.realpath` on\n * both the base and the result and re-run {@link isPathContained} on those.\n *\n * @param baseDirectory - Directory the result must stay within. Resolved against the\n * process working directory when relative.\n * @param untrustedPath - Candidate path from an untrusted source.\n * @param options - See {@link ContainmentOptions}.\n * @returns The resolved absolute path when contained, otherwise `null`. Also `null`\n * for non-string arguments, an empty base, or a NUL byte in either argument.\n *\n * @example\n * ```ts\n * const base = \"/srv/uploads\";\n *\n * resolveContainedPath(base, \"avatar.png\"); // \"/srv/uploads/avatar.png\"\n * resolveContainedPath(base, \"nested/a.txt\"); // \"/srv/uploads/nested/a.txt\"\n * resolveContainedPath(base, \"../../etc/passwd\"); // null\n * resolveContainedPath(base, \"/etc/passwd\"); // null\n * ```\n */\nexport function resolveContainedPath(\n\tbaseDirectory: string,\n\tuntrustedPath: string,\n\toptions: ContainmentOptions = {},\n): string | null {\n\tif (typeof baseDirectory !== \"string\" || typeof untrustedPath !== \"string\") {\n\t\treturn null;\n\t}\n\tif (baseDirectory.length === 0) return null;\n\n\t// `a.txt\\0.png` can pass an extension check and then open `a.txt`.\n\tif (untrustedPath.includes(NUL) || baseDirectory.includes(NUL)) {\n\t\treturn null;\n\t}\n\n\tconst { allowBaseItself = true } = options;\n\n\tconst base = path.resolve(baseDirectory);\n\tconst candidate = path.resolve(base, untrustedPath);\n\tconst relative = path.relative(base, candidate);\n\n\tif (relative === \"\") {\n\t\treturn allowBaseItself ? candidate : null;\n\t}\n\n\t// `path.relative` yields a `..`-prefixed path when the candidate escapes, and an\n\t// absolute path when the two sit on different Windows drives.\n\tif (relative === \"..\" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n\t\treturn null;\n\t}\n\n\treturn candidate;\n}\n\n/**\n * Boolean form of {@link resolveContainedPath}.\n *\n * @param baseDirectory - Directory the candidate must stay within.\n * @param candidatePath - Path to test.\n * @param options - See {@link ContainmentOptions}.\n * @returns `true` when the candidate resolves inside the base.\n */\nexport function isPathContained(\n\tbaseDirectory: string,\n\tcandidatePath: string,\n\toptions?: ContainmentOptions,\n): boolean {\n\treturn resolveContainedPath(baseDirectory, candidatePath, options) !== null;\n}\n\n//#endregion\n\n//#region Filename sanitization\n\n/**\n * Characters invalid in a Windows path segment, plus both separators and the C0\n * control range.\n */\n// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are invalid in filenames and must be stripped\nconst UNSAFE_FILENAME_CHARS = /[<>:\"/\\\\|?*\\u0000-\\u001f\\u007f]/g;\n\n/**\n * Windows device names, reserved regardless of extension — opening `CON.txt` still\n * targets the console device.\n */\nconst RESERVED_WINDOWS_NAMES = /^(?:CON|PRN|AUX|NUL|COM\\d|LPT\\d)(?:\\.|$)/i;\n\n/** Longest filename accepted on common filesystems. */\nconst MAX_FILENAME_LENGTH = 255;\n\n/** Longest extension preserved when truncating an over-long filename. */\nconst MAX_PRESERVED_EXTENSION = 16;\n\n/**\n * Reduce an untrusted string to a single safe path segment.\n *\n * Strips directory separators, control characters, and Windows-reserved characters;\n * removes leading dots so the result cannot become `.`/`..` or a hidden file; trims\n * the trailing dots and spaces Windows silently drops (which is how `evil.php.`\n * becomes `evil.php` after the fact); and refuses reserved device names.\n *\n * The output is a *segment*, never a path. Join it onto a base directory yourself and\n * confirm the result with {@link resolveContainedPath} — that remains the containment\n * control; this is only hygiene.\n *\n * @param filename - Untrusted filename, typically from a multipart upload.\n * @param fallback - Returned when nothing usable survives. Defaults to `\"file\"`.\n * @returns A safe single path segment.\n *\n * @example\n * ```ts\n * sanitizeFilename(\"../../etc/passwd\"); // \"etcpasswd\"\n * sanitizeFilename(\"report.pdf.\"); // \"report.pdf\"\n * sanitizeFilename(\"CON.txt\"); // \"file\"\n * ```\n */\nexport function sanitizeFilename(filename: string, fallback = \"file\"): string {\n\tif (typeof filename !== \"string\" || filename.length === 0) return fallback;\n\n\tlet safe = filename\n\t\t.normalize(\"NFC\")\n\t\t.replace(UNSAFE_FILENAME_CHARS, \"\")\n\t\t.replace(/^\\.+/, \"\")\n\t\t.replace(/[. ]+$/, \"\")\n\t\t.trim();\n\n\tif (safe.length === 0) return fallback;\n\tif (RESERVED_WINDOWS_NAMES.test(safe)) return fallback;\n\n\tif (safe.length > MAX_FILENAME_LENGTH) {\n\t\tconst extension = path.extname(safe).slice(0, MAX_PRESERVED_EXTENSION);\n\t\tsafe = safe.slice(0, MAX_FILENAME_LENGTH - extension.length) + extension;\n\t}\n\n\treturn safe;\n}\n\n//#endregion\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.mjs","names":[],"sources":["../src/paths.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n * SPDX-License-Identifier: Apache-2.0\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview Path containment — the *prevention* half of CWE-22, as distinct from\n * the detection rules in `@resq-systems/security/threats`.\n *\n * Detecting `../` is not the control. CWE-22 describes the failure to keep a\n * constructed path inside its restricted directory, so the control is to resolve the\n * candidate against the base and verify the result still lives beneath it. That check\n * holds for inputs no signature catches: an absolute path, a Windows drive-relative\n * path, an alternate separator, or a name whose `..` only appears after normalization.\n *\n * **Node-only.** This module statically imports `node:path` and is therefore published\n * exclusively under the `@resq-systems/security/paths` subpath, never through the\n * package root, so browser bundles of the root barrel stay free of Node builtins.\n *\n * @module @resq-systems/security/paths\n */\n\nimport path from \"node:path\";\n\n//#region Containment\n\n/** Options for {@link resolveContainedPath}. */\nexport interface ContainmentOptions {\n\t/**\n\t * Treat the base directory itself as an acceptable result. Defaults to `true`.\n\t * Pass `false` when the caller requires a path strictly *inside* the base — an\n\t * upload target, say, rather than a listing root.\n\t */\n\treadonly allowBaseItself?: boolean;\n}\n\n/** NUL truncates paths in some syscall layers, so it is rejected before resolution. */\nconst NUL = \"\\u0000\";\n\n/**\n * Resolve an untrusted path against a base directory and verify containment.\n *\n * The candidate is resolved against the base — which also normalizes `.`, `..`, and\n * duplicate separators — and accepted only if the result is the base or sits beneath\n * it. An absolute `untrustedPath` overrides the base under `path.resolve` semantics,\n * so it too is caught by the containment check rather than silently trusted.\n *\n * **This function performs no filesystem I/O**, so it cannot see symlinks. A path that\n * is textually contained may still resolve on disk to a target outside the base. When\n * the target may exist and may be a link, follow up with `fs.promises.realpath` on\n * both the base and the result and re-run {@link isPathContained} on those.\n *\n * @param baseDirectory - Directory the result must stay within. Resolved against the\n * process working directory when relative.\n * @param untrustedPath - Candidate path from an untrusted source.\n * @param options - See {@link ContainmentOptions}.\n * @returns The resolved absolute path when contained, otherwise `null`. Also `null`\n * for non-string arguments, an empty base, or a NUL byte in either argument.\n *\n * @example\n * ```ts\n * const base = \"/srv/uploads\";\n *\n * resolveContainedPath(base, \"avatar.png\"); // \"/srv/uploads/avatar.png\"\n * resolveContainedPath(base, \"nested/a.txt\"); // \"/srv/uploads/nested/a.txt\"\n * resolveContainedPath(base, \"../../etc/passwd\"); // null\n * resolveContainedPath(base, \"/etc/passwd\"); // null\n * ```\n */\nexport function resolveContainedPath(\n\tbaseDirectory: string,\n\tuntrustedPath: string,\n\toptions: ContainmentOptions = {},\n): string | null {\n\tif (typeof baseDirectory !== \"string\" || typeof untrustedPath !== \"string\") {\n\t\treturn null;\n\t}\n\tif (baseDirectory.length === 0) return null;\n\n\t// `a.txt\\0.png` can pass an extension check and then open `a.txt`.\n\tif (untrustedPath.includes(NUL) || baseDirectory.includes(NUL)) {\n\t\treturn null;\n\t}\n\n\tconst { allowBaseItself = true } = options;\n\n\tconst base = path.resolve(baseDirectory);\n\tconst candidate = path.resolve(base, untrustedPath);\n\tconst relative = path.relative(base, candidate);\n\n\tif (relative === \"\") {\n\t\treturn allowBaseItself ? candidate : null;\n\t}\n\n\t// `path.relative` yields a `..`-prefixed path when the candidate escapes, and an\n\t// absolute path when the two sit on different Windows drives.\n\tif (relative === \"..\" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n\t\treturn null;\n\t}\n\n\treturn candidate;\n}\n\n/**\n * Boolean form of {@link resolveContainedPath}.\n *\n * @param baseDirectory - Directory the candidate must stay within.\n * @param candidatePath - Path to test.\n * @param options - See {@link ContainmentOptions}.\n * @returns `true` when the candidate resolves inside the base.\n */\nexport function isPathContained(\n\tbaseDirectory: string,\n\tcandidatePath: string,\n\toptions?: ContainmentOptions,\n): boolean {\n\treturn resolveContainedPath(baseDirectory, candidatePath, options) !== null;\n}\n\n//#endregion\n\n//#region Filename sanitization\n\n/**\n * Characters invalid in a Windows path segment, plus both separators and the C0\n * control range.\n */\n// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are invalid in filenames and must be stripped\nconst UNSAFE_FILENAME_CHARS = /[<>:\"/\\\\|?*\\u0000-\\u001f\\u007f]/g;\n\n/**\n * Windows device names, reserved regardless of extension — opening `CON.txt` still\n * targets the console device.\n */\nconst RESERVED_WINDOWS_NAMES = /^(?:CON|PRN|AUX|NUL|COM\\d|LPT\\d)(?:\\.|$)/i;\n\n/** Longest filename accepted on common filesystems. */\nconst MAX_FILENAME_LENGTH = 255;\n\n/** Longest extension preserved when truncating an over-long filename. */\nconst MAX_PRESERVED_EXTENSION = 16;\n\n/**\n * Reduce an untrusted string to a single safe path segment.\n *\n * Strips directory separators, control characters, and Windows-reserved characters;\n * removes leading dots so the result cannot become `.`/`..` or a hidden file; trims\n * the trailing dots and spaces Windows silently drops (which is how `evil.php.`\n * becomes `evil.php` after the fact); and refuses reserved device names.\n *\n * The output is a *segment*, never a path. Join it onto a base directory yourself and\n * confirm the result with {@link resolveContainedPath} — that remains the containment\n * control; this is only hygiene.\n *\n * @param filename - Untrusted filename, typically from a multipart upload.\n * @param fallback - Returned when nothing usable survives. Defaults to `\"file\"`.\n * @returns A safe single path segment.\n *\n * @example\n * ```ts\n * sanitizeFilename(\"../../etc/passwd\"); // \"etcpasswd\"\n * sanitizeFilename(\"report.pdf.\"); // \"report.pdf\"\n * sanitizeFilename(\"CON.txt\"); // \"file\"\n * ```\n */\nexport function sanitizeFilename(filename: string, fallback = \"file\"): string {\n\tif (typeof filename !== \"string\" || filename.length === 0) return fallback;\n\n\tlet safe = filename\n\t\t.normalize(\"NFC\")\n\t\t.replace(UNSAFE_FILENAME_CHARS, \"\")\n\t\t.replace(/^\\.+/, \"\")\n\t\t.replace(/[. ]+$/, \"\")\n\t\t.trim();\n\n\tif (safe.length === 0) return fallback;\n\tif (RESERVED_WINDOWS_NAMES.test(safe)) return fallback;\n\n\tif (safe.length > MAX_FILENAME_LENGTH) {\n\t\tconst extension = path.extname(safe).slice(0, MAX_PRESERVED_EXTENSION);\n\t\tsafe = safe.slice(0, MAX_FILENAME_LENGTH - extension.length) + extension;\n\t}\n\n\treturn safe;\n}\n\n//#endregion\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCZ,SAAgB,qBACf,eACA,eACA,UAA8B,CAAC,GACf;CAChB,IAAI,OAAO,kBAAkB,YAAY,OAAO,kBAAkB,UACjE,OAAO;CAER,IAAI,cAAc,WAAW,GAAG,OAAO;CAGvC,IAAI,cAAc,SAAS,GAAG,KAAK,cAAc,SAAS,GAAG,GAC5D,OAAO;CAGR,MAAM,EAAE,kBAAkB,SAAS;CAEnC,MAAM,OAAO,KAAK,QAAQ,aAAa;CACvC,MAAM,YAAY,KAAK,QAAQ,MAAM,aAAa;CAClD,MAAM,WAAW,KAAK,SAAS,MAAM,SAAS;CAE9C,IAAI,aAAa,IAChB,OAAO,kBAAkB,YAAY;CAKtC,IAAI,aAAa,QAAQ,SAAS,WAAW,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW,QAAQ,GACxF,OAAO;CAGR,OAAO;AACR;;;;;;;;;AAUA,SAAgB,gBACf,eACA,eACA,SACU;CACV,OAAO,qBAAqB,eAAe,eAAe,OAAO,MAAM;AACxE;;;;;AAWA,MAAM,wBAAwB;;;;;AAM9B,MAAM,yBAAyB;;AAG/B,MAAM,sBAAsB;;AAG5B,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;AAyBhC,SAAgB,iBAAiB,UAAkB,WAAW,QAAgB;CAC7E,IAAI,OAAO,aAAa,YAAY,SAAS,WAAW,GAAG,OAAO;CAElE,IAAI,OAAO,SACT,UAAU,KAAK,CAAC,CAChB,QAAQ,uBAAuB,EAAE,CAAC,CAClC,QAAQ,QAAQ,EAAE,CAAC,CACnB,QAAQ,UAAU,EAAE,CAAC,CACrB,KAAK;CAEP,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,IAAI,uBAAuB,KAAK,IAAI,GAAG,OAAO;CAE9C,IAAI,KAAK,SAAS,qBAAqB;EACtC,MAAM,YAAY,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,uBAAuB;EACrE,OAAO,KAAK,MAAM,GAAG,sBAAsB,UAAU,MAAM,IAAI;CAChE;CAEA,OAAO;AACR"}
|
package/lib/sanitize.d.mts
CHANGED
|
@@ -11,14 +11,14 @@ type SyncSchema<T> = Schema.Codec<T, unknown, never>;
|
|
|
11
11
|
* Schema constraining a URL protocol to the recognized safe set.
|
|
12
12
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
13
13
|
*/
|
|
14
|
-
declare const UrlProtocolSchema: Schema.Literals<readonly ["http:", "https:", "mailto:", "tel:", "ftp:"]>;
|
|
14
|
+
export declare const UrlProtocolSchema: Schema.Literals<readonly ["http:", "https:", "mailto:", "tel:", "ftp:"]>;
|
|
15
15
|
/** One of the protocols accepted by {@link UrlProtocolSchema}. */
|
|
16
|
-
type UrlProtocol = typeof UrlProtocolSchema.Type;
|
|
16
|
+
export type UrlProtocol = typeof UrlProtocolSchema.Type;
|
|
17
17
|
/**
|
|
18
18
|
* Schema for the per-category toggles that drive {@link redactPII}.
|
|
19
19
|
* @compliance NIST 800-53 AU-3 (Content of Audit Records)
|
|
20
20
|
*/
|
|
21
|
-
declare const PIIRedactionOptionsSchema: Schema.Struct<{
|
|
21
|
+
export declare const PIIRedactionOptionsSchema: Schema.Struct<{
|
|
22
22
|
readonly redactEmails: Schema.optional<Schema.Boolean>;
|
|
23
23
|
readonly redactPhones: Schema.optional<Schema.Boolean>;
|
|
24
24
|
readonly redactSSN: Schema.optional<Schema.Boolean>;
|
|
@@ -27,20 +27,20 @@ declare const PIIRedactionOptionsSchema: Schema.Struct<{
|
|
|
27
27
|
readonly redactDates: Schema.optional<Schema.Boolean>;
|
|
28
28
|
}>;
|
|
29
29
|
/** Decoded options accepted by {@link redactPIIEffect} / {@link redactPII}. */
|
|
30
|
-
type PIIRedactionOptions = typeof PIIRedactionOptionsSchema.Type;
|
|
30
|
+
export type PIIRedactionOptions = typeof PIIRedactionOptionsSchema.Type;
|
|
31
31
|
/**
|
|
32
32
|
* Schema for the options controlling {@link validateUserInputEffect}.
|
|
33
33
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
34
34
|
*/
|
|
35
|
-
declare const UserInputOptionsSchema: Schema.Struct<{
|
|
35
|
+
export declare const UserInputOptionsSchema: Schema.Struct<{
|
|
36
36
|
readonly maxLength: Schema.optional<Schema.Int>;
|
|
37
37
|
readonly allowHtml: Schema.optional<Schema.Boolean>;
|
|
38
38
|
readonly allowNewlines: Schema.optional<Schema.Boolean>;
|
|
39
39
|
readonly trimWhitespace: Schema.optional<Schema.Boolean>;
|
|
40
40
|
}>;
|
|
41
41
|
/** Decoded options accepted by {@link validateUserInputEffect}. */
|
|
42
|
-
type UserInputOptions = typeof UserInputOptionsSchema.Type;
|
|
43
|
-
declare const SafeUrlSchema: Schema.String;
|
|
42
|
+
export type UserInputOptions = typeof UserInputOptionsSchema.Type;
|
|
43
|
+
export declare const SafeUrlSchema: Schema.String;
|
|
44
44
|
/**
|
|
45
45
|
* A URL string vouched safe against scheme-based injection. Mint one by
|
|
46
46
|
* narrowing through the {@link isValidUrl} type guard (backed by
|
|
@@ -52,13 +52,13 @@ declare const SafeUrlSchema: Schema.String;
|
|
|
52
52
|
* It does **not** guarantee the host is reachable or trusted — for that, validate the
|
|
53
53
|
* resolved origin with `isAllowedOrigin` from `@resq-systems/security/controls`.
|
|
54
54
|
*/
|
|
55
|
-
type SafeUrl = Brand<string, "SafeUrl">;
|
|
55
|
+
export type SafeUrl = Brand<string, "SafeUrl">;
|
|
56
56
|
/**
|
|
57
57
|
* Schema for a sanitized HTML-safe string — validates the value is a string; the
|
|
58
58
|
* actual escaping is applied at runtime by the sanitization helpers.
|
|
59
59
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
60
60
|
*/
|
|
61
|
-
declare const SanitizedStringSchema: Schema.String;
|
|
61
|
+
export declare const SanitizedStringSchema: Schema.String;
|
|
62
62
|
/**
|
|
63
63
|
* A string carrying the {@link SanitizedStringSchema} contract. The schema is
|
|
64
64
|
* `S.String` alone, so decoding asserts only that the value is a string —
|
|
@@ -66,7 +66,7 @@ declare const SanitizedStringSchema: Schema.String;
|
|
|
66
66
|
* (e.g. {@link escapeHtml}). The type name signals intent, not a proof of
|
|
67
67
|
* escaping.
|
|
68
68
|
*/
|
|
69
|
-
type SanitizedString = typeof SanitizedStringSchema.Type;
|
|
69
|
+
export type SanitizedString = typeof SanitizedStringSchema.Type;
|
|
70
70
|
/**
|
|
71
71
|
* Schema for email address validation.
|
|
72
72
|
*
|
|
@@ -75,43 +75,43 @@ type SanitizedString = typeof SanitizedStringSchema.Type;
|
|
|
75
75
|
* sync with `@resq-systems/email-templates`'s `EmailAddress` brand.
|
|
76
76
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
77
77
|
*/
|
|
78
|
-
declare const EmailSchema: Schema.String;
|
|
78
|
+
export declare const EmailSchema: Schema.String;
|
|
79
79
|
/**
|
|
80
80
|
* An email address that matches {@link EmailSchema}. Mint one by narrowing
|
|
81
81
|
* through the {@link isValidEmail} type guard. The brand guarantees only
|
|
82
82
|
* syntactic well-formedness (including IDN/Punycode TLDs) — not that the
|
|
83
83
|
* mailbox exists or is deliverable.
|
|
84
84
|
*/
|
|
85
|
-
type Email = Brand<string, "Email">;
|
|
85
|
+
export type Email = Brand<string, "Email">;
|
|
86
86
|
/**
|
|
87
87
|
* Schema for phone number validation (US format).
|
|
88
88
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
89
89
|
*/
|
|
90
|
-
declare const PhoneNumberSchema: Schema.String;
|
|
90
|
+
export declare const PhoneNumberSchema: Schema.String;
|
|
91
91
|
/**
|
|
92
92
|
* A US-format phone number matching {@link PhoneNumberSchema}. Mint one by
|
|
93
93
|
* narrowing through the {@link isValidPhone} type guard. The brand asserts
|
|
94
94
|
* the digit/separator shape only; it neither normalizes formatting nor
|
|
95
95
|
* confirms the number is assigned.
|
|
96
96
|
*/
|
|
97
|
-
type PhoneNumber = Brand<string, "PhoneNumber">;
|
|
97
|
+
export type PhoneNumber = Brand<string, "PhoneNumber">;
|
|
98
98
|
/**
|
|
99
99
|
* Schema for SSN validation (US format).
|
|
100
100
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
101
101
|
*/
|
|
102
|
-
declare const SSNSchema: Schema.String;
|
|
102
|
+
export declare const SSNSchema: Schema.String;
|
|
103
103
|
/**
|
|
104
104
|
* A US Social Security Number matching {@link SSNSchema}. Mint one by
|
|
105
105
|
* narrowing through the {@link isValidSSN} type guard. The brand asserts
|
|
106
106
|
* the `NNN-NN-NNNN` shape only — it does not validate area/group ranges or
|
|
107
107
|
* confirm the number was ever issued. Treat any value as sensitive PII.
|
|
108
108
|
*/
|
|
109
|
-
type SSN = Brand<string, "SSN">;
|
|
109
|
+
export type SSN = Brand<string, "SSN">;
|
|
110
110
|
/**
|
|
111
111
|
* Schema for credit card number validation.
|
|
112
112
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
113
113
|
*/
|
|
114
|
-
declare const CreditCardSchema: Schema.String;
|
|
114
|
+
export declare const CreditCardSchema: Schema.String;
|
|
115
115
|
/**
|
|
116
116
|
* A card number matching the {@link CreditCardSchema} pattern (13–16 digits
|
|
117
117
|
* with optional group separators). No exported type guard mints this brand;
|
|
@@ -119,18 +119,18 @@ declare const CreditCardSchema: Schema.String;
|
|
|
119
119
|
* shape check only — it performs **no** Luhn checksum and does not identify
|
|
120
120
|
* the issuer. Treat any value as sensitive PII.
|
|
121
121
|
*/
|
|
122
|
-
type CreditCard = Brand<string, "CreditCard">;
|
|
122
|
+
export type CreditCard = Brand<string, "CreditCard">;
|
|
123
123
|
/**
|
|
124
124
|
* Schema for IPv4 address validation.
|
|
125
125
|
*/
|
|
126
|
-
declare const IPv4Schema: Schema.String;
|
|
126
|
+
export declare const IPv4Schema: Schema.String;
|
|
127
127
|
/**
|
|
128
128
|
* A dotted-quad string matching {@link IPv4Schema}. No exported type guard
|
|
129
129
|
* mints this brand; decode {@link IPv4Schema} directly. The pattern checks
|
|
130
130
|
* four dot-separated groups of 1–3 digits only — it does **not** bound each
|
|
131
131
|
* octet to `0–255`, so `999.0.0.1` still matches.
|
|
132
132
|
*/
|
|
133
|
-
type IPv4 = Brand<string, "IPv4">;
|
|
133
|
+
export type IPv4 = Brand<string, "IPv4">;
|
|
134
134
|
/**
|
|
135
135
|
* Escapes special HTML characters in a string to their corresponding HTML entities,
|
|
136
136
|
* preventing direct injection of HTML and JavaScript when rendering untrusted content.
|
|
@@ -145,7 +145,7 @@ type IPv4 = Brand<string, "IPv4">;
|
|
|
145
145
|
* // "<script>alert("xss")</script>"
|
|
146
146
|
* ```
|
|
147
147
|
*/
|
|
148
|
-
declare const escapeHtml: (text: string) => string;
|
|
148
|
+
export declare const escapeHtml: (text: string) => string;
|
|
149
149
|
/**
|
|
150
150
|
* Validates and sanitizes a user-supplied URL using Effect Schema.
|
|
151
151
|
* Returns an Exit with the sanitized URL or an error.
|
|
@@ -171,7 +171,7 @@ declare const escapeHtml: (text: string) => string;
|
|
|
171
171
|
* // Exit.fail(...)
|
|
172
172
|
* ```
|
|
173
173
|
*/
|
|
174
|
-
declare const sanitizeUrlEffect: (url: string, allowedProtocols?: readonly UrlProtocol[]) => Exit.Exit<string, Schema.SchemaError>;
|
|
174
|
+
export declare const sanitizeUrlEffect: (url: string, allowedProtocols?: readonly UrlProtocol[]) => Exit.Exit<string, Schema.SchemaError>;
|
|
175
175
|
/**
|
|
176
176
|
* Validates and sanitizes a user-supplied URL, ensuring it conforms to allowed protocols
|
|
177
177
|
* and is not a vector for injection attacks like `javascript:` or `data:`.
|
|
@@ -187,7 +187,7 @@ declare const sanitizeUrlEffect: (url: string, allowedProtocols?: readonly UrlPr
|
|
|
187
187
|
* sanitizeUrl('javascript:alert(1)'); // ''
|
|
188
188
|
* ```
|
|
189
189
|
*/
|
|
190
|
-
declare const sanitizeUrl: (url: string, allowedProtocols?: readonly UrlProtocol[]) => string;
|
|
190
|
+
export declare const sanitizeUrl: (url: string, allowedProtocols?: readonly UrlProtocol[]) => string;
|
|
191
191
|
/**
|
|
192
192
|
* Sanitizes HTML to prevent XSS attacks.
|
|
193
193
|
* Uses DOMPurify under the hood. If DOM is not available (e.g. server-side without JSDOM),
|
|
@@ -207,7 +207,7 @@ declare const sanitizeUrl: (url: string, allowedProtocols?: readonly UrlProtocol
|
|
|
207
207
|
* @returns The sanitized HTML string, or the escaped string when no DOM is
|
|
208
208
|
* available.
|
|
209
209
|
*/
|
|
210
|
-
declare const sanitizeHtml: (html: string, options?: Config) => string;
|
|
210
|
+
export declare const sanitizeHtml: (html: string, options?: Config) => string;
|
|
211
211
|
/**
|
|
212
212
|
* Validates user input using Effect Schema and returns an Exit.
|
|
213
213
|
*
|
|
@@ -224,7 +224,7 @@ declare const sanitizeHtml: (html: string, options?: Config) => string;
|
|
|
224
224
|
* to `maxLength`; failure carries a {@link S.SchemaError}.
|
|
225
225
|
* @compliance NIST 800-53 SI-10 (Information Input Validation)
|
|
226
226
|
*/
|
|
227
|
-
declare const validateUserInputEffect: (input: string, options?: UserInputOptions) => Exit.Exit<string, Schema.SchemaError>;
|
|
227
|
+
export declare const validateUserInputEffect: (input: string, options?: UserInputOptions) => Exit.Exit<string, Schema.SchemaError>;
|
|
228
228
|
/**
|
|
229
229
|
* Validates and sanitizes generic user input by trimming, removing HTML tags (unless allowed),
|
|
230
230
|
* normalizing whitespace, and removing dangerous patterns to prevent XSS and basic injection flaws.
|
|
@@ -241,7 +241,7 @@ declare const validateUserInputEffect: (input: string, options?: UserInputOption
|
|
|
241
241
|
* validateUserInput('<script>alert(1)</script>test', 100); // "test"
|
|
242
242
|
* ```
|
|
243
243
|
*/
|
|
244
|
-
declare const validateUserInput: (input: string, maxLength?: number, allowHtml?: boolean) => string;
|
|
244
|
+
export declare const validateUserInput: (input: string, maxLength?: number, allowHtml?: boolean) => string;
|
|
245
245
|
/**
|
|
246
246
|
* Safely parses JSON with Effect Schema validation and prototype pollution protection.
|
|
247
247
|
*
|
|
@@ -267,7 +267,7 @@ declare const validateUserInput: (input: string, maxLength?: number, allowHtml?:
|
|
|
267
267
|
* // Option.some({ name: 'John', age: 30 })
|
|
268
268
|
* ```
|
|
269
269
|
*/
|
|
270
|
-
declare const parseJsonWithSchema: <A>(jsonString: string, schema: SyncSchema<A>) => Option.Option<A>;
|
|
270
|
+
export declare const parseJsonWithSchema: <A>(jsonString: string, schema: SyncSchema<A>) => Option.Option<A>;
|
|
271
271
|
/**
|
|
272
272
|
* Sanitizes and safely parses a JSON string, removing suspicious syntax elements that could
|
|
273
273
|
* potentially result in JSON polyglot exploits or prototype pollution.
|
|
@@ -288,7 +288,7 @@ declare const parseJsonWithSchema: <A>(jsonString: string, schema: SyncSchema<A>
|
|
|
288
288
|
* // obj: unknown — narrow before use, or use parseJsonWithSchema
|
|
289
289
|
* ```
|
|
290
290
|
*/
|
|
291
|
-
declare const sanitizeJson: (jsonString: string) => unknown;
|
|
291
|
+
export declare const sanitizeJson: (jsonString: string) => unknown;
|
|
292
292
|
/**
|
|
293
293
|
* Strips ANSI escape sequences from a string.
|
|
294
294
|
*
|
|
@@ -310,7 +310,7 @@ declare const sanitizeJson: (jsonString: string) => unknown;
|
|
|
310
310
|
* stripAnsi('\x1b[31mRed text\x1b[0m'); // 'Red text'
|
|
311
311
|
* ```
|
|
312
312
|
*/
|
|
313
|
-
declare const stripAnsi: (text: string) => string;
|
|
313
|
+
export declare const stripAnsi: (text: string) => string;
|
|
314
314
|
/**
|
|
315
315
|
* Redacts PII from text using Effect Schema validated options.
|
|
316
316
|
*
|
|
@@ -325,7 +325,7 @@ declare const stripAnsi: (text: string) => string;
|
|
|
325
325
|
* carries a {@link S.SchemaError} from invalid `options`.
|
|
326
326
|
* @compliance NIST 800-53 AU-3 (Content of Audit Records)
|
|
327
327
|
*/
|
|
328
|
-
declare const redactPIIEffect: (text: string, options?: PIIRedactionOptions) => Exit.Exit<string, Schema.SchemaError>;
|
|
328
|
+
export declare const redactPIIEffect: (text: string, options?: PIIRedactionOptions) => Exit.Exit<string, Schema.SchemaError>;
|
|
329
329
|
/**
|
|
330
330
|
* Redacts common PII patterns in a string for safe logging.
|
|
331
331
|
* Detects and masks SSNs, credit cards, emails, phone numbers, etc.
|
|
@@ -350,7 +350,7 @@ declare const redactPIIEffect: (text: string, options?: PIIRedactionOptions) =>
|
|
|
350
350
|
* // 'SSN: [SSN]'
|
|
351
351
|
* ```
|
|
352
352
|
*/
|
|
353
|
-
declare const redactPII: (text: string, options?: PIIRedactionOptions & {
|
|
353
|
+
export declare const redactPII: (text: string, options?: PIIRedactionOptions & {
|
|
354
354
|
customPatterns?: Array<{
|
|
355
355
|
pattern: RegExp;
|
|
356
356
|
replacement: string;
|
|
@@ -379,7 +379,7 @@ declare const redactPII: (text: string, options?: PIIRedactionOptions & {
|
|
|
379
379
|
* // '{\n "user": "john",\n "password": "[REDACTED]"\n}'
|
|
380
380
|
* ```
|
|
381
381
|
*/
|
|
382
|
-
declare const safeStringify: (obj: unknown, sensitiveKeys?: string[], indent?: number) => string;
|
|
382
|
+
export declare const safeStringify: (obj: unknown, sensitiveKeys?: string[], indent?: number) => string;
|
|
383
383
|
/**
|
|
384
384
|
* Validates if a string is a valid email address using Effect Schema.
|
|
385
385
|
*
|
|
@@ -389,7 +389,7 @@ declare const safeStringify: (obj: unknown, sensitiveKeys?: string[], indent?: n
|
|
|
389
389
|
* @param email - The string to validate.
|
|
390
390
|
* @returns true if valid email, false otherwise.
|
|
391
391
|
*/
|
|
392
|
-
declare const isValidEmail: (email: string) => email is Email;
|
|
392
|
+
export declare const isValidEmail: (email: string) => email is Email;
|
|
393
393
|
/**
|
|
394
394
|
* Validates if a string is a valid phone number using Effect Schema.
|
|
395
395
|
*
|
|
@@ -398,7 +398,7 @@ declare const isValidEmail: (email: string) => email is Email;
|
|
|
398
398
|
* @param phone - The string to validate.
|
|
399
399
|
* @returns true if valid phone number, false otherwise.
|
|
400
400
|
*/
|
|
401
|
-
declare const isValidPhone: (phone: string) => phone is PhoneNumber;
|
|
401
|
+
export declare const isValidPhone: (phone: string) => phone is PhoneNumber;
|
|
402
402
|
/**
|
|
403
403
|
* Validates if a string is a valid SSN using Effect Schema.
|
|
404
404
|
*
|
|
@@ -407,7 +407,7 @@ declare const isValidPhone: (phone: string) => phone is PhoneNumber;
|
|
|
407
407
|
* @param ssn - The string to validate.
|
|
408
408
|
* @returns true if valid SSN, false otherwise.
|
|
409
409
|
*/
|
|
410
|
-
declare const isValidSSN: (ssn: string) => ssn is SSN;
|
|
410
|
+
export declare const isValidSSN: (ssn: string) => ssn is SSN;
|
|
411
411
|
/**
|
|
412
412
|
* Validates if a string is a safe URL using Effect Schema.
|
|
413
413
|
*
|
|
@@ -416,7 +416,6 @@ declare const isValidSSN: (ssn: string) => ssn is SSN;
|
|
|
416
416
|
* @param url - The string to validate.
|
|
417
417
|
* @returns true if valid and safe URL, false otherwise.
|
|
418
418
|
*/
|
|
419
|
-
declare const isValidUrl: (url: string) => url is SafeUrl;
|
|
419
|
+
export declare const isValidUrl: (url: string) => url is SafeUrl;
|
|
420
420
|
//#endregion
|
|
421
|
-
export { CreditCard, CreditCardSchema, Email, EmailSchema, IPv4, IPv4Schema, PIIRedactionOptions, PIIRedactionOptionsSchema, PhoneNumber, PhoneNumberSchema, SSN, SSNSchema, SafeUrl, SafeUrlSchema, SanitizedString, SanitizedStringSchema, UrlProtocol, UrlProtocolSchema, UserInputOptions, UserInputOptionsSchema, escapeHtml, isValidEmail, isValidPhone, isValidSSN, isValidUrl, parseJsonWithSchema, redactPII, redactPIIEffect, safeStringify, sanitizeHtml, sanitizeJson, sanitizeUrl, sanitizeUrlEffect, stripAnsi, validateUserInput, validateUserInputEffect };
|
|
422
421
|
//# sourceMappingURL=sanitize.d.mts.map
|
package/lib/sanitize.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.mts","names":[],"sources":["../src/sanitize.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"sanitize.d.mts","names":[],"sources":["../src/sanitize.ts"],"mappings":";;;;;;;;KAqCK,WAAW,KAAK,OAAE,MAAM;;;;;qBAShB,mBAAiB,OAAA;;YAElB,qBAAqB,kBAAkB;;;;;qBAMtC,2BAAyB,OAAA;;;;;;;;;YAS1B,6BAA6B,0BAA0B;;;;;qBAMtD,wBAAsB,OAAA;;;;;;;YAOvB,0BAA0B,uBAAuB;qBAuBhD,eAAa,OAAA;;;;;;;;;;;;YA4Bd,UAAU;;;;;;qBAOT,uBAAqB,OAAA;;;;;;;;YAQtB,yBAAyB,sBAAsB;;;;;;;;;qBAU9C,aAAW,OAAA;;;;;;;YASZ,QAAQ;;;;;qBAMP,mBAAiB,OAAA;;;;;;;YASlB,cAAc;;;;;qBAMb,WAAS,OAAA;;;;;;;YAOV,MAAM;;;;;qBAML,kBAAgB,OAAA;;;;;;;;YAUjB,aAAa;;;;qBAKZ,YAAU,OAAA;;;;;;;YAOX,OAAO;;;;;;;;;;;;;;;qBAmBN,aAAc;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsCd,oBACZ,aACA,4BAA2B,kBACzB,KAAK,aAAa,OAAE;;;;;;;;;;;;;;;;qBA+CV,cACZ,aACA,4BAA2B;;;;;;;;;;;;;;;;;;;;qBAyGf,eAAgB,cAAc,UAAU;;;;;;;;;;;;;;;;;qBA6BxC,0BACZ,eACA,UAAS,qBACP,KAAK,aAAa,OAAE;;;;;;;;;;;;;;;;;qBA+DV,oBAAqB,eAAe,oBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmFrD,sBAAuB,GACnC,oBACA,QAAQ,WAAW,OACjB,OAAO,OAAO;;;;;;;;;;;;;;;;;;;;;qBA0CJ,eAAgB;;;;;;;;;;;;;;;;;;;;;;qBA0ChB,YAAa;;;;;;;;;;;;;;;qBAoEb,kBACZ,cACA,UAAS,wBACP,KAAK,aAAa,OAAE;;;;;;;;;;;;;;;;;;;;;;;;;qBAuEV,YACZ,cACA,UAAS;EACR,iBAAiB;IAAQ,SAAS;IAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;qBA0D/B,gBACZ,cACA,0BAUA;;;;;;;;;;qBA+BY,eAAgB,kBAAgB,SAAS;;;;;;;;;qBAYzC,eAAgB,kBAAgB,SAAS;;;;;;;;;qBAYzC,aAAc,gBAAc,OAAO;;;;;;;;;qBAYnC,aAAc,gBAAc,OAAO"}
|