@resq-systems/security 1.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.
@@ -0,0 +1,269 @@
1
+ //#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
+ /**
18
+ * Outcome of {@link detectThreatPatterns}.
19
+ *
20
+ * `isSafe` is the boolean shortcut; `threats` is the full list of
21
+ * findings (one per detector that fired). Use
22
+ * {@link getThreatErrorMessage} to render a user-facing message for
23
+ * the first finding.
24
+ */
25
+ interface ThreatDetectionResult {
26
+ /** `true` when no detectors fired. Equivalent to `threats.length === 0`. */
27
+ isSafe: boolean;
28
+ /** All findings produced by enabled detectors, in detector order. */
29
+ threats: ThreatFinding[];
30
+ }
31
+ /**
32
+ * A single detector hit. Detectors that fire return at most one
33
+ * finding per call (one example is enough to reject the input).
34
+ */
35
+ interface ThreatFinding {
36
+ /** Which detector matched. */
37
+ type: ThreatType;
38
+ /** Human-readable description suitable for log lines (not for end users — use {@link getThreatErrorMessage} instead). */
39
+ description: string;
40
+ /** First 50 chars of the matching substring, for diagnostics. Truncated to prevent leaking large payloads in logs. */
41
+ matchedPattern?: string;
42
+ }
43
+ /**
44
+ * The closed set of threat categories the validators recognize. Add
45
+ * new categories here when adding a new detector.
46
+ */
47
+ type ThreatType = "xss" | "sql_injection" | "nosql_injection" | "command_injection" | "path_traversal" | "homoglyph";
48
+ /**
49
+ * Detect XSS-style payloads (script tags, event handlers, dangerous
50
+ * URI schemes, prototype pollution, …) in a UTF-8 input.
51
+ *
52
+ * Inputs longer than 100 000 characters are truncated before scanning
53
+ * to bound regex evaluation cost and prevent ReDoS on crafted
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.
57
+ *
58
+ * @param input - String to scan.
59
+ * @returns Empty array when nothing matches, or a single
60
+ * {@link ThreatFinding} of type `"xss"`.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * containsXSSPatterns(`<img src=x onerror="alert(1)">`);
65
+ * // → [{ type: "xss", description: "...", matchedPattern: "onerror=" }]
66
+ * ```
67
+ */
68
+ declare function containsXSSPatterns(input: string): ThreatFinding[];
69
+ /**
70
+ * Detect SQL-injection patterns (UNION SELECT, DROP TABLE,
71
+ * comment-based bypasses, always-true tautologies, stacked queries)
72
+ * in input.
73
+ *
74
+ * **Not a replacement for parameterised queries.** Use this as a
75
+ * defense-in-depth signal in addition to a properly bound prepared
76
+ * statement, never as the only barrier.
77
+ *
78
+ * @param input - String to scan. Truncated at 100 000 characters.
79
+ * @returns Empty array, or one finding of type `"sql_injection"`.
80
+ */
81
+ declare function containsSQLInjection(input: string): ThreatFinding[];
82
+ /**
83
+ * Detect NoSQL-injection patterns — Mongo-style operator injection
84
+ * (`$where`, `$ne`, `$regex`), JavaScript-in-query payloads, and
85
+ * structural manipulators that can bypass auth filters in document
86
+ * stores.
87
+ *
88
+ * @param input - String to scan.
89
+ * @returns Empty array, or one finding of type `"nosql_injection"`.
90
+ */
91
+ declare function containsNoSQLInjection(input: string): ThreatFinding[];
92
+ /**
93
+ * Detect shell command-injection patterns: command substitution
94
+ * (`$(...)`, backticks), chained dangerous commands (`; rm`, `; curl`,
95
+ * …) and shell-piped exec (`| sh`, `| bash`).
96
+ *
97
+ * **Off by default in {@link detectThreatPatterns}** — these patterns
98
+ * occasionally fire on legitimate user content. Enable explicitly
99
+ * (`checkCommandInjection: true`) only when input flows into a child
100
+ * process or shell.
101
+ *
102
+ * @param input - String to scan. Truncated at 100 000 characters.
103
+ * @returns Empty array, or one finding of type `"command_injection"`.
104
+ */
105
+ declare function containsCommandInjection(input: string): ThreatFinding[];
106
+ /**
107
+ * Detect path-traversal payloads — `../`, encoded dots, raw absolute
108
+ * paths trying to escape a base directory. Pair with `path.resolve()`
109
+ * + a `startsWith()` containment check on the canonicalised path
110
+ * before reading or writing the file.
111
+ *
112
+ * @param input - String to scan.
113
+ * @returns Empty array, or one finding of type `"path_traversal"`.
114
+ */
115
+ declare function containsPathTraversal(input: string): ThreatFinding[];
116
+ /**
117
+ * Detect lookalike Unicode characters (Cyrillic / Greek glyphs that
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.
121
+ *
122
+ * Use {@link normalizeUnicode} to *replace* homoglyphs with their
123
+ * ASCII equivalents — this function only flags their presence.
124
+ *
125
+ * @param input - String to scan.
126
+ * @returns Empty array, or one finding of type `"homoglyph"` (the
127
+ * first matched lookalike).
128
+ */
129
+ declare function containsHomoglyphs(input: string): ThreatFinding[];
130
+ /**
131
+ * Per-detector toggles for {@link detectThreatPatterns}.
132
+ *
133
+ * Defaults: XSS, SQL, NoSQL, path-traversal, and homoglyph detectors
134
+ * are **on**; command injection is **off** (false-positive prone).
135
+ * Pass `false` to disable a detector or `true` to force-enable
136
+ * `checkCommandInjection`.
137
+ */
138
+ interface ThreatDetectionConfig {
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
+ }
152
+ /**
153
+ * Run every enabled detector against `input` and aggregate findings.
154
+ *
155
+ * Returns early-but-not-immediately: each individual detector still
156
+ * runs to completion, but each detector returns at most one finding,
157
+ * so the aggregate threats array is small (≤ 6 entries).
158
+ *
159
+ * Non-string inputs (`null`, `undefined`, numbers, …) are treated as
160
+ * safe — wrap caller-side validation around this if you want to
161
+ * reject non-strings.
162
+ *
163
+ * @param input - The candidate string.
164
+ * @param config - Detector toggles. Defaults turn on everything
165
+ * except command-injection.
166
+ * @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
+ */
174
+ declare function detectThreatPatterns(input: string, config?: ThreatDetectionConfig): ThreatDetectionResult;
175
+ /**
176
+ * Boolean shortcut over {@link detectThreatPatterns} — discards the
177
+ * findings list when you only need a yes/no decision.
178
+ *
179
+ * @param input - String to test.
180
+ * @param config - Optional detector toggles.
181
+ * @returns `true` when no detector fires.
182
+ */
183
+ declare function isSafeInput(input: string, config?: ThreatDetectionConfig): boolean;
184
+ /**
185
+ * HTML-entity escape `&`, `<`, `>`, `"`, `'`, and `/` for safe
186
+ * insertion into HTML text and attribute contexts.
187
+ *
188
+ * **Limited scope.** This is appropriate for plain text destined for
189
+ * `textContent` or attribute values, not for unfiltered HTML
190
+ * rendering. For rich-text use a vetted sanitizer (DOMPurify on the
191
+ * client, sanitize-html or similar on the server).
192
+ *
193
+ * Returns `""` for non-string or empty input.
194
+ *
195
+ * @param input - Untrusted string.
196
+ * @returns Entity-escaped output safe to interpolate into HTML.
197
+ */
198
+ declare function sanitizeForDisplay(input: string): string;
199
+ /**
200
+ * Canonicalise a string for safe equality checks against ASCII.
201
+ *
202
+ * Two-pass:
203
+ * 1. Normalize to NFC (composed form) so combining-character
204
+ * sequences don't compare differently from their pre-composed
205
+ * counterparts.
206
+ * 2. Replace known homoglyphs (Cyrillic `А`, Greek `Ε`, …) with their
207
+ * ASCII equivalents (`A`, `E`, …).
208
+ *
209
+ * Use before storing user-controlled identifiers (usernames, domain
210
+ * names) and before comparing them to a denylist or to each other.
211
+ *
212
+ * Returns `""` for non-string or empty input.
213
+ *
214
+ * @param input - Raw string from an untrusted source.
215
+ * @returns ASCII-normalized, NFC-composed string.
216
+ */
217
+ declare function normalizeUnicode(input: string): string;
218
+ /**
219
+ * Generic user-facing fallback message. Render this verbatim when a
220
+ * detector fires but you don't want to expose which one. Prefer
221
+ * {@link getThreatErrorMessage} for category-specific messages.
222
+ */
223
+ declare const THREAT_DETECTED_MESSAGE = "Input contains potentially unsafe content";
224
+ /**
225
+ * Boolean refinement helper for use with `zod.string().refine(...)`,
226
+ * `effect/Schema.filter(...)`, or any predicate-based validator.
227
+ *
228
+ * Equivalent to `isSafeInput(input)` with default config.
229
+ */
230
+ declare function validateSafeText(input: string): boolean;
231
+ /**
232
+ * Refinement for human name fields. More permissive than
233
+ * {@link validateSafeText} — allows international letters,
234
+ * combining marks, hyphens, apostrophes, and spaces — but still
235
+ * rejects HTML/SQL/NoSQL injection patterns and homoglyph forgeries.
236
+ *
237
+ * Suitable for first/last/full-name inputs in registration forms.
238
+ *
239
+ * @returns `true` when the name passes both the threat detectors and
240
+ * the name-shape regex.
241
+ */
242
+ declare function validateSafeName(input: string): boolean;
243
+ /**
244
+ * Refinement for email fields. Combines:
245
+ *
246
+ * 1. RFC-style format check (length-bounded to ≤ 254 chars to
247
+ * prevent ReDoS).
248
+ * 2. XSS / SQL / NoSQL / homoglyph detectors — emails are extremely
249
+ * constrained and should never legitimately contain HTML or query
250
+ * operators.
251
+ *
252
+ * @returns `true` when both checks pass.
253
+ */
254
+ declare function validateSafeEmail(input: string): boolean;
255
+ /**
256
+ * Map a {@link ThreatDetectionResult} into a user-facing error
257
+ * message string suitable for an HTTP 400 response or form
258
+ * validation error. Returns `""` when the result is safe (so
259
+ * `error || undefined` works).
260
+ *
261
+ * Uses only the **first** finding for the message — exposing every
262
+ * threat type to the user can leak information about the detection
263
+ * rules. For full diagnostics, log `result.threats` server-side
264
+ * rather than returning them.
265
+ */
266
+ declare function getThreatErrorMessage(result: ThreatDetectionResult): string;
267
+ //#endregion
268
+ export { THREAT_DETECTED_MESSAGE, ThreatDetectionConfig, ThreatDetectionResult, ThreatFinding, ThreatType, containsCommandInjection, containsHomoglyphs, containsNoSQLInjection, containsPathTraversal, containsSQLInjection, containsXSSPatterns, detectThreatPatterns, getThreatErrorMessage, isSafeInput, normalizeUnicode, sanitizeForDisplay, validateSafeEmail, validateSafeName, validateSafeText };
269
+ //# sourceMappingURL=validators.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.d.mts","names":[],"sources":["../src/validators.ts"],"mappings":";;AAyKA;;;;;;;;;AAWA;;;;;;;;;;AAaA;;;UAxBiB,qBAAA;EAwBK;EAtBrB,MAAA;EAkDkC;EAhDlC,OAAA,EAAS,aAAA;AAAA;;AAgFV;;;UAzEiB,aAAA;EAyEiD;EAvEjE,IAAA,EAAM,UAAA;EAmG+B;EAjGrC,WAAA;EAiGsC;EA/FtC,cAAA;AAAA;;;;;KAOW,UAAA;;;;;AA2LZ;;;;;AA+BA;;;;;;;;;;;iBA9LgB,mBAAA,CAAoB,KAAA,WAAgB,aAAA;AA4OpD;;;;;;;;;;AAgDA;;AAhDA,iBA5MgB,oBAAA,CAAqB,KAAA,WAAgB,aAAA;;;;;;;AA8QrD;;;iBAlPgB,sBAAA,CAAuB,KAAA,WAAgB,aAAA;;AAgRvD;;;;;AAyBA;;;;;AAQA;;iBAlRgB,wBAAA,CAAyB,KAAA,WAAgB,aAAA;;;AAiSzD;;;;;AA0BA;;iBAtRgB,qBAAA,CAAsB,KAAA,WAAgB,aAAA;;;AAwTtD;;;;;;;;;;;iBAzRgB,kBAAA,CAAmB,KAAA,WAAgB,aAAA;;;;;;;;;UA+BlC,qBAAA;;EAEhB,QAAA;;EAEA,iBAAA;;EAEA,mBAAA;;EAEA,qBAAA;;EAEA,kBAAA;;EAEA,eAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;iBAkCe,oBAAA,CACf,KAAA,UACA,MAAA,GAAQ,qBAAA,GACN,qBAAA;;;;;;;;;iBA6Ca,WAAA,CAAY,KAAA,UAAe,MAAA,GAAS,qBAAA;;;;;;;;;;;;;;;iBAkBpC,kBAAA,CAAmB,KAAA;;;;;;;;;;;;;;;;;;;iBA8BnB,gBAAA,CAAiB,KAAA;;;;;;cAyBpB,uBAAA;;;;;;;iBAQG,gBAAA,CAAiB,KAAA;;;;;;;;;;;;iBAejB,gBAAA,CAAiB,KAAA;;;;;;;;;;;;iBA0BjB,iBAAA,CAAkB,KAAA;;;;;;;;;;;;iBAkClB,qBAAA,CAAsB,MAAA,EAAQ,qBAAA"}