@datdm198x/safe-logger 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 ADDED
@@ -0,0 +1,52 @@
1
+ # Safe Logger
2
+
3
+ A lightweight TypeScript utility to sanitize sensitive information from objects before logging, preventing potential security risks from leaking credentials in logs.
4
+
5
+ ## Features
6
+
7
+ - Recursive object sanitization.
8
+ - Automatic detection of sensitive fields based on keys (passwords, tokens, API keys, credit cards, PII, etc.).
9
+ - Configurable masking strategies for different data types.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @datdm198x/safe-logger
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```typescript
20
+ import { safeLog } from '@datdm198x/safe-logger';
21
+
22
+ const sensitiveData = {
23
+ username: 'john_doe',
24
+ password: 'my-super-secret-password',
25
+ api_key: 'sk_live_1234567890abcdef',
26
+ email: 'john.doe@gmail.com'
27
+ };
28
+
29
+ const sanitizedData = safeLog(sensitiveData);
30
+ console.log(sanitizedData);
31
+ /*
32
+ Output:
33
+ {
34
+ username: 'john_doe',
35
+ password: '********',
36
+ api_key: '********cdef',
37
+ email: 'j******e@gmail.com'
38
+ }
39
+ */
40
+ ```
41
+
42
+ ## Development
43
+
44
+ ### Build
45
+ ```bash
46
+ npm run build
47
+ ```
48
+
49
+ ### Testing
50
+ ```bash
51
+ npm test
52
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ maskValue: () => maskValue,
24
+ safeLog: () => safeLog
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/safe-log.ts
29
+ var DEFAULT_MASK_CHAR = "*";
30
+ function normalizeKey(key) {
31
+ return key.trim().toLowerCase().replace(/[\s._-]/g, "");
32
+ }
33
+ function maskValue(value, options = {}) {
34
+ const {
35
+ visiblePercent = 0.2,
36
+ minVisible = 2,
37
+ maskChar = DEFAULT_MASK_CHAR
38
+ } = options;
39
+ if (!value) {
40
+ return value;
41
+ }
42
+ const length = value.length;
43
+ const visible = Math.max(
44
+ minVisible,
45
+ Math.floor(length * visiblePercent)
46
+ );
47
+ if (visible >= length) {
48
+ return value;
49
+ }
50
+ return maskChar.repeat(length - visible) + value.slice(length - visible);
51
+ }
52
+ function maskPassword() {
53
+ return "********";
54
+ }
55
+ function maskEmail(email) {
56
+ const parts = email.split("@");
57
+ if (parts.length !== 2) {
58
+ return maskValue(email);
59
+ }
60
+ const name = parts[0] ?? "";
61
+ const domain = parts[1] ?? "";
62
+ if (name.length <= 2) {
63
+ return `**@${domain}`;
64
+ }
65
+ return name[0] + "*".repeat(name.length - 2) + name[name.length - 1] + "@" + domain;
66
+ }
67
+ function maskPhone(phone) {
68
+ const digits = phone.replace(/\D/g, "");
69
+ if (digits.length <= 4) {
70
+ return phone;
71
+ }
72
+ return "*".repeat(digits.length - 4) + digits.slice(-4);
73
+ }
74
+ function maskCreditCard(card) {
75
+ return maskValue(card.replace(/\s/g, ""), {
76
+ visiblePercent: 0.25,
77
+ minVisible: 4
78
+ });
79
+ }
80
+ function maskToken(token) {
81
+ return maskValue(token, {
82
+ visiblePercent: 0.1,
83
+ minVisible: 4
84
+ });
85
+ }
86
+ var FIELD_MASKERS = /* @__PURE__ */ new Map([
87
+ ["password", maskPassword],
88
+ ["passwd", maskPassword],
89
+ ["pwd", maskPassword],
90
+ ["pass", maskToken],
91
+ ["email", maskEmail],
92
+ ["phone", maskPhone],
93
+ ["creditcard", maskCreditCard],
94
+ ["cvv", () => "***"],
95
+ // AWS
96
+ ["awsaccesskeyid", maskToken],
97
+ ["awssecretaccesskey", maskToken],
98
+ ["awsregion", maskToken],
99
+ ["smtpusername", maskToken],
100
+ ["smtppassword", maskToken],
101
+ ["sessmtpusername", maskToken],
102
+ ["sessmtppassword", maskToken],
103
+ // Security & Auth
104
+ ["clientid", maskToken],
105
+ ["clientsecret", maskToken],
106
+ ["jwtsecret", maskToken],
107
+ ["encryptionkey", maskToken],
108
+ ["sshkey", maskToken],
109
+ ["rsakey", maskToken],
110
+ ["masterkey", maskToken],
111
+ ["salt", maskToken],
112
+ ["encryptionpassword", maskPassword],
113
+ ["dbpassword", maskPassword],
114
+ ["dbpass", maskPassword],
115
+ ["connectionstring", maskToken],
116
+ ["connstr", maskToken],
117
+ ["ldapbindpassword", maskPassword],
118
+ ["stripekey", maskToken],
119
+ ["stripesecret", maskToken],
120
+ ["sendgridapikey", maskToken],
121
+ ["slackwebhookurl", maskToken],
122
+ ["ssn", maskToken],
123
+ ["socialsecuritynumber", maskToken],
124
+ ["nationalid", maskToken],
125
+ ["passportnumber", maskToken],
126
+ ["driverslicense", maskToken],
127
+ ["dob", maskToken],
128
+ ["dateofbirth", maskToken],
129
+ ["cardnumber", maskCreditCard],
130
+ ["cvc", () => "***"],
131
+ ["accountnumber", maskToken],
132
+ ["routingnumber", maskToken],
133
+ // Misc
134
+ ["username", maskToken],
135
+ ["userid", maskToken],
136
+ ["sessionid", maskToken],
137
+ ["ip", maskToken],
138
+ ["ipaddress", maskToken],
139
+ ["macaddress", maskToken],
140
+ ["jwt", maskToken],
141
+ ["token", maskToken],
142
+ ["accesstoken", maskToken],
143
+ ["refreshtoken", maskToken],
144
+ ["authorization", maskToken],
145
+ ["apikey", maskToken],
146
+ ["secret", maskToken],
147
+ ["cookie", maskToken],
148
+ ["privatekey", maskToken],
149
+ // Database connection strings
150
+ ["mongouri", maskToken],
151
+ ["redisuri", maskToken],
152
+ ["mysqluri", maskToken],
153
+ ["postgresqluri", maskToken],
154
+ ["pgsql", maskToken]
155
+ ]);
156
+ var SENSITIVE_KEYWORDS = [
157
+ "password",
158
+ "passwd",
159
+ "pwd",
160
+ "pass",
161
+ "token",
162
+ "jwt",
163
+ "bearer",
164
+ "secret",
165
+ "apikey",
166
+ "accesskey",
167
+ "authorization",
168
+ "cookie",
169
+ "creditcard",
170
+ "cardnumber",
171
+ "cvv",
172
+ "cvc",
173
+ "otp",
174
+ "privatekey",
175
+ "clientsecret",
176
+ "clientid",
177
+ "accesstoken",
178
+ "refreshtoken",
179
+ "username",
180
+ "userid",
181
+ "sessionid",
182
+ "ipaddress",
183
+ "encryption",
184
+ "ssh",
185
+ "rsa",
186
+ "master",
187
+ "salt",
188
+ "connection",
189
+ "conn",
190
+ "ldap",
191
+ "stripe",
192
+ "sendgrid",
193
+ "slack",
194
+ "ssn",
195
+ "socialsecurity",
196
+ "national",
197
+ "passport",
198
+ "drivers",
199
+ "dob",
200
+ "birth",
201
+ "account",
202
+ "routing"
203
+ ];
204
+ function isSensitiveKey(key) {
205
+ const normalized = normalizeKey(key);
206
+ return SENSITIVE_KEYWORDS.some(
207
+ (keyword) => normalized.includes(keyword)
208
+ );
209
+ }
210
+ function sanitize(value) {
211
+ if (value === null || value === void 0) {
212
+ return value;
213
+ }
214
+ if (Array.isArray(value)) {
215
+ return value.map(sanitize);
216
+ }
217
+ if (typeof value !== "object") {
218
+ return value;
219
+ }
220
+ const obj = value;
221
+ return Object.fromEntries(
222
+ Object.entries(obj).map(([key, value2]) => {
223
+ if (typeof value2 === "string") {
224
+ const normalized = normalizeKey(key);
225
+ const masker = FIELD_MASKERS.get(normalized);
226
+ if (masker) {
227
+ return [key, masker(value2)];
228
+ }
229
+ if (isSensitiveKey(normalized)) {
230
+ return [key, maskToken(value2)];
231
+ }
232
+ return [key, value2];
233
+ }
234
+ if (value2 && typeof value2 === "object") {
235
+ return [key, sanitize(value2)];
236
+ }
237
+ return [key, value2];
238
+ })
239
+ );
240
+ }
241
+ function safeLog(value) {
242
+ return sanitize(value);
243
+ }
244
+ // Annotate the CommonJS export names for ESM import in node:
245
+ 0 && (module.exports = {
246
+ maskValue,
247
+ safeLog
248
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ============================================================================
3
+ * Safe Logger Utility
4
+ * ============================================================================
5
+ */
6
+ interface MaskOptions {
7
+ visiblePercent?: number;
8
+ minVisible?: number;
9
+ maskChar?: string;
10
+ }
11
+ /**
12
+ * Generic masking
13
+ */
14
+ declare function maskValue(value: string, options?: MaskOptions): string;
15
+ /**
16
+ * Public API
17
+ */
18
+ declare function safeLog<T>(value: T): T;
19
+
20
+ export { maskValue, safeLog };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ============================================================================
3
+ * Safe Logger Utility
4
+ * ============================================================================
5
+ */
6
+ interface MaskOptions {
7
+ visiblePercent?: number;
8
+ minVisible?: number;
9
+ maskChar?: string;
10
+ }
11
+ /**
12
+ * Generic masking
13
+ */
14
+ declare function maskValue(value: string, options?: MaskOptions): string;
15
+ /**
16
+ * Public API
17
+ */
18
+ declare function safeLog<T>(value: T): T;
19
+
20
+ export { maskValue, safeLog };
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ // src/safe-log.ts
2
+ var DEFAULT_MASK_CHAR = "*";
3
+ function normalizeKey(key) {
4
+ return key.trim().toLowerCase().replace(/[\s._-]/g, "");
5
+ }
6
+ function maskValue(value, options = {}) {
7
+ const {
8
+ visiblePercent = 0.2,
9
+ minVisible = 2,
10
+ maskChar = DEFAULT_MASK_CHAR
11
+ } = options;
12
+ if (!value) {
13
+ return value;
14
+ }
15
+ const length = value.length;
16
+ const visible = Math.max(
17
+ minVisible,
18
+ Math.floor(length * visiblePercent)
19
+ );
20
+ if (visible >= length) {
21
+ return value;
22
+ }
23
+ return maskChar.repeat(length - visible) + value.slice(length - visible);
24
+ }
25
+ function maskPassword() {
26
+ return "********";
27
+ }
28
+ function maskEmail(email) {
29
+ const parts = email.split("@");
30
+ if (parts.length !== 2) {
31
+ return maskValue(email);
32
+ }
33
+ const name = parts[0] ?? "";
34
+ const domain = parts[1] ?? "";
35
+ if (name.length <= 2) {
36
+ return `**@${domain}`;
37
+ }
38
+ return name[0] + "*".repeat(name.length - 2) + name[name.length - 1] + "@" + domain;
39
+ }
40
+ function maskPhone(phone) {
41
+ const digits = phone.replace(/\D/g, "");
42
+ if (digits.length <= 4) {
43
+ return phone;
44
+ }
45
+ return "*".repeat(digits.length - 4) + digits.slice(-4);
46
+ }
47
+ function maskCreditCard(card) {
48
+ return maskValue(card.replace(/\s/g, ""), {
49
+ visiblePercent: 0.25,
50
+ minVisible: 4
51
+ });
52
+ }
53
+ function maskToken(token) {
54
+ return maskValue(token, {
55
+ visiblePercent: 0.1,
56
+ minVisible: 4
57
+ });
58
+ }
59
+ var FIELD_MASKERS = /* @__PURE__ */ new Map([
60
+ ["password", maskPassword],
61
+ ["passwd", maskPassword],
62
+ ["pwd", maskPassword],
63
+ ["pass", maskToken],
64
+ ["email", maskEmail],
65
+ ["phone", maskPhone],
66
+ ["creditcard", maskCreditCard],
67
+ ["cvv", () => "***"],
68
+ // AWS
69
+ ["awsaccesskeyid", maskToken],
70
+ ["awssecretaccesskey", maskToken],
71
+ ["awsregion", maskToken],
72
+ ["smtpusername", maskToken],
73
+ ["smtppassword", maskToken],
74
+ ["sessmtpusername", maskToken],
75
+ ["sessmtppassword", maskToken],
76
+ // Security & Auth
77
+ ["clientid", maskToken],
78
+ ["clientsecret", maskToken],
79
+ ["jwtsecret", maskToken],
80
+ ["encryptionkey", maskToken],
81
+ ["sshkey", maskToken],
82
+ ["rsakey", maskToken],
83
+ ["masterkey", maskToken],
84
+ ["salt", maskToken],
85
+ ["encryptionpassword", maskPassword],
86
+ ["dbpassword", maskPassword],
87
+ ["dbpass", maskPassword],
88
+ ["connectionstring", maskToken],
89
+ ["connstr", maskToken],
90
+ ["ldapbindpassword", maskPassword],
91
+ ["stripekey", maskToken],
92
+ ["stripesecret", maskToken],
93
+ ["sendgridapikey", maskToken],
94
+ ["slackwebhookurl", maskToken],
95
+ ["ssn", maskToken],
96
+ ["socialsecuritynumber", maskToken],
97
+ ["nationalid", maskToken],
98
+ ["passportnumber", maskToken],
99
+ ["driverslicense", maskToken],
100
+ ["dob", maskToken],
101
+ ["dateofbirth", maskToken],
102
+ ["cardnumber", maskCreditCard],
103
+ ["cvc", () => "***"],
104
+ ["accountnumber", maskToken],
105
+ ["routingnumber", maskToken],
106
+ // Misc
107
+ ["username", maskToken],
108
+ ["userid", maskToken],
109
+ ["sessionid", maskToken],
110
+ ["ip", maskToken],
111
+ ["ipaddress", maskToken],
112
+ ["macaddress", maskToken],
113
+ ["jwt", maskToken],
114
+ ["token", maskToken],
115
+ ["accesstoken", maskToken],
116
+ ["refreshtoken", maskToken],
117
+ ["authorization", maskToken],
118
+ ["apikey", maskToken],
119
+ ["secret", maskToken],
120
+ ["cookie", maskToken],
121
+ ["privatekey", maskToken],
122
+ // Database connection strings
123
+ ["mongouri", maskToken],
124
+ ["redisuri", maskToken],
125
+ ["mysqluri", maskToken],
126
+ ["postgresqluri", maskToken],
127
+ ["pgsql", maskToken]
128
+ ]);
129
+ var SENSITIVE_KEYWORDS = [
130
+ "password",
131
+ "passwd",
132
+ "pwd",
133
+ "pass",
134
+ "token",
135
+ "jwt",
136
+ "bearer",
137
+ "secret",
138
+ "apikey",
139
+ "accesskey",
140
+ "authorization",
141
+ "cookie",
142
+ "creditcard",
143
+ "cardnumber",
144
+ "cvv",
145
+ "cvc",
146
+ "otp",
147
+ "privatekey",
148
+ "clientsecret",
149
+ "clientid",
150
+ "accesstoken",
151
+ "refreshtoken",
152
+ "username",
153
+ "userid",
154
+ "sessionid",
155
+ "ipaddress",
156
+ "encryption",
157
+ "ssh",
158
+ "rsa",
159
+ "master",
160
+ "salt",
161
+ "connection",
162
+ "conn",
163
+ "ldap",
164
+ "stripe",
165
+ "sendgrid",
166
+ "slack",
167
+ "ssn",
168
+ "socialsecurity",
169
+ "national",
170
+ "passport",
171
+ "drivers",
172
+ "dob",
173
+ "birth",
174
+ "account",
175
+ "routing"
176
+ ];
177
+ function isSensitiveKey(key) {
178
+ const normalized = normalizeKey(key);
179
+ return SENSITIVE_KEYWORDS.some(
180
+ (keyword) => normalized.includes(keyword)
181
+ );
182
+ }
183
+ function sanitize(value) {
184
+ if (value === null || value === void 0) {
185
+ return value;
186
+ }
187
+ if (Array.isArray(value)) {
188
+ return value.map(sanitize);
189
+ }
190
+ if (typeof value !== "object") {
191
+ return value;
192
+ }
193
+ const obj = value;
194
+ return Object.fromEntries(
195
+ Object.entries(obj).map(([key, value2]) => {
196
+ if (typeof value2 === "string") {
197
+ const normalized = normalizeKey(key);
198
+ const masker = FIELD_MASKERS.get(normalized);
199
+ if (masker) {
200
+ return [key, masker(value2)];
201
+ }
202
+ if (isSensitiveKey(normalized)) {
203
+ return [key, maskToken(value2)];
204
+ }
205
+ return [key, value2];
206
+ }
207
+ if (value2 && typeof value2 === "object") {
208
+ return [key, sanitize(value2)];
209
+ }
210
+ return [key, value2];
211
+ })
212
+ );
213
+ }
214
+ function safeLog(value) {
215
+ return sanitize(value);
216
+ }
217
+ export {
218
+ maskValue,
219
+ safeLog
220
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@datdm198x/safe-logger",
3
+ "version": "1.0.0",
4
+ "description": "Safe logger utility",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "test": "vitest run",
12
+ "build": "tsup src/index.ts --format cjs,esm --dts",
13
+ "prepare": "npm run build"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/dat-dao-c2c/safe-logger.git"
18
+ },
19
+ "keywords": [],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "type": "module",
23
+ "bugs": {
24
+ "url": "https://github.com/dat-dao-c2c/safe-logger/issues"
25
+ },
26
+ "homepage": "https://github.com/dat-dao-c2c/safe-logger#readme",
27
+ "dependencies": {
28
+ "tsup": "^8.5.1",
29
+ "typescript": "^6.0.3"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^26.0.1",
33
+ "vitest": "^4.1.9"
34
+ }
35
+ }