@resq-systems/security 1.0.5 → 2.0.0
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 +236 -33
- package/lib/controls/address.d.mts +142 -0
- package/lib/controls/address.d.mts.map +1 -0
- package/lib/controls/address.mjs +533 -0
- package/lib/controls/address.mjs.map +1 -0
- package/lib/controls/csrf.d.mts +91 -0
- package/lib/controls/csrf.d.mts.map +1 -0
- package/lib/controls/csrf.mjs +200 -0
- package/lib/controls/csrf.mjs.map +1 -0
- package/lib/controls/index.d.mts +8 -0
- package/lib/controls/index.mjs +8 -0
- package/lib/controls/origin.d.mts +95 -0
- package/lib/controls/origin.d.mts.map +1 -0
- package/lib/controls/origin.mjs +156 -0
- package/lib/controls/origin.mjs.map +1 -0
- package/lib/controls/payload.d.mts +84 -0
- package/lib/controls/payload.d.mts.map +1 -0
- package/lib/controls/payload.mjs +147 -0
- package/lib/controls/payload.mjs.map +1 -0
- package/lib/controls/query.d.mts +157 -0
- package/lib/controls/query.d.mts.map +1 -0
- package/lib/controls/query.mjs +368 -0
- package/lib/controls/query.mjs.map +1 -0
- package/lib/controls/redirect.d.mts +92 -0
- package/lib/controls/redirect.d.mts.map +1 -0
- package/lib/controls/redirect.mjs +110 -0
- package/lib/controls/redirect.mjs.map +1 -0
- package/lib/controls/upload.d.mts +108 -0
- package/lib/controls/upload.d.mts.map +1 -0
- package/lib/controls/upload.mjs +374 -0
- package/lib/controls/upload.mjs.map +1 -0
- package/lib/crypto.d.mts +18 -5
- package/lib/crypto.d.mts.map +1 -1
- package/lib/crypto.mjs +35 -24
- package/lib/crypto.mjs.map +1 -1
- package/lib/hash.d.mts +51 -6
- package/lib/hash.d.mts.map +1 -1
- package/lib/hash.mjs +51 -6
- package/lib/hash.mjs.map +1 -1
- package/lib/index.d.mts +17 -2
- package/lib/index.mjs +19 -2
- package/lib/paths.d.mts +92 -0
- package/lib/paths.d.mts.map +1 -0
- package/lib/paths.mjs +140 -0
- package/lib/paths.mjs.map +1 -0
- package/lib/sanitize.d.mts +137 -35
- package/lib/sanitize.d.mts.map +1 -1
- package/lib/sanitize.mjs +170 -46
- package/lib/sanitize.mjs.map +1 -1
- package/lib/threats/capec.generated.d.mts +59 -0
- package/lib/threats/capec.generated.d.mts.map +1 -0
- package/lib/threats/capec.generated.mjs +644 -0
- package/lib/threats/capec.generated.mjs.map +1 -0
- package/lib/threats/engine.d.mts +94 -0
- package/lib/threats/engine.d.mts.map +1 -0
- package/lib/threats/engine.mjs +167 -0
- package/lib/threats/engine.mjs.map +1 -0
- package/lib/threats/index.d.mts +11 -0
- package/lib/threats/index.mjs +11 -0
- package/lib/threats/rules/datastore.d.mts +13 -0
- package/lib/threats/rules/datastore.d.mts.map +1 -0
- package/lib/threats/rules/datastore.mjs +366 -0
- package/lib/threats/rules/datastore.mjs.map +1 -0
- package/lib/threats/rules/index.d.mts +54 -0
- package/lib/threats/rules/index.d.mts.map +1 -0
- package/lib/threats/rules/index.mjs +121 -0
- package/lib/threats/rules/index.mjs.map +1 -0
- package/lib/threats/rules/markup.d.mts +28 -0
- package/lib/threats/rules/markup.d.mts.map +1 -0
- package/lib/threats/rules/markup.mjs +373 -0
- package/lib/threats/rules/markup.mjs.map +1 -0
- package/lib/threats/rules/protocol.d.mts +49 -0
- package/lib/threats/rules/protocol.d.mts.map +1 -0
- package/lib/threats/rules/protocol.mjs +175 -0
- package/lib/threats/rules/protocol.mjs.map +1 -0
- package/lib/threats/rules/system.d.mts +19 -0
- package/lib/threats/rules/system.d.mts.map +1 -0
- package/lib/threats/rules/system.mjs +455 -0
- package/lib/threats/rules/system.mjs.map +1 -0
- package/lib/threats/rules/web.d.mts +26 -0
- package/lib/threats/rules/web.d.mts.map +1 -0
- package/lib/threats/rules/web.mjs +412 -0
- package/lib/threats/rules/web.mjs.map +1 -0
- package/lib/threats/scoring.d.mts +59 -0
- package/lib/threats/scoring.d.mts.map +1 -0
- package/lib/threats/scoring.mjs +111 -0
- package/lib/threats/scoring.mjs.map +1 -0
- package/lib/threats/types.d.mts +245 -0
- package/lib/threats/types.d.mts.map +1 -0
- package/lib/threats/types.mjs +52 -0
- package/lib/threats/types.mjs.map +1 -0
- package/lib/threats/variants.d.mts +57 -0
- package/lib/threats/variants.d.mts.map +1 -0
- package/lib/threats/variants.mjs +144 -0
- package/lib/threats/variants.mjs.map +1 -0
- package/lib/unicode/confusables.d.mts +82 -0
- package/lib/unicode/confusables.d.mts.map +1 -0
- package/lib/unicode/confusables.mjs +954 -0
- package/lib/unicode/confusables.mjs.map +1 -0
- package/lib/unicode/index.d.mts +126 -0
- package/lib/unicode/index.d.mts.map +1 -0
- package/lib/unicode/index.mjs +288 -0
- package/lib/unicode/index.mjs.map +1 -0
- package/lib/validators.d.mts +341 -164
- package/lib/validators.d.mts.map +1 -1
- package/lib/validators.mjs +519 -338
- package/lib/validators.mjs.map +1 -1
- package/package.json +35 -8
package/lib/validators.d.mts
CHANGED
|
@@ -1,180 +1,165 @@
|
|
|
1
|
+
import { ThreatFinding, ThreatType } from "./threats/types.mjs";
|
|
1
2
|
//#region src/validators.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Copyright 2026 ResQ Systems, Inc.
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
3
|
/**
|
|
18
4
|
* Outcome of {@link detectThreatPatterns}.
|
|
19
5
|
*
|
|
20
|
-
* `isSafe` is the boolean shortcut; `threats`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* the first finding.
|
|
6
|
+
* `isSafe` is the boolean shortcut; `threats` carries the findings. Prefer
|
|
7
|
+
* {@link scanForThreats}, whose result adds a numeric score and an allow/review/block
|
|
8
|
+
* verdict instead of collapsing everything into one boolean.
|
|
24
9
|
*/
|
|
25
10
|
interface ThreatDetectionResult {
|
|
26
|
-
/** `true` when no
|
|
11
|
+
/** `true` when no detector fired. Equivalent to `threats.length === 0`. */
|
|
27
12
|
isSafe: boolean;
|
|
28
|
-
/**
|
|
13
|
+
/** Findings from the enabled detectors, at most one per weakness category. */
|
|
29
14
|
threats: ThreatFinding[];
|
|
30
15
|
}
|
|
31
16
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
17
|
+
* Minimal shape {@link getThreatErrorMessage} needs.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately narrower than {@link ThreatFinding} so callers can pass a hand-built
|
|
20
|
+
* summary — or a finding from an older version of this package — without having to
|
|
21
|
+
* populate the full record.
|
|
34
22
|
*/
|
|
35
|
-
interface
|
|
36
|
-
/**
|
|
37
|
-
type: ThreatType;
|
|
38
|
-
/**
|
|
39
|
-
description
|
|
40
|
-
/**
|
|
41
|
-
matchedPattern?: string;
|
|
23
|
+
interface ThreatSummary {
|
|
24
|
+
/** Weakness category. The only field the message depends on. */
|
|
25
|
+
readonly type: ThreatType;
|
|
26
|
+
/** Operator-facing description, if available. */
|
|
27
|
+
readonly description?: string;
|
|
28
|
+
/** Matched excerpt, if available. */
|
|
29
|
+
readonly matchedPattern?: string;
|
|
42
30
|
}
|
|
43
31
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
32
|
+
* Per-detector toggles for {@link detectThreatPatterns}.
|
|
33
|
+
*
|
|
34
|
+
* @deprecated Prefer {@link scanForThreats} with an explicit `contexts` list. These
|
|
35
|
+
* booleans conflate "which weakness am I looking for" with "where is this value
|
|
36
|
+
* going", and the second question is the one that decides whether a signature is
|
|
37
|
+
* evidence or noise. Each flag maps onto a context: `checkXSS` → `html`,
|
|
38
|
+
* `checkSQLInjection` → `sql`, `checkNoSQLInjection` → `nosql`,
|
|
39
|
+
* `checkCommandInjection` → `shell`, `checkPathTraversal` → `filesystem`;
|
|
40
|
+
* `checkHomoglyphs` runs UTS #39 identifier analysis.
|
|
46
41
|
*/
|
|
47
|
-
|
|
42
|
+
interface ThreatDetectionConfig {
|
|
43
|
+
/** Default `true`. Maps to the `html` context. */
|
|
44
|
+
checkXSS?: boolean;
|
|
45
|
+
/** Default `true`. Maps to the `sql` context. */
|
|
46
|
+
checkSQLInjection?: boolean;
|
|
47
|
+
/** Default `true`. Maps to the `nosql` context. */
|
|
48
|
+
checkNoSQLInjection?: boolean;
|
|
49
|
+
/** Default `false` — opt in only when input reaches a shell. Maps to `shell`. */
|
|
50
|
+
checkCommandInjection?: boolean;
|
|
51
|
+
/** Default `true`. Maps to the `filesystem` context. */
|
|
52
|
+
checkPathTraversal?: boolean;
|
|
53
|
+
/** Default `true`. Runs UTS #39 identifier analysis rather than a pattern list. */
|
|
54
|
+
checkHomoglyphs?: boolean;
|
|
55
|
+
}
|
|
48
56
|
/**
|
|
49
|
-
* Detect XSS
|
|
50
|
-
*
|
|
57
|
+
* Detect XSS payloads — script tags, inline event handlers, dangerous URI schemes,
|
|
58
|
+
* markup sinks — in a value bound for an HTML context.
|
|
51
59
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* payloads. Returns at most one finding — the regex catalog is
|
|
55
|
-
* exhaustive enough that the first hit is sufficient for a
|
|
56
|
-
* reject-or-sanitize decision.
|
|
60
|
+
* @param input - String to scan. Truncated at 100 000 characters.
|
|
61
|
+
* @returns Empty array, or a single finding of type `"xss"`.
|
|
57
62
|
*
|
|
58
|
-
* @
|
|
59
|
-
*
|
|
60
|
-
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* Prototype-pollution patterns (`__proto__`, `constructor[`) no longer surface here.
|
|
65
|
+
* They are a distinct weakness class with distinct controls and now report as
|
|
66
|
+
* `prototype_pollution` — see {@link containsPrototypePollution}.
|
|
61
67
|
*
|
|
62
68
|
* @example
|
|
63
69
|
* ```ts
|
|
64
70
|
* containsXSSPatterns(`<img src=x onerror="alert(1)">`);
|
|
65
|
-
* // → [{
|
|
71
|
+
* // → [{ ruleId: "XSS-EVENT-HANDLER-001", type: "xss", severity: "high", … }]
|
|
66
72
|
* ```
|
|
67
73
|
*/
|
|
68
74
|
declare function containsXSSPatterns(input: string): ThreatFinding[];
|
|
69
75
|
/**
|
|
70
|
-
* Detect
|
|
71
|
-
*
|
|
72
|
-
* in input.
|
|
76
|
+
* Detect prototype-pollution payloads — `__proto__`, `constructor.prototype`, and the
|
|
77
|
+
* nested-object forms that arrive through a JSON body or query-string expansion.
|
|
73
78
|
*
|
|
74
|
-
* **Not
|
|
75
|
-
*
|
|
76
|
-
*
|
|
79
|
+
* **Not the control.** Reject unknown keys with schema validation, build lookup
|
|
80
|
+
* objects with `Object.create(null)`, and use a merge that skips `__proto__`,
|
|
81
|
+
* `constructor`, and `prototype`.
|
|
82
|
+
*
|
|
83
|
+
* @param input - String to scan.
|
|
84
|
+
* @returns Empty array, or a single finding of type `"prototype_pollution"`.
|
|
85
|
+
*/
|
|
86
|
+
declare function containsPrototypePollution(input: string): ThreatFinding[];
|
|
87
|
+
/**
|
|
88
|
+
* Detect SQL-injection patterns in a value bound for a query.
|
|
89
|
+
*
|
|
90
|
+
* **Not a replacement for parameterized queries.** A bound parameter is safe whatever
|
|
91
|
+
* keywords it contains; an interpolated one is unsafe however many signatures it
|
|
92
|
+
* dodges. Use this for telemetry alongside binding, never instead of it.
|
|
77
93
|
*
|
|
78
94
|
* @param input - String to scan. Truncated at 100 000 characters.
|
|
79
95
|
* @returns Empty array, or one finding of type `"sql_injection"`.
|
|
80
96
|
*/
|
|
81
97
|
declare function containsSQLInjection(input: string): ThreatFinding[];
|
|
82
98
|
/**
|
|
83
|
-
* Detect NoSQL
|
|
84
|
-
*
|
|
85
|
-
* structural manipulators that can bypass auth filters in document
|
|
86
|
-
* stores.
|
|
99
|
+
* Detect NoSQL operator injection — `$where`, `$ne`, `$regex`, and the object and
|
|
100
|
+
* array forms that bypass authentication filters in document stores.
|
|
87
101
|
*
|
|
88
102
|
* @param input - String to scan.
|
|
89
103
|
* @returns Empty array, or one finding of type `"nosql_injection"`.
|
|
90
104
|
*/
|
|
91
105
|
declare function containsNoSQLInjection(input: string): ThreatFinding[];
|
|
92
106
|
/**
|
|
93
|
-
* Detect shell command-injection patterns
|
|
94
|
-
*
|
|
95
|
-
* …) and shell-piped exec (`| sh`, `| bash`).
|
|
107
|
+
* Detect shell command-injection patterns — command substitution, chained commands,
|
|
108
|
+
* pipes into an interpreter.
|
|
96
109
|
*
|
|
97
|
-
* **Off by default in {@link detectThreatPatterns}
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* process or shell.
|
|
110
|
+
* **Off by default in {@link detectThreatPatterns}**, because these patterns fire on
|
|
111
|
+
* ordinary prose. Enable only when the value reaches a child process, and prefer
|
|
112
|
+
* spawning with an argv array and `shell: false`, which makes the category moot.
|
|
101
113
|
*
|
|
102
114
|
* @param input - String to scan. Truncated at 100 000 characters.
|
|
103
115
|
* @returns Empty array, or one finding of type `"command_injection"`.
|
|
104
116
|
*/
|
|
105
117
|
declare function containsCommandInjection(input: string): ThreatFinding[];
|
|
106
118
|
/**
|
|
107
|
-
* Detect path-traversal payloads — `../`, encoded
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
119
|
+
* Detect path-traversal payloads — `../`, its percent-encoded and double-encoded
|
|
120
|
+
* forms, NUL truncation, and references to sensitive system paths.
|
|
121
|
+
*
|
|
122
|
+
* **Not the control.** Use `resolveContainedPath` from
|
|
123
|
+
* `@resq-systems/security/paths`, which resolves the candidate against a base
|
|
124
|
+
* directory and verifies containment — a check that also catches absolute paths and
|
|
125
|
+
* separator tricks no signature enumerates.
|
|
111
126
|
*
|
|
112
127
|
* @param input - String to scan.
|
|
113
128
|
* @returns Empty array, or one finding of type `"path_traversal"`.
|
|
114
129
|
*/
|
|
115
130
|
declare function containsPathTraversal(input: string): ThreatFinding[];
|
|
116
131
|
/**
|
|
117
|
-
* Detect
|
|
118
|
-
* render identically to common ASCII letters). The classic phishing
|
|
119
|
-
* trick is `paypaӏ.com` (`ӏ` instead of `l`); this detector catches
|
|
120
|
-
* the building blocks.
|
|
132
|
+
* Detect visually confusable characters in a **protected identifier**.
|
|
121
133
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
134
|
+
* Backed by UTS #39 script analysis rather than a hand-written lookalike table, so it
|
|
135
|
+
* reports the actual signal — a Latin/Cyrillic mix in `pаypal` — instead of flagging
|
|
136
|
+
* every non-ASCII character. Single-script values are not confusable with anything, so
|
|
137
|
+
* `Ольга Иванова` and `東京タワー` pass where the previous implementation rejected both.
|
|
124
138
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* first matched lookalike).
|
|
128
|
-
*/
|
|
129
|
-
declare function containsHomoglyphs(input: string): ThreatFinding[];
|
|
130
|
-
/**
|
|
131
|
-
* Per-detector toggles for {@link detectThreatPatterns}.
|
|
139
|
+
* Scope this to usernames, domains, org names, and package names. Do **not** run it on
|
|
140
|
+
* prose or on people's names — see {@link validatePersonName}.
|
|
132
141
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* Pass `false` to disable a detector or `true` to force-enable
|
|
136
|
-
* `checkCommandInjection`.
|
|
142
|
+
* @param input - Identifier to scan.
|
|
143
|
+
* @returns Empty array, or a single finding of type `"homoglyph"`.
|
|
137
144
|
*/
|
|
138
|
-
|
|
139
|
-
/** Default `true`. */
|
|
140
|
-
checkXSS?: boolean;
|
|
141
|
-
/** Default `true`. */
|
|
142
|
-
checkSQLInjection?: boolean;
|
|
143
|
-
/** Default `true`. */
|
|
144
|
-
checkNoSQLInjection?: boolean;
|
|
145
|
-
/** Default `false` — opt in only when input reaches a shell. */
|
|
146
|
-
checkCommandInjection?: boolean;
|
|
147
|
-
/** Default `true`. */
|
|
148
|
-
checkPathTraversal?: boolean;
|
|
149
|
-
/** Default `true`. */
|
|
150
|
-
checkHomoglyphs?: boolean;
|
|
151
|
-
}
|
|
145
|
+
declare function containsHomoglyphs(input: string): ThreatFinding[];
|
|
152
146
|
/**
|
|
153
|
-
* Run
|
|
147
|
+
* Run the enabled detectors against `input` and aggregate findings.
|
|
154
148
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
149
|
+
* @deprecated Prefer {@link scanForThreats}, which takes explicit contexts and returns
|
|
150
|
+
* a score and verdict rather than one boolean. This wrapper maps the legacy toggles
|
|
151
|
+
* onto contexts and keeps the one-finding-per-category shape.
|
|
158
152
|
*
|
|
159
|
-
* Non-string
|
|
160
|
-
*
|
|
161
|
-
* reject non-strings.
|
|
153
|
+
* Non-string input (`null`, `undefined`, a number) is reported safe — wrap your own
|
|
154
|
+
* type validation around this if you need to reject those.
|
|
162
155
|
*
|
|
163
156
|
* @param input - The candidate string.
|
|
164
|
-
* @param config - Detector toggles.
|
|
165
|
-
* except command-injection.
|
|
157
|
+
* @param config - Detector toggles. Everything except command injection defaults on.
|
|
166
158
|
* @returns `{ isSafe, threats }`.
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```ts
|
|
170
|
-
* const result = detectThreatPatterns(req.body.query);
|
|
171
|
-
* if (!result.isSafe) return new Response(getThreatErrorMessage(result), { status: 400 });
|
|
172
|
-
* ```
|
|
173
159
|
*/
|
|
174
160
|
declare function detectThreatPatterns(input: string, config?: ThreatDetectionConfig): ThreatDetectionResult;
|
|
175
161
|
/**
|
|
176
|
-
* Boolean shortcut over {@link detectThreatPatterns}
|
|
177
|
-
* findings list when you only need a yes/no decision.
|
|
162
|
+
* Boolean shortcut over {@link detectThreatPatterns}.
|
|
178
163
|
*
|
|
179
164
|
* @param input - String to test.
|
|
180
165
|
* @param config - Optional detector toggles.
|
|
@@ -182,88 +167,280 @@ declare function detectThreatPatterns(input: string, config?: ThreatDetectionCon
|
|
|
182
167
|
*/
|
|
183
168
|
declare function isSafeInput(input: string, config?: ThreatDetectionConfig): boolean;
|
|
184
169
|
/**
|
|
185
|
-
* HTML-entity
|
|
186
|
-
*
|
|
170
|
+
* HTML-entity-escape a value being inserted as **element text**.
|
|
171
|
+
*
|
|
172
|
+
* Escapes `&`, `<`, `>`, `"`, `'`, and `/`, which covers text nodes and fully quoted
|
|
173
|
+
* attribute values.
|
|
174
|
+
*
|
|
175
|
+
* **Output encoding is context-dependent.** HTML text, quoted attributes, unquoted
|
|
176
|
+
* attributes, URLs, JavaScript string literals, and CSS each have different rules, and
|
|
177
|
+
* no single function is correct for all of them. This one is correct for text; use
|
|
178
|
+
* {@link escapeHtmlAttribute} for attribute values, `sanitizeUrl` for URLs, and
|
|
179
|
+
* `sanitizeHtml` (DOMPurify) when the value is meant to *be* markup.
|
|
180
|
+
*
|
|
181
|
+
* There is deliberately no CSS-context escaper here, and no general JavaScript-string
|
|
182
|
+
* escaper — hand-rolled versions of those are reliably wrong, and the fix is to stop
|
|
183
|
+
* interpolating untrusted values into style and script *source*. Embedding untrusted
|
|
184
|
+
* *data* in a script element is the one tractable case, because `JSON.stringify` fixes
|
|
185
|
+
* the string boundaries first; {@link encodeJsonForScript} covers that and nothing else.
|
|
186
|
+
*
|
|
187
|
+
* @param input - Untrusted string. Non-string or empty input yields `""`.
|
|
188
|
+
* @returns Entity-escaped output safe to interpolate into HTML text.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* escapeHtmlText('<script>alert("xss")</script>');
|
|
193
|
+
* // "<script>alert("xss")</script>"
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
declare function escapeHtmlText(input: string): string;
|
|
197
|
+
/**
|
|
198
|
+
* HTML-entity-escape a value being inserted as an **attribute value**.
|
|
187
199
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* client, sanitize-html or similar on the server).
|
|
200
|
+
* Everything {@link escapeHtmlText} escapes, plus backtick, equals, and whitespace —
|
|
201
|
+
* the characters that let a payload break out of an *unquoted* attribute. That case is
|
|
202
|
+
* precisely what generic "escape for display" helpers get wrong.
|
|
192
203
|
*
|
|
193
|
-
*
|
|
204
|
+
* The ceiling on the unquoted case is injection of a valueless boolean attribute —
|
|
205
|
+
* `autofocus`, `disabled`, `formnovalidate` — not script execution: an injected
|
|
206
|
+
* `onmouseover=…` arrives with its `=` already escaped, so it lands as an attribute
|
|
207
|
+
* whose *name* is the escaped text, with no handler bound.
|
|
208
|
+
*
|
|
209
|
+
* Quote your attributes anyway. This makes an unquoted attribute survivable; it does
|
|
210
|
+
* not make it correct.
|
|
211
|
+
*
|
|
212
|
+
* @param input - Untrusted string. Non-string or empty input yields `""`.
|
|
213
|
+
* @returns Output safe to interpolate into a quoted or unquoted attribute value.
|
|
214
|
+
*/
|
|
215
|
+
declare function escapeHtmlAttribute(input: string): string;
|
|
216
|
+
/**
|
|
217
|
+
* HTML-entity-escape a value for display.
|
|
218
|
+
*
|
|
219
|
+
* @deprecated Renamed to {@link escapeHtmlText}, which says what it actually does. The
|
|
220
|
+
* old name suggested a general-purpose "make this safe to display" operation, and
|
|
221
|
+
* callers reasonably read it as attribute-safe — which entity escaping alone is not,
|
|
222
|
+
* for *unquoted* attributes. Behaviour is unchanged; only the name is.
|
|
194
223
|
*
|
|
195
224
|
* @param input - Untrusted string.
|
|
196
|
-
* @returns Entity-escaped output
|
|
225
|
+
* @returns Entity-escaped output.
|
|
197
226
|
*/
|
|
198
227
|
declare function sanitizeForDisplay(input: string): string;
|
|
199
228
|
/**
|
|
200
|
-
*
|
|
229
|
+
* Escape a value for inclusion in a log record.
|
|
230
|
+
*
|
|
231
|
+
* This is the control named by the log-injection rules. A log line is a *sink*: a value
|
|
232
|
+
* carrying a newline forges an entry (CWE-117), one carrying a terminal escape rewrites
|
|
233
|
+
* what an operator sees, and one carrying a bidirectional override reorders the line
|
|
234
|
+
* without altering a byte of it.
|
|
235
|
+
*
|
|
236
|
+
* Escaping rather than stripping is deliberate. The record is evidence, so the encoded
|
|
237
|
+
* form is reversible and nothing is silently discarded — contrast `stripAnsi`, which
|
|
238
|
+
* deletes. Structured logging is still the better answer, because it removes the
|
|
239
|
+
* ambiguity this function can only make visible; use both.
|
|
201
240
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
241
|
+
* @param value - Untrusted field value. Non-string or empty input yields `""`.
|
|
242
|
+
* @param options - Optional bounds.
|
|
243
|
+
* @param options.maxLength - Characters read from `value`. Defaults to 2048. Truncation
|
|
244
|
+
* is announced in the output rather than applied silently, and the returned string may
|
|
245
|
+
* exceed this length, because escaping expands.
|
|
246
|
+
* @returns A single-line, control-free rendering of `value`.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* encodeLogValue("alice\nINFO user promoted to admin");
|
|
251
|
+
* // "alice\\nINFO user promoted to admin" — one line, no forged entry
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function encodeLogValue(value: string, options?: {
|
|
255
|
+
readonly maxLength?: number;
|
|
256
|
+
}): string;
|
|
257
|
+
/**
|
|
258
|
+
* Escape one cell for CSV export.
|
|
259
|
+
*
|
|
260
|
+
* This is the control named by the formula-injection rules. A CSV file is not inert: a
|
|
261
|
+
* cell beginning `=`, `+`, `-`, `@`, tab or CR is evaluated as a formula by Excel,
|
|
262
|
+
* Sheets and LibreOffice when the recipient opens it, so the payload executes on *their*
|
|
263
|
+
* machine, outside the exporting application entirely (CWE-1236).
|
|
264
|
+
*
|
|
265
|
+
* Two separate jobs, in order: neutralise the formula trigger with a leading apostrophe,
|
|
266
|
+
* then apply RFC 4180 quoting so the field cannot break the row.
|
|
267
|
+
*
|
|
268
|
+
* **Only strings are prefixed.** A `number` or `boolean` came from the application's own
|
|
269
|
+
* types and cannot carry a formula, so `-1234` exports as a negative number while
|
|
270
|
+
* `"-1234"` exports as text. Pass numeric columns as numbers, or every negative value in
|
|
271
|
+
* the sheet becomes a string.
|
|
272
|
+
*
|
|
273
|
+
* Three things worth knowing before relying on it:
|
|
274
|
+
* - The leading apostrophe is an Excel convention, **not** an RFC 4180 construct. Readers
|
|
275
|
+
* that do not implement it surface it as a literal character in the data.
|
|
276
|
+
* - NUL is removed rather than escaped, so it does not round-trip.
|
|
277
|
+
* - Scanning the output with `scanForThreats` still reports a finding, by design:
|
|
278
|
+
* `CSV-FORMULA-LEAD-001` sees through the apostrophe and `CSV-DDE-001` is
|
|
279
|
+
* position-independent. The rules describe the *value*; this function protects the
|
|
280
|
+
* *file*. A clean scan is the wrong acceptance test.
|
|
281
|
+
*
|
|
282
|
+
* @param value - Cell value. `null` and `undefined` become `""`.
|
|
283
|
+
* @param options - Optional dialect settings.
|
|
284
|
+
* @param options.delimiter - Field separator the row will be joined with. Defaults to `","`.
|
|
285
|
+
* @returns The escaped field, ready to join into a row.
|
|
208
286
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* escapeCsvField("=WEBSERVICE(\"https://evil.example\")");
|
|
290
|
+
* // quoted, and inert on open
|
|
291
|
+
* escapeCsvField(-1234); // "-1234" — a number, not a formula
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
declare function escapeCsvField(value: unknown, options?: {
|
|
295
|
+
readonly delimiter?: string;
|
|
296
|
+
}): string;
|
|
297
|
+
/**
|
|
298
|
+
* Escape and join one row for CSV export.
|
|
211
299
|
*
|
|
212
|
-
*
|
|
300
|
+
* @param values - Cell values, in column order.
|
|
301
|
+
* @param options - Optional dialect settings.
|
|
302
|
+
* @param options.delimiter - Field separator. Defaults to `","`.
|
|
303
|
+
* @returns The joined row, without a line terminator.
|
|
213
304
|
*
|
|
214
|
-
* @
|
|
215
|
-
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* toCsvRow(["Ada Lovelace", "=1+1", 42]);
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
declare function toCsvRow(values: readonly unknown[], options?: {
|
|
311
|
+
readonly delimiter?: string;
|
|
312
|
+
}): string;
|
|
313
|
+
/**
|
|
314
|
+
* Serialise a value for embedding inside a `<script>` element.
|
|
315
|
+
*
|
|
316
|
+
* `JSON.stringify` alone is not safe here. Its output may contain `</script>`, which
|
|
317
|
+
* closes the element from *inside a string literal* — the HTML tokenizer never looks at
|
|
318
|
+
* JavaScript syntax — so the remainder of the payload becomes markup.
|
|
319
|
+
*
|
|
320
|
+
* **Script element content only.** The output contains unescaped `"`, so it must never be
|
|
321
|
+
* placed in an attribute; use {@link escapeHtmlAttribute} there. It is also not a general
|
|
322
|
+
* JavaScript-string escaper — it is safe precisely because `JSON.stringify` has already
|
|
323
|
+
* decided where the string boundaries are.
|
|
324
|
+
*
|
|
325
|
+
* Using `<script type="application/json">` with `JSON.parse(el.textContent)` does **not**
|
|
326
|
+
* remove the need for this: a raw `</script>` in the data closes that element too.
|
|
327
|
+
*
|
|
328
|
+
* @param value - Any JSON-serialisable value.
|
|
329
|
+
* @returns JSON text safe to place between `<script>` tags.
|
|
330
|
+
* @throws {TypeError} If `value` cannot be represented as JSON — `undefined`, a function
|
|
331
|
+
* or a symbol at the top level (for which `JSON.stringify` returns `undefined` rather
|
|
332
|
+
* than a string), a circular structure, or a `BigInt`. Failing loudly is deliberate: a
|
|
333
|
+
* sentinel string would emit a syntax error into the page instead.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* const json = encodeJsonForScript({ name: userName });
|
|
338
|
+
* const html = "<script>window.__DATA__ = " + json + ";</script>";
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
declare function encodeJsonForScript(value: unknown): string;
|
|
342
|
+
/**
|
|
343
|
+
* Fold non-ASCII lookalike characters onto ASCII and compose to NFC.
|
|
344
|
+
*
|
|
345
|
+
* @deprecated Prefer `getSkeleton` and `analyzeIdentifier` from
|
|
346
|
+
* `@resq-systems/security/unicode`. Rewriting a user's identifier into a different
|
|
347
|
+
* string loses information and only *looks* safe — the durable pattern is to store
|
|
348
|
+
* what they typed, index its skeleton, and compare skeletons for collisions.
|
|
349
|
+
*
|
|
350
|
+
* Now backed by the UTS #39 confusable tables rather than the previous 14-entry map,
|
|
351
|
+
* so coverage is far wider. Combining marks are preserved (`e` + U+0301 still composes
|
|
352
|
+
* to `é`) and ASCII characters are never rewritten.
|
|
353
|
+
*
|
|
354
|
+
* @param input - Raw string from an untrusted source. Non-string input yields `""`.
|
|
355
|
+
* @returns NFC-composed string with non-ASCII confusables folded to ASCII.
|
|
216
356
|
*/
|
|
217
357
|
declare function normalizeUnicode(input: string): string;
|
|
218
358
|
/**
|
|
219
|
-
* Generic user-facing fallback message. Render
|
|
220
|
-
*
|
|
221
|
-
* {@link getThreatErrorMessage} for category-specific messages.
|
|
359
|
+
* Generic user-facing fallback message. Render verbatim when a detector fires and you
|
|
360
|
+
* do not want to reveal which one.
|
|
222
361
|
*/
|
|
223
362
|
declare const THREAT_DETECTED_MESSAGE = "Input contains potentially unsafe content";
|
|
224
363
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
364
|
+
* Refinement helper for `zod.string().refine(...)`, `effect/Schema.filter(...)`, or
|
|
365
|
+
* any predicate-based validator. Equivalent to {@link isSafeInput} with defaults.
|
|
227
366
|
*
|
|
228
|
-
*
|
|
367
|
+
* @param input - String to test.
|
|
368
|
+
* @returns `true` when no detector fires.
|
|
229
369
|
*/
|
|
230
370
|
declare function validateSafeText(input: string): boolean;
|
|
231
371
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
372
|
+
* Validate a human name field.
|
|
373
|
+
*
|
|
374
|
+
* The policy is an allowlist of what a name is made of — letters in any script,
|
|
375
|
+
* combining marks, apostrophes, hyphens, periods, spaces — plus a length bound and a
|
|
376
|
+
* bidirectional-control check. Nothing that passes it can carry an injection payload,
|
|
377
|
+
* because `<`, `(`, `;`, `$`, `=`, and every digit are already excluded.
|
|
236
378
|
*
|
|
237
|
-
*
|
|
379
|
+
* It deliberately does **not** run SQL, path-traversal, or confusable detectors. A
|
|
380
|
+
* name is not a query, a path, or a protected identifier, and subjecting one to those
|
|
381
|
+
* checks rejects real people: the previous implementation ran the homoglyph detector
|
|
382
|
+
* here, which failed any name containing а, е, о, р, с, or х — that is, most Russian,
|
|
383
|
+
* Ukrainian, Bulgarian, Serbian, and Greek names.
|
|
238
384
|
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
385
|
+
* Encode the value at whatever sink it eventually reaches. That is what makes it safe;
|
|
386
|
+
* this function only establishes that it is a name.
|
|
387
|
+
*
|
|
388
|
+
* @param input - Candidate name.
|
|
389
|
+
* @returns `true` when the value is a plausible name.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* validatePersonName("O'Brien"); // true
|
|
394
|
+
* validatePersonName("José García"); // true
|
|
395
|
+
* validatePersonName("Ольга Иванова"); // true
|
|
396
|
+
* validatePersonName("John123"); // false
|
|
397
|
+
* validatePersonName("<script>x</script>"); // false
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
declare function validatePersonName(input: string): boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Validate a human name field.
|
|
403
|
+
*
|
|
404
|
+
* @deprecated Renamed to {@link validatePersonName}. The old name implied a general
|
|
405
|
+
* "safe name" check and was implemented as one, running injection and homoglyph
|
|
406
|
+
* detectors against people's names. Behaviour now matches
|
|
407
|
+
* {@link validatePersonName}.
|
|
408
|
+
*
|
|
409
|
+
* @param input - Candidate name.
|
|
410
|
+
* @returns `true` when the value is a plausible name.
|
|
241
411
|
*/
|
|
242
412
|
declare function validateSafeName(input: string): boolean;
|
|
243
413
|
/**
|
|
244
|
-
*
|
|
414
|
+
* Validate an email address.
|
|
245
415
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* operators.
|
|
416
|
+
* Two checks: an RFC-shaped format match (length-bounded first, so the pattern never
|
|
417
|
+
* sees an unbounded string), and UTS #39 identifier analysis of the **domain**, where
|
|
418
|
+
* a mixed-script host is the IDN homograph attack — `аpple.com` with a Cyrillic `а`
|
|
419
|
+
* resolves somewhere else entirely.
|
|
251
420
|
*
|
|
252
|
-
*
|
|
421
|
+
* The local part is not confusable-checked: it is not a routable identifier, and
|
|
422
|
+
* flagging it would reject legitimate internationalized mailboxes.
|
|
423
|
+
*
|
|
424
|
+
* @param input - Candidate address.
|
|
425
|
+
* @returns `true` when the format is valid and the domain is not a script mix.
|
|
253
426
|
*/
|
|
254
427
|
declare function validateSafeEmail(input: string): boolean;
|
|
255
428
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
429
|
+
* Render a user-facing error message for a detection result.
|
|
430
|
+
*
|
|
431
|
+
* Uses only the **first** finding: enumerating every category that fired leaks the
|
|
432
|
+
* shape of the rule set to whoever is probing it. Log `result.threats` server-side for
|
|
433
|
+
* diagnostics and return this to the client.
|
|
434
|
+
*
|
|
435
|
+
* @param result - A {@link ThreatDetectionResult}, a `ThreatScanResult`-shaped object,
|
|
436
|
+
* or any `{ isSafe, threats }` pair.
|
|
437
|
+
* @returns A message, or `""` when the result is safe — so `message || undefined`
|
|
438
|
+
* works at a call site.
|
|
265
439
|
*/
|
|
266
|
-
declare function getThreatErrorMessage(result:
|
|
440
|
+
declare function getThreatErrorMessage(result: {
|
|
441
|
+
readonly isSafe: boolean;
|
|
442
|
+
readonly threats: readonly ThreatSummary[];
|
|
443
|
+
}): string;
|
|
267
444
|
//#endregion
|
|
268
|
-
export { THREAT_DETECTED_MESSAGE, ThreatDetectionConfig, ThreatDetectionResult, ThreatFinding, ThreatType, containsCommandInjection, containsHomoglyphs, containsNoSQLInjection, containsPathTraversal, containsSQLInjection, containsXSSPatterns, detectThreatPatterns, getThreatErrorMessage, isSafeInput, normalizeUnicode, sanitizeForDisplay, validateSafeEmail, validateSafeName, validateSafeText };
|
|
445
|
+
export { THREAT_DETECTED_MESSAGE, ThreatDetectionConfig, ThreatDetectionResult, type ThreatFinding, ThreatSummary, type ThreatType, containsCommandInjection, containsHomoglyphs, containsNoSQLInjection, containsPathTraversal, containsPrototypePollution, containsSQLInjection, containsXSSPatterns, detectThreatPatterns, encodeJsonForScript, encodeLogValue, escapeCsvField, escapeHtmlAttribute, escapeHtmlText, getThreatErrorMessage, isSafeInput, normalizeUnicode, sanitizeForDisplay, toCsvRow, validatePersonName, validateSafeEmail, validateSafeName, validateSafeText };
|
|
269
446
|
//# sourceMappingURL=validators.d.mts.map
|
package/lib/validators.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.mts","names":[],"sources":["../src/validators.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"validators.d.mts","names":[],"sources":["../src/validators.ts"],"mappings":";;;;;;;;;UAgDiB;;EAEhB;;EAEA,SAAS;;;;;;;;;UAUO;;WAEP,MAAM;;WAEN;;WAEA;;;;;;;;;;;;;UAkBO;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;;;iBAkDe,oBAAoB,gBAAgB;;;;;;;;;;;;iBAepC,2BAA2B,gBAAgB;;;;;;;;;;;iBAc3C,qBAAqB,gBAAgB;;;;;;;;iBAWrC,uBAAuB,gBAAgB;;;;;;;;;;;;iBAevC,yBAAyB,gBAAgB;;;;;;;;;;;;;iBAgBzC,sBAAsB,gBAAgB;;;;;;;;;;;;;;;iBA2CtC,mBAAmB,gBAAgB;;;;;;;;;;;;;;;iBA0CnC,qBACf,eACA,SAAQ,wBACN;;;;;;;;iBA8Ba,YAAY,eAAe,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCpC,eAAe;;;;;;;;;;;;;;;;;;;iBA4Cf,oBAAoB;;;;;;;;;;;;iBAwBpB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwDnB,eACf,eACA;WAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqEL,eACf,gBACA;WAAoB;;;;;;;;;;;;;;;iBA+BL,SACf,4BACA;WAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDL,oBAAoB;;;;;;;;;;;;;;;;iBAuCpB,iBAAiB;;;;;cAYpB;;;;;;;;iBASG,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoDjB,mBAAmB;;;;;;;;;;;;iBAyBnB,iBAAiB;;;;;;;;;;;;;;;iBAyBjB,kBAAkB;;;;;;;;;;;;;iBA2BlB,sBAAsB;WAC5B;WACA,kBAAkB"}
|