@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.
- package/README.md +280 -0
- package/lib/crypto.d.mts +195 -0
- package/lib/crypto.d.mts.map +1 -0
- package/lib/crypto.mjs +294 -0
- package/lib/crypto.mjs.map +1 -0
- package/lib/index.d.mts +4 -0
- package/lib/index.mjs +4 -0
- package/lib/sanitize.d.mts +316 -0
- package/lib/sanitize.d.mts.map +1 -0
- package/lib/sanitize.mjs +529 -0
- package/lib/sanitize.mjs.map +1 -0
- package/lib/validators.d.mts +269 -0
- package/lib/validators.d.mts.map +1 -0
- package/lib/validators.mjs +487 -0
- package/lib/validators.mjs.map +1 -0
- package/package.json +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 ResQ Systems, Inc.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# @resq-systems/security
|
|
18
|
+
|
|
19
|
+
[](https://www.npmjs.com/package/@resq-systems/security)
|
|
20
|
+
[](../../LICENSE.md)
|
|
21
|
+
|
|
22
|
+
> Encryption, threat detection, input validation, PII sanitization, and Effect Schema validators.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun add @resq-systems/security effect
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Peer dependency: `effect`. Uses Node.js `crypto` module for encryption.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { encryptData, decryptData, isSafeInput, escapeHtml, redactPII } from "@resq-systems/security";
|
|
36
|
+
|
|
37
|
+
// Encrypt/decrypt
|
|
38
|
+
const encrypted = await encryptData("sensitive", "my-secret-key");
|
|
39
|
+
const decrypted = await decryptData(encrypted, "my-secret-key");
|
|
40
|
+
|
|
41
|
+
// Validate input
|
|
42
|
+
if (!isSafeInput(userInput)) {
|
|
43
|
+
return new Response("Invalid input", { status: 400 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Sanitize for display
|
|
47
|
+
const safe = escapeHtml('<script>alert("xss")</script>');
|
|
48
|
+
|
|
49
|
+
// Redact PII for logging
|
|
50
|
+
const clean = redactPII("Email: john@example.com, SSN: 123-45-6789");
|
|
51
|
+
// "Email: [EMAIL], SSN: [SSN]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### Encryption (`crypto.ts`)
|
|
57
|
+
|
|
58
|
+
#### `encryptData(plaintext, encryptionKey): Promise<string>`
|
|
59
|
+
|
|
60
|
+
Encrypts data using AES-256-GCM with scrypt key derivation.
|
|
61
|
+
|
|
62
|
+
- **plaintext** (`string`) -- data to encrypt.
|
|
63
|
+
- **encryptionKey** (`string`) -- encryption key/password.
|
|
64
|
+
- Returns base64 string containing `salt:iv:authTag:ciphertext`.
|
|
65
|
+
|
|
66
|
+
#### `decryptData(encryptedData, encryptionKey): Promise<string>`
|
|
67
|
+
|
|
68
|
+
Decrypts data produced by `encryptData`.
|
|
69
|
+
|
|
70
|
+
- **encryptedData** (`string`) -- base64-encoded encrypted data.
|
|
71
|
+
- **encryptionKey** (`string`) -- same key used for encryption.
|
|
72
|
+
- Returns the original plaintext string.
|
|
73
|
+
|
|
74
|
+
#### `hashData(data): string`
|
|
75
|
+
|
|
76
|
+
Hashes data using SHA-256. Non-reversible.
|
|
77
|
+
|
|
78
|
+
- Returns hex-encoded hash string.
|
|
79
|
+
|
|
80
|
+
#### `generateSecureToken(length?): string`
|
|
81
|
+
|
|
82
|
+
Generates a cryptographically secure random token.
|
|
83
|
+
|
|
84
|
+
- **length** (`number`, default `32`) -- byte length.
|
|
85
|
+
- Returns hex string (2x the byte length).
|
|
86
|
+
|
|
87
|
+
#### `maskPII(data): string`
|
|
88
|
+
|
|
89
|
+
Masks a string, showing first 2 and last 2 characters (e.g. `"Al****ce"`). Returns `"****"` for strings <= 4 chars.
|
|
90
|
+
|
|
91
|
+
#### `maskEmail(email): string`
|
|
92
|
+
|
|
93
|
+
Masks email local part (e.g. `"j***n@example.com"`).
|
|
94
|
+
|
|
95
|
+
#### `sanitizeForLogging(obj, sensitiveFields?): Partial<T>`
|
|
96
|
+
|
|
97
|
+
Recursively redacts sensitive fields from an object for safe logging.
|
|
98
|
+
|
|
99
|
+
- **sensitiveFields** (`string[]`, default: `["password", "passwordHash", "token", "secret", "twoFactorSecret", "apiKey"]`)
|
|
100
|
+
- Email fields are automatically masked.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
sanitizeForLogging({ password: "secret", email: "john@example.com", name: "John" });
|
|
104
|
+
// { password: "[REDACTED]", email: "j***n@example.com", name: "John" }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Threat Detection (`validators.ts`)
|
|
108
|
+
|
|
109
|
+
#### `detectThreatPatterns(input, config?): ThreatDetectionResult`
|
|
110
|
+
|
|
111
|
+
Runs all configured detectors on input.
|
|
112
|
+
|
|
113
|
+
- Returns `{ isSafe: boolean, threats: ThreatFinding[] }`.
|
|
114
|
+
|
|
115
|
+
| Config Option | Type | Default | Description |
|
|
116
|
+
|---------------|------|---------|-------------|
|
|
117
|
+
| `checkXSS` | `boolean` | `true` | Detect script injection, event handlers |
|
|
118
|
+
| `checkSQLInjection` | `boolean` | `true` | Detect UNION, DROP, stacked queries |
|
|
119
|
+
| `checkNoSQLInjection` | `boolean` | `true` | Detect MongoDB operators |
|
|
120
|
+
| `checkCommandInjection` | `boolean` | `false` | Detect shell commands (can cause false positives) |
|
|
121
|
+
| `checkPathTraversal` | `boolean` | `true` | Detect `../`, `%2e%2e`, null bytes |
|
|
122
|
+
| `checkHomoglyphs` | `boolean` | `true` | Detect Unicode lookalike characters |
|
|
123
|
+
|
|
124
|
+
#### `isSafeInput(input, config?): boolean`
|
|
125
|
+
|
|
126
|
+
Quick check returning `true` if no threats are detected.
|
|
127
|
+
|
|
128
|
+
#### Individual Detectors
|
|
129
|
+
|
|
130
|
+
Each returns `ThreatFinding[]`:
|
|
131
|
+
|
|
132
|
+
| Function | Detects |
|
|
133
|
+
|----------|---------|
|
|
134
|
+
| `containsXSSPatterns(input)` | Script tags, event handlers, `javascript:` URIs, `eval()` |
|
|
135
|
+
| `containsSQLInjection(input)` | UNION SELECT, DROP TABLE, `1=1`, SLEEP, stacked queries |
|
|
136
|
+
| `containsNoSQLInjection(input)` | MongoDB operators (`$gt`, `$where`, `$function`) |
|
|
137
|
+
| `containsCommandInjection(input)` | Command substitution, piped shells |
|
|
138
|
+
| `containsPathTraversal(input)` | Directory traversal, null bytes, sensitive paths |
|
|
139
|
+
| `containsHomoglyphs(input)` | Cyrillic/Greek lookalike characters |
|
|
140
|
+
|
|
141
|
+
#### `sanitizeForDisplay(input): string`
|
|
142
|
+
|
|
143
|
+
Escapes HTML entities (`<`, `>`, `&`, `"`, `'`, `/`) for safe rendering.
|
|
144
|
+
|
|
145
|
+
#### `normalizeUnicode(input): string`
|
|
146
|
+
|
|
147
|
+
Normalizes to NFC form and replaces known homoglyphs with ASCII equivalents.
|
|
148
|
+
|
|
149
|
+
#### `validateSafeText(input): boolean`
|
|
150
|
+
|
|
151
|
+
Validates text is safe from all attack patterns. For use as a schema refinement.
|
|
152
|
+
|
|
153
|
+
#### `validateSafeName(input): boolean`
|
|
154
|
+
|
|
155
|
+
Validates a name field -- allows international characters but blocks injection patterns.
|
|
156
|
+
|
|
157
|
+
#### `validateSafeEmail(input): boolean`
|
|
158
|
+
|
|
159
|
+
Validates email format and checks for injection patterns.
|
|
160
|
+
|
|
161
|
+
#### `getThreatErrorMessage(result): string`
|
|
162
|
+
|
|
163
|
+
Returns a human-readable error message for a threat detection result.
|
|
164
|
+
|
|
165
|
+
### Sanitization (`sanitize.ts`)
|
|
166
|
+
|
|
167
|
+
#### `escapeHtml(text): string`
|
|
168
|
+
|
|
169
|
+
Escapes `&`, `<`, `>`, `"`, `'` to HTML entities.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
escapeHtml('<img onerror="alert(1)">'); // "<img onerror="alert(1)">"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### `sanitizeUrl(url, allowedProtocols?): string`
|
|
176
|
+
|
|
177
|
+
Validates and returns a safe URL; returns empty string if unsafe.
|
|
178
|
+
|
|
179
|
+
- **allowedProtocols** (`string[]`, default: `["http:", "https:", "mailto:"]`).
|
|
180
|
+
- Blocks `javascript:`, `data:` URIs.
|
|
181
|
+
|
|
182
|
+
#### `sanitizeUrlEffect(url, allowedProtocols?): Exit<string, unknown>`
|
|
183
|
+
|
|
184
|
+
Effect-based version returning an `Exit`.
|
|
185
|
+
|
|
186
|
+
#### `validateUserInput(input, maxLength?, allowHtml?): string`
|
|
187
|
+
|
|
188
|
+
Strips HTML tags, normalizes whitespace, removes dangerous patterns, and truncates.
|
|
189
|
+
|
|
190
|
+
- **maxLength** (`number`, default `500`).
|
|
191
|
+
- **allowHtml** (`boolean`, default `false`).
|
|
192
|
+
|
|
193
|
+
#### `validateUserInputEffect(input, options?): Exit<string, unknown>`
|
|
194
|
+
|
|
195
|
+
Effect-based version with full options.
|
|
196
|
+
|
|
197
|
+
| Option | Type | Default | Description |
|
|
198
|
+
|--------|------|---------|-------------|
|
|
199
|
+
| `maxLength` | `number` | `500` | Max output length |
|
|
200
|
+
| `allowHtml` | `boolean` | `false` | Preserve HTML tags |
|
|
201
|
+
| `allowNewlines` | `boolean` | `false` | Preserve newlines |
|
|
202
|
+
| `trimWhitespace` | `boolean` | `true` | Trim leading/trailing whitespace |
|
|
203
|
+
|
|
204
|
+
#### `sanitizeJson<T>(jsonString): T | null`
|
|
205
|
+
|
|
206
|
+
Safely parses JSON with prototype pollution protection. Removes `__proto__`, `constructor`, `prototype` keys.
|
|
207
|
+
|
|
208
|
+
#### `parseJsonWithSchema<A>(jsonString, schema): Option<A>`
|
|
209
|
+
|
|
210
|
+
Parses JSON with Effect Schema validation and prototype pollution protection. Returns `Option.some(value)` or `Option.none()`.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const UserSchema = Schema.Struct({ name: Schema.String, age: Schema.Number });
|
|
214
|
+
const user = parseJsonWithSchema('{"name":"John","age":30}', UserSchema);
|
|
215
|
+
// Option.some({ name: "John", age: 30 })
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `stripAnsi(text): string`
|
|
219
|
+
|
|
220
|
+
Removes ANSI escape codes from strings.
|
|
221
|
+
|
|
222
|
+
#### `redactPII(text, options?): string`
|
|
223
|
+
|
|
224
|
+
Replaces PII patterns with redaction markers.
|
|
225
|
+
|
|
226
|
+
| Option | Type | Default | Marker |
|
|
227
|
+
|--------|------|---------|--------|
|
|
228
|
+
| `redactEmails` | `boolean` | `true` | `[EMAIL]` |
|
|
229
|
+
| `redactPhones` | `boolean` | `true` | `[PHONE]` |
|
|
230
|
+
| `redactSSN` | `boolean` | `true` | `[SSN]` |
|
|
231
|
+
| `redactCreditCards` | `boolean` | `true` | `[CREDIT_CARD]` |
|
|
232
|
+
| `redactIPs` | `boolean` | `true` | `[IP_ADDRESS]` |
|
|
233
|
+
| `customPatterns` | `Array<{ pattern, replacement }>` | `[]` | custom |
|
|
234
|
+
|
|
235
|
+
#### `safeStringify(obj, sensitiveKeys?, indent?): string`
|
|
236
|
+
|
|
237
|
+
JSON.stringify with automatic redaction of sensitive keys.
|
|
238
|
+
|
|
239
|
+
- **sensitiveKeys** (`string[]`, default: `["password", "token", "apiKey", "secret", "authorization", "cookie", "ssn", "creditCard"]`).
|
|
240
|
+
|
|
241
|
+
### Validation Helpers
|
|
242
|
+
|
|
243
|
+
| Function | Description |
|
|
244
|
+
|----------|-------------|
|
|
245
|
+
| `isValidEmail(email)` | Validates email format |
|
|
246
|
+
| `isValidPhone(phone)` | Validates US phone format |
|
|
247
|
+
| `isValidSSN(ssn)` | Validates US SSN format |
|
|
248
|
+
| `isValidUrl(url)` | Validates safe URL |
|
|
249
|
+
|
|
250
|
+
### Effect Schemas
|
|
251
|
+
|
|
252
|
+
Exported for runtime validation: `SafeUrlSchema`, `EmailSchema`, `PhoneNumberSchema`, `SSNSchema`, `CreditCardSchema`, `IPv4Schema`, `SanitizedStringSchema`, `UrlProtocolSchema`, `PIIRedactionOptionsSchema`, `UserInputOptionsSchema`.
|
|
253
|
+
|
|
254
|
+
### Types
|
|
255
|
+
|
|
256
|
+
Exported types: `ThreatDetectionResult`, `ThreatFinding`, `ThreatType`, `ThreatDetectionConfig`, `PIIRedactionOptions`, `UserInputOptions`, `SafeUrl`, `Email`, `PhoneNumber`, `SSN`, `CreditCard`, `IPv4`, `UrlProtocol`.
|
|
257
|
+
|
|
258
|
+
## Prerequisites
|
|
259
|
+
|
|
260
|
+
- **Runtime**: Bun 1.1+ or Node.js 20+
|
|
261
|
+
- **Peer Dependencies**: `effect` (v4.0.0-beta.93+)
|
|
262
|
+
|
|
263
|
+
## Configuration
|
|
264
|
+
|
|
265
|
+
- **Crypto Key**: Ensure `ENCRYPTION_KEY` is set for cryptographic modules (must be a valid hex string of proper bit length).
|
|
266
|
+
|
|
267
|
+
## Testing
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
bun --filter @resq-systems/security test
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Troubleshooting
|
|
274
|
+
|
|
275
|
+
- **Strict JSON parsing error**: Stricter `ts-reset` settings type `JSON.parse` as `unknown`. Use the provided `sanitizeJson` / `parseJsonWithSchema` wrapper helpers to cast to safe formats.
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
Apache-2.0
|
package/lib/crypto.d.mts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Brand, PositiveInt } from "@resq-systems/types";
|
|
2
|
+
|
|
3
|
+
//#region src/crypto.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Base64 AES-256-GCM payload produced by {@link encryptData} — the
|
|
6
|
+
* `salt | iv | authTag | ciphertext` envelope. Only {@link decryptData}
|
|
7
|
+
* should consume a value of this type; read one back from storage through
|
|
8
|
+
* {@link toCiphertext}.
|
|
9
|
+
*/
|
|
10
|
+
type Ciphertext = Brand<string, "Ciphertext">;
|
|
11
|
+
/**
|
|
12
|
+
* A secret accepted by {@link encryptData}/{@link decryptData} as the
|
|
13
|
+
* scrypt password. Mint one at the boundary where the secret enters the
|
|
14
|
+
* process (typically from `process.env`) via {@link toEncryptionKey}.
|
|
15
|
+
*/
|
|
16
|
+
type EncryptionKey = Brand<string, "EncryptionKey">;
|
|
17
|
+
/** Cryptographically random hex token minted by {@link generateSecureToken}. */
|
|
18
|
+
type SecureToken = Brand<string, "SecureToken">;
|
|
19
|
+
/** Lowercase 64-char SHA-256 hex digest produced by {@link hashData}. */
|
|
20
|
+
type Sha256Hex = Brand<string, "Sha256Hex">;
|
|
21
|
+
/** A PII string masked for safe logging by {@link maskPII}/{@link maskEmail}. */
|
|
22
|
+
type Masked = Brand<string, "Masked">;
|
|
23
|
+
/** Type guard: `true` when `value` is a usable {@link EncryptionKey}. */
|
|
24
|
+
declare const isEncryptionKey: (value: string) => value is Brand<string, "EncryptionKey">;
|
|
25
|
+
/** Assert `value` is a non-empty secret and brand it, throwing otherwise. */
|
|
26
|
+
declare const toEncryptionKey: (value: string) => Brand<string, "EncryptionKey">;
|
|
27
|
+
/** Return `value` branded as an {@link EncryptionKey}, or `null` when empty. */
|
|
28
|
+
declare const coerceEncryptionKey: (value: string) => Brand<string, "EncryptionKey">;
|
|
29
|
+
/** Brand `value` as an {@link EncryptionKey} without checking. */
|
|
30
|
+
declare const unsafeEncryptionKey: (value: string) => Brand<string, "EncryptionKey">;
|
|
31
|
+
/** Type guard: `true` when `value` is a well-formed {@link Ciphertext} envelope. */
|
|
32
|
+
declare const isCiphertext: (value: string) => value is Brand<string, "Ciphertext">;
|
|
33
|
+
/** Assert `value` is a well-formed envelope and brand it, throwing otherwise. */
|
|
34
|
+
declare const toCiphertext: (value: string) => Brand<string, "Ciphertext">;
|
|
35
|
+
/** Return `value` branded as a {@link Ciphertext}, or `null` when malformed. */
|
|
36
|
+
declare const coerceCiphertext: (value: string) => Brand<string, "Ciphertext">;
|
|
37
|
+
/** Brand `value` as a {@link Ciphertext} without checking. */
|
|
38
|
+
declare const unsafeCiphertext: (value: string) => Brand<string, "Ciphertext">;
|
|
39
|
+
/**
|
|
40
|
+
* Encrypt a UTF-8 string with AES-256-GCM authenticated encryption.
|
|
41
|
+
*
|
|
42
|
+
* Each call generates a fresh random salt and IV — the same plaintext
|
|
43
|
+
* encrypted twice with the same `encryptionKey` produces different
|
|
44
|
+
* ciphertexts, which is the property you want for at-rest encryption.
|
|
45
|
+
*
|
|
46
|
+
* Output layout (base64-encoded): `salt(32) | iv(16) | authTag(16) | ciphertext(*)`.
|
|
47
|
+
* The companion {@link decryptData} understands this layout.
|
|
48
|
+
*
|
|
49
|
+
* @param plaintext - UTF-8 string to encrypt.
|
|
50
|
+
* @param encryptionKey - Caller-supplied secret. Treated as a password
|
|
51
|
+
* and stretched into a 256-bit AES key via scrypt; can be any length,
|
|
52
|
+
* though a high-entropy secret (≥ 32 bytes) is strongly preferred.
|
|
53
|
+
*
|
|
54
|
+
* @returns A self-contained base64 string. Store or transmit verbatim;
|
|
55
|
+
* the salt/IV are recovered on decryption.
|
|
56
|
+
*
|
|
57
|
+
* @throws From the underlying Node crypto primitives if `encryptionKey`
|
|
58
|
+
* is empty or scrypt fails.
|
|
59
|
+
*
|
|
60
|
+
* @compliance NIST 800-53 SC-28 (Protection of Information at Rest),
|
|
61
|
+
* SC-13 (Cryptographic Protection).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const ct = await encryptData("user@example.com", process.env.PII_KEY!);
|
|
66
|
+
* await db.users.update(id, { email: ct });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
declare function encryptData(plaintext: string, encryptionKey: EncryptionKey): Promise<Ciphertext>;
|
|
70
|
+
/**
|
|
71
|
+
* Reverse {@link encryptData}. Verifies the GCM authentication tag
|
|
72
|
+
* before returning plaintext — tampered ciphertexts throw.
|
|
73
|
+
*
|
|
74
|
+
* @param encryptedData - Base64 string produced by {@link encryptData}.
|
|
75
|
+
* @param encryptionKey - Same key/password used to encrypt. Wrong keys
|
|
76
|
+
* throw an "Unsupported state or unable to authenticate data" error
|
|
77
|
+
* from Node — the authenticated tag failure is indistinguishable from
|
|
78
|
+
* tampering, by design.
|
|
79
|
+
*
|
|
80
|
+
* @returns The original UTF-8 plaintext.
|
|
81
|
+
*
|
|
82
|
+
* @throws Error if the tag does not verify (wrong key, modified
|
|
83
|
+
* ciphertext, truncated payload). Catch this and treat it as a
|
|
84
|
+
* security event, not a recoverable error.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const plaintext = await decryptData(stored, process.env.PII_KEY!);
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
declare function decryptData(encryptedData: Ciphertext, encryptionKey: EncryptionKey): Promise<string>;
|
|
92
|
+
/**
|
|
93
|
+
* Compute a SHA-256 digest of a UTF-8 string and return it as lowercase
|
|
94
|
+
* hex.
|
|
95
|
+
*
|
|
96
|
+
* **Not for password storage.** SHA-256 is fast by design — use a
|
|
97
|
+
* deliberately slow KDF (`bcrypt`, `argon2`, or `scrypt`) for
|
|
98
|
+
* password-equivalent material. This helper is intended for
|
|
99
|
+
* non-reversible identifiers, content hashes, and idempotency keys.
|
|
100
|
+
*
|
|
101
|
+
* @param data - UTF-8 input.
|
|
102
|
+
* @returns 64-character lowercase hex digest.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* hashData("hello"); // → "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare function hashData(data: string): Sha256Hex;
|
|
110
|
+
/**
|
|
111
|
+
* Generate a cryptographically random hex token suitable for session
|
|
112
|
+
* IDs, password-reset tokens, CSRF tokens, and similar single-use
|
|
113
|
+
* secrets.
|
|
114
|
+
*
|
|
115
|
+
* @param length - Number of random *bytes* to draw as a {@link PositiveInt}
|
|
116
|
+
* (the returned hex string is twice as long). Default `32` ⇒ 64-char hex
|
|
117
|
+
* / 256 bits of entropy. Construct non-default lengths with `toPositiveInt`
|
|
118
|
+
* so zero-byte and negative lengths are unrepresentable.
|
|
119
|
+
* @returns A {@link SecureToken}: lowercase hex string of length `length * 2`.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { toPositiveInt } from "@resq-systems/types";
|
|
124
|
+
* generateSecureToken(); // 64-char hex (256-bit entropy)
|
|
125
|
+
* generateSecureToken(toPositiveInt(16)); // 32-char hex (128-bit entropy)
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function generateSecureToken(length?: PositiveInt): SecureToken;
|
|
129
|
+
/**
|
|
130
|
+
* Mask an arbitrary PII string for safe logging — keeps the first two
|
|
131
|
+
* and last two characters and replaces everything in between with
|
|
132
|
+
* asterisks. Strings of length ≤ 4 are fully masked as `"****"`.
|
|
133
|
+
*
|
|
134
|
+
* @param data - Raw PII string.
|
|
135
|
+
* @returns Masked representation safe for logs.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* maskPII("4242424242424242"); // → "42************42"
|
|
140
|
+
* maskPII("AB12"); // → "****"
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
declare function maskPII(data: string): Masked;
|
|
144
|
+
/**
|
|
145
|
+
* Mask an email address while preserving the domain — useful for
|
|
146
|
+
* deduplication and support workflows where the domain is non-PII but
|
|
147
|
+
* the local part identifies the user.
|
|
148
|
+
*
|
|
149
|
+
* @param email - Full email. Falls back to {@link maskPII} if the input
|
|
150
|
+
* does not contain a valid `local@domain` shape.
|
|
151
|
+
* @returns Masked email; e.g. `"j*****e@example.com"`.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* maskEmail("jane@example.com"); // → "j**e@example.com"
|
|
156
|
+
* maskEmail("ab@example.com"); // → "**@example.com"
|
|
157
|
+
* maskEmail("not-an-email"); // → "no********il" (maskPII fallback)
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function maskEmail(email: string): Masked;
|
|
161
|
+
/**
|
|
162
|
+
* Recursively shallow-copy an object, replacing any field whose key
|
|
163
|
+
* contains a sensitive substring (case-insensitive) with `[REDACTED]`,
|
|
164
|
+
* and masking string fields whose key contains `"email"` via
|
|
165
|
+
* {@link maskEmail}.
|
|
166
|
+
*
|
|
167
|
+
* Designed for log structures — preserves shape so log queries continue
|
|
168
|
+
* to work, but ensures secrets and identifiers don't leak. Use as a
|
|
169
|
+
* defensive layer **before** writing structured log lines.
|
|
170
|
+
*
|
|
171
|
+
* @param obj - Object to sanitize. Original is not mutated.
|
|
172
|
+
* @param sensitiveFields - Substring allow-list. Defaults to
|
|
173
|
+
* `["password", "passwordHash", "token", "secret",
|
|
174
|
+
* "twoFactorSecret", "apiKey"]`. Substrings match anywhere in the
|
|
175
|
+
* key, e.g. `"token"` matches `"refreshToken"` and `"id_token"`.
|
|
176
|
+
*
|
|
177
|
+
* @returns A new object with sensitive fields redacted and emails
|
|
178
|
+
* masked. Nested objects are recursed; arrays and primitives pass
|
|
179
|
+
* through unchanged.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* sanitizeForLogging({
|
|
184
|
+
* id: 1,
|
|
185
|
+
* email: "u@x.com",
|
|
186
|
+
* apiKey: "sk-...",
|
|
187
|
+
* nested: { token: "..." },
|
|
188
|
+
* });
|
|
189
|
+
* // → { id: 1, email: "u@x.com" (masked), apiKey: "[REDACTED]", nested: { token: "[REDACTED]" } }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare function sanitizeForLogging(obj: Record<string, unknown>, sensitiveFields?: string[]): Record<string, unknown>;
|
|
193
|
+
//#endregion
|
|
194
|
+
export { Ciphertext, EncryptionKey, Masked, SecureToken, Sha256Hex, coerceCiphertext, coerceEncryptionKey, decryptData, encryptData, generateSecureToken, hashData, isCiphertext, isEncryptionKey, maskEmail, maskPII, sanitizeForLogging, toCiphertext, toEncryptionKey, unsafeCiphertext, unsafeEncryptionKey };
|
|
195
|
+
//# sourceMappingURL=crypto.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.mts","names":[],"sources":["../src/crypto.ts"],"mappings":";;;;AA8FA;;;;;KAjCY,UAAA,GAAa,KAAA;;;;AAmCzB;;KA5BY,aAAA,GAAgB,KAAA;;KAGhB,WAAA,GAAc,KAAA;AA2B1B;AAAA,KAxBY,SAAA,GAAY,KAAA;;KAGZ,MAAA,GAAS,KAAA;;cAiBR,eAAA,GAAe,KAAA,aAAA,KAAA,IAAA,KAAA;;cAEf,eAAA,GAAe,KAAA,aAAA,KAAA;;cAEf,mBAAA,GAAmB,KAAA,aAAA,KAAA;AAgBhC;AAAA,cAda,mBAAA,GAAmB,KAAA,aAAA,KAAA;;cAcnB,YAAA,GAAY,KAAA,aAAA,KAAA,IAAA,KAAA;;cAEZ,YAAA,GAAY,KAAA,aAAA,KAAA;;cAEZ,gBAAA,GAAgB,KAAA,aAAA,KAAA;;cAEhB,gBAAA,GAAgB,KAAA,aAAA,KAAA;;;;;AAF7B;;;;;AAEA;;;;;AA8CA;;;;;;;;;;;;;;;AAqCA;iBArCsB,WAAA,CACrB,SAAA,UACA,aAAA,EAAe,aAAA,GACb,OAAA,CAAQ,UAAA;;;;;;;;;;;;;;AA0EX;;;;;AAsBA;;;iBA9DsB,WAAA,CACrB,aAAA,EAAe,UAAA,EACf,aAAA,EAAe,aAAA,GACb,OAAA;;;;;;AA6EH;;;;;AAyBA;;;;;AA2CA;;iBA5GgB,QAAA,CAAS,IAAA,WAAe,SAAA;;;;;;;;;;;;;;;;;;;iBAsBxB,mBAAA,CAAoB,MAAA,GAAQ,WAAA,GAAkC,WAAA;;;;;;;;;;;;;;;iBAkB9D,OAAA,CAAQ,IAAA,WAAe,MAAA;;;;;;;;;;;;;;;;;iBAyBvB,SAAA,CAAU,KAAA,WAAgB,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2C1B,kBAAA,CACf,GAAA,EAAK,MAAA,mBACL,eAAA,cAQE,MAAA"}
|