@zudojs/errors 1.0.0 → 1.0.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.
|
@@ -19,9 +19,21 @@ export const REDACTED_METADATA_VALUE = "[REDACTED]";
|
|
|
19
19
|
export function isForbiddenMetadataKey(key) {
|
|
20
20
|
return FORBIDDEN_METADATA_KEYS.has(key);
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns a copy of `pattern` without the `g` and `y` flags.
|
|
24
|
+
*
|
|
25
|
+
* Those flags make `RegExp.prototype.test` advance `lastIndex`, so a shared
|
|
26
|
+
* sensitive-key pattern would redact a key on one call and let the same key
|
|
27
|
+
* through on the next. Every key check in this module goes through here.
|
|
28
|
+
*/
|
|
29
|
+
function statelessPattern(pattern) {
|
|
30
|
+
if (!pattern.global && !pattern.sticky)
|
|
31
|
+
return pattern;
|
|
32
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
|
|
33
|
+
}
|
|
22
34
|
/** Returns whether a metadata key looks like it carries a secret. */
|
|
23
35
|
export function isSensitiveMetadataKey(key, pattern = SENSITIVE_METADATA_KEY_PATTERN) {
|
|
24
|
-
return pattern.test(key);
|
|
36
|
+
return statelessPattern(pattern).test(key);
|
|
25
37
|
}
|
|
26
38
|
/** Returns whether a value is a plain object (Object.prototype or null prototype). */
|
|
27
39
|
function isPlainObject(value) {
|
|
@@ -286,7 +298,7 @@ function sanitizeValue(value, seen, depth) {
|
|
|
286
298
|
export function redactErrorMetadata(metadata, options = {}) {
|
|
287
299
|
if (metadata === undefined || metadata === null)
|
|
288
300
|
return Object.freeze({});
|
|
289
|
-
const pattern = options.sensitiveKeyPattern ?? SENSITIVE_METADATA_KEY_PATTERN;
|
|
301
|
+
const pattern = statelessPattern(options.sensitiveKeyPattern ?? SENSITIVE_METADATA_KEY_PATTERN);
|
|
290
302
|
const extraKeys = new Set((options.keys ?? []).map((key) => key.toLowerCase()));
|
|
291
303
|
const replacement = options.replacement ?? REDACTED_METADATA_VALUE;
|
|
292
304
|
const isSensitive = (key) => pattern.test(key) || extraKeys.has(key.toLowerCase());
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Converts errors into safe, predictable serialized structures.
|
|
3
3
|
*/
|
|
4
4
|
import { BaseError } from "../base/core/baseError.core.js";
|
|
5
|
-
import { pickErrorMetadata, redactErrorMetadata, } from "../base/core/errorMetadata.core.js";
|
|
5
|
+
import { isSensitiveMetadataKey, pickErrorMetadata, redactErrorMetadata, REDACTED_METADATA_VALUE, } from "../base/core/errorMetadata.core.js";
|
|
6
6
|
import { normalizeUnknownError } from "./errorSerializer.factory.js";
|
|
7
7
|
export { createErrorSerializer, serializeError, serializePublicError, normalizeUnknownError, } from "./errorSerializer.factory.js";
|
|
8
8
|
/** Converts errors into safe, predictable serialized structures. */
|
|
@@ -84,10 +84,16 @@ export class ErrorSerializer {
|
|
|
84
84
|
if (isSerializedBaseError(record)) {
|
|
85
85
|
return this.serializeLevel(record);
|
|
86
86
|
}
|
|
87
|
-
// Native error shape: { name, message, stack?, cause? }
|
|
87
|
+
// Native error shape: { name, message, stack?, cause? } — or an
|
|
88
|
+
// arbitrary plain object that was thrown/attached as a cause. The
|
|
89
|
+
// latter reaches here by reference and used to be copied verbatim, so
|
|
90
|
+
// `redactSensitiveData` did not apply to it.
|
|
88
91
|
const { stack, cause: nested, ...rest } = record;
|
|
92
|
+
const fields = this.redactSensitiveData
|
|
93
|
+
? redactCauseFields(rest, this.sensitiveKeyPattern, new WeakSet([record]))
|
|
94
|
+
: rest;
|
|
89
95
|
return {
|
|
90
|
-
...
|
|
96
|
+
...fields,
|
|
91
97
|
...(this.includeStack && stack !== undefined ? { stack } : {}),
|
|
92
98
|
...(nested !== undefined ? { cause: this.serializeCause(nested) } : {}),
|
|
93
99
|
};
|
|
@@ -118,6 +124,57 @@ export class ErrorSerializer {
|
|
|
118
124
|
});
|
|
119
125
|
}
|
|
120
126
|
}
|
|
127
|
+
/** Plain objects (Object.prototype or null prototype) are walked; anything else is kept as-is. */
|
|
128
|
+
function isPlainRecord(value) {
|
|
129
|
+
if (value === null || typeof value !== "object")
|
|
130
|
+
return false;
|
|
131
|
+
const proto = Object.getPrototypeOf(value);
|
|
132
|
+
return proto === Object.prototype || proto === null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Redacts sensitive keys inside a non-BaseError cause without changing its
|
|
136
|
+
* shape: primitives, dates and class instances are kept, plain objects and
|
|
137
|
+
* arrays are walked, cycles stop at "[Circular]".
|
|
138
|
+
*/
|
|
139
|
+
function redactCauseFields(fields, pattern, seen = new WeakSet()) {
|
|
140
|
+
const result = {};
|
|
141
|
+
for (const key of Object.keys(fields)) {
|
|
142
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype")
|
|
143
|
+
continue;
|
|
144
|
+
const value = fields[key];
|
|
145
|
+
const sensitive = pattern === undefined
|
|
146
|
+
? isSensitiveMetadataKey(key)
|
|
147
|
+
: isSensitiveMetadataKey(key, pattern);
|
|
148
|
+
result[key] = sensitive
|
|
149
|
+
? REDACTED_METADATA_VALUE
|
|
150
|
+
: redactCauseValue(value, pattern, seen);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
function redactCauseValue(value, pattern, seen) {
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
if (seen.has(value))
|
|
157
|
+
return "[Circular]";
|
|
158
|
+
seen.add(value);
|
|
159
|
+
try {
|
|
160
|
+
return value.map((entry) => redactCauseValue(entry, pattern, seen));
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
seen.delete(value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!isPlainRecord(value))
|
|
167
|
+
return value;
|
|
168
|
+
if (seen.has(value))
|
|
169
|
+
return "[Circular]";
|
|
170
|
+
seen.add(value);
|
|
171
|
+
try {
|
|
172
|
+
return redactCauseFields(value, pattern, seen);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
seen.delete(value);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
121
178
|
/** Structural check for a serialized BaseError (used on nested causes). */
|
|
122
179
|
function isSerializedBaseError(value) {
|
|
123
180
|
return (typeof value.code === "string" &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/errors",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Shared error base class, error codes, and error handling utilities for the Zudojs framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"node": ">=24.0.0"
|
|
29
29
|
},
|
|
30
30
|
"license": "MIT",
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "Oluwayemi Oyinlola",
|
|
33
|
+
"url": "https://github.com/oyinlola-tech"
|
|
34
|
+
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public"
|
|
33
37
|
},
|