@skillsmith/core 0.10.0 → 0.11.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.
Files changed (162) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +7 -5
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +23 -0
  5. package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -1
  6. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +137 -0
  7. package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -1
  8. package/dist/src/analysis/tree-sitter/pythonIncremental.js +83 -10
  9. package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -1
  10. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +6 -1
  11. package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -1
  12. package/dist/src/config/audit-notify-state.d.ts +46 -0
  13. package/dist/src/config/audit-notify-state.d.ts.map +1 -0
  14. package/dist/src/config/audit-notify-state.js +55 -0
  15. package/dist/src/config/audit-notify-state.js.map +1 -0
  16. package/dist/src/config/audit-notify-state.test.d.ts +9 -0
  17. package/dist/src/config/audit-notify-state.test.d.ts.map +1 -0
  18. package/dist/src/config/audit-notify-state.test.js +63 -0
  19. package/dist/src/config/audit-notify-state.test.js.map +1 -0
  20. package/dist/src/config/index.d.ts +10 -0
  21. package/dist/src/config/index.d.ts.map +1 -1
  22. package/dist/src/config/index.js.map +1 -1
  23. package/dist/src/exports/services.d.ts +1 -1
  24. package/dist/src/exports/services.d.ts.map +1 -1
  25. package/dist/src/exports/services.js +3 -1
  26. package/dist/src/exports/services.js.map +1 -1
  27. package/dist/src/index.d.ts +4 -1
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +7 -1
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/logging/context.d.ts +58 -0
  32. package/dist/src/logging/context.d.ts.map +1 -0
  33. package/dist/src/logging/context.js +62 -0
  34. package/dist/src/logging/context.js.map +1 -0
  35. package/dist/src/logging/context.test.d.ts +14 -0
  36. package/dist/src/logging/context.test.d.ts.map +1 -0
  37. package/dist/src/logging/context.test.js +88 -0
  38. package/dist/src/logging/context.test.js.map +1 -0
  39. package/dist/src/logging/index.d.ts +13 -0
  40. package/dist/src/logging/index.d.ts.map +1 -0
  41. package/dist/src/logging/index.js +12 -0
  42. package/dist/src/logging/index.js.map +1 -0
  43. package/dist/src/logging/logger.d.ts +71 -0
  44. package/dist/src/logging/logger.d.ts.map +1 -0
  45. package/dist/src/logging/logger.js +264 -0
  46. package/dist/src/logging/logger.js.map +1 -0
  47. package/dist/src/logging/logger.test.d.ts +9 -0
  48. package/dist/src/logging/logger.test.d.ts.map +1 -0
  49. package/dist/src/logging/logger.test.js +320 -0
  50. package/dist/src/logging/logger.test.js.map +1 -0
  51. package/dist/src/logging/redact.d.ts +35 -0
  52. package/dist/src/logging/redact.d.ts.map +1 -0
  53. package/dist/src/logging/redact.js +152 -0
  54. package/dist/src/logging/redact.js.map +1 -0
  55. package/dist/src/logging/redact.test.d.ts +8 -0
  56. package/dist/src/logging/redact.test.d.ts.map +1 -0
  57. package/dist/src/logging/redact.test.js +330 -0
  58. package/dist/src/logging/redact.test.js.map +1 -0
  59. package/dist/src/logging/rotation.d.ts +75 -0
  60. package/dist/src/logging/rotation.d.ts.map +1 -0
  61. package/dist/src/logging/rotation.js +284 -0
  62. package/dist/src/logging/rotation.js.map +1 -0
  63. package/dist/src/logging/rotation.test.d.ts +11 -0
  64. package/dist/src/logging/rotation.test.d.ts.map +1 -0
  65. package/dist/src/logging/rotation.test.js +132 -0
  66. package/dist/src/logging/rotation.test.js.map +1 -0
  67. package/dist/src/logging/types.d.ts +69 -0
  68. package/dist/src/logging/types.d.ts.map +1 -0
  69. package/dist/src/logging/types.js +9 -0
  70. package/dist/src/logging/types.js.map +1 -0
  71. package/dist/src/security/index.d.ts +2 -0
  72. package/dist/src/security/index.d.ts.map +1 -1
  73. package/dist/src/security/index.js +4 -0
  74. package/dist/src/security/index.js.map +1 -1
  75. package/dist/src/security/scanner/SecurityScanner.hostile-update.d.ts +46 -0
  76. package/dist/src/security/scanner/SecurityScanner.hostile-update.d.ts.map +1 -0
  77. package/dist/src/security/scanner/SecurityScanner.hostile-update.js +209 -0
  78. package/dist/src/security/scanner/SecurityScanner.hostile-update.js.map +1 -0
  79. package/dist/src/security/scanner/index.d.ts +2 -1
  80. package/dist/src/security/scanner/index.d.ts.map +1 -1
  81. package/dist/src/security/scanner/index.js +2 -0
  82. package/dist/src/security/scanner/index.js.map +1 -1
  83. package/dist/src/security/scanner/types.d.ts +21 -0
  84. package/dist/src/security/scanner/types.d.ts.map +1 -1
  85. package/dist/src/services/skill-installation.helpers.d.ts +13 -1
  86. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  87. package/dist/src/services/skill-installation.helpers.js +14 -1
  88. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  89. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  90. package/dist/src/services/skill-installation.io.js +19 -2
  91. package/dist/src/services/skill-installation.io.js.map +1 -1
  92. package/dist/src/sync/BackgroundSyncService.d.ts +10 -2
  93. package/dist/src/sync/BackgroundSyncService.d.ts.map +1 -1
  94. package/dist/src/sync/BackgroundSyncService.js +46 -4
  95. package/dist/src/sync/BackgroundSyncService.js.map +1 -1
  96. package/dist/src/sync/SyncEngine.d.ts +9 -0
  97. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  98. package/dist/src/sync/SyncEngine.js +23 -4
  99. package/dist/src/sync/SyncEngine.js.map +1 -1
  100. package/dist/src/sync/access-token.d.ts +27 -0
  101. package/dist/src/sync/access-token.d.ts.map +1 -0
  102. package/dist/src/sync/access-token.js +40 -0
  103. package/dist/src/sync/access-token.js.map +1 -0
  104. package/dist/src/sync/audit-notify-client.d.ts +83 -0
  105. package/dist/src/sync/audit-notify-client.d.ts.map +1 -0
  106. package/dist/src/sync/audit-notify-client.js +126 -0
  107. package/dist/src/sync/audit-notify-client.js.map +1 -0
  108. package/dist/src/sync/audit-notify-client.test.d.ts +14 -0
  109. package/dist/src/sync/audit-notify-client.test.d.ts.map +1 -0
  110. package/dist/src/sync/audit-notify-client.test.js +133 -0
  111. package/dist/src/sync/audit-notify-client.test.js.map +1 -0
  112. package/dist/src/sync/index.d.ts +1 -0
  113. package/dist/src/sync/index.d.ts.map +1 -1
  114. package/dist/src/sync/index.js +2 -0
  115. package/dist/src/sync/index.js.map +1 -1
  116. package/dist/src/sync/inventory-client.d.ts.map +1 -1
  117. package/dist/src/sync/inventory-client.js +5 -16
  118. package/dist/src/sync/inventory-client.js.map +1 -1
  119. package/dist/src/telemetry/posthog.d.ts +15 -0
  120. package/dist/src/telemetry/posthog.d.ts.map +1 -1
  121. package/dist/src/telemetry/posthog.js +6 -1
  122. package/dist/src/telemetry/posthog.js.map +1 -1
  123. package/dist/src/telemetry/wrap.bench.js +5 -0
  124. package/dist/src/telemetry/wrap.bench.js.map +1 -1
  125. package/dist/src/telemetry/wrap.correlation.test.d.ts +20 -0
  126. package/dist/src/telemetry/wrap.correlation.test.d.ts.map +1 -0
  127. package/dist/src/telemetry/wrap.correlation.test.js +174 -0
  128. package/dist/src/telemetry/wrap.correlation.test.js.map +1 -0
  129. package/dist/src/telemetry/wrap.d.ts.map +1 -1
  130. package/dist/src/telemetry/wrap.gate.test.js +5 -0
  131. package/dist/src/telemetry/wrap.gate.test.js.map +1 -1
  132. package/dist/src/telemetry/wrap.js +111 -52
  133. package/dist/src/telemetry/wrap.js.map +1 -1
  134. package/dist/src/telemetry/wrap.marker.test.js +5 -0
  135. package/dist/src/telemetry/wrap.marker.test.js.map +1 -1
  136. package/dist/src/telemetry/wrap.test.js +10 -0
  137. package/dist/src/telemetry/wrap.test.js.map +1 -1
  138. package/dist/src/types.d.ts +6 -0
  139. package/dist/src/types.d.ts.map +1 -1
  140. package/dist/tests/security/scanner-regression-guard.test.js +179 -1
  141. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  142. package/dist/tests/sync/BackgroundSyncService.abort.test.d.ts +17 -0
  143. package/dist/tests/sync/BackgroundSyncService.abort.test.d.ts.map +1 -0
  144. package/dist/tests/sync/BackgroundSyncService.abort.test.js +182 -0
  145. package/dist/tests/sync/BackgroundSyncService.abort.test.js.map +1 -0
  146. package/dist/tests/sync/BackgroundSyncService.test.d.ts +9 -1
  147. package/dist/tests/sync/BackgroundSyncService.test.d.ts.map +1 -1
  148. package/dist/tests/sync/BackgroundSyncService.test.js +8 -3
  149. package/dist/tests/sync/BackgroundSyncService.test.js.map +1 -1
  150. package/dist/tests/sync/SyncEngine.abort.test.d.ts +15 -0
  151. package/dist/tests/sync/SyncEngine.abort.test.d.ts.map +1 -0
  152. package/dist/tests/sync/SyncEngine.abort.test.js +100 -0
  153. package/dist/tests/sync/SyncEngine.abort.test.js.map +1 -0
  154. package/dist/tests/sync/SyncEngine.status.test.d.ts +10 -0
  155. package/dist/tests/sync/SyncEngine.status.test.d.ts.map +1 -0
  156. package/dist/tests/sync/SyncEngine.status.test.js +97 -0
  157. package/dist/tests/sync/SyncEngine.status.test.js.map +1 -0
  158. package/dist/tests/sync/SyncEngine.test.d.ts +22 -1
  159. package/dist/tests/sync/SyncEngine.test.d.ts.map +1 -1
  160. package/dist/tests/sync/SyncEngine.test.js +3 -69
  161. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  162. package/package.json +7 -2
@@ -0,0 +1,330 @@
1
+ /**
2
+ * SMI-883 / SMI-5615: Tests for logger sensitive data redaction, ported from
3
+ * `packages/mcp-server/tests/unit/logger.test.ts` — same coverage, new
4
+ * location (`@skillsmith/core`'s `logging/redact.ts`).
5
+ * Ensures API keys, tokens, passwords, and secrets are never written to disk.
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ import { redactSensitiveData, redactSensitiveObject, stripAnsi } from './redact.js';
9
+ describe('SMI-883: Logger Sensitive Data Redaction', () => {
10
+ describe('redactSensitiveData', () => {
11
+ describe('GitHub tokens', () => {
12
+ it('should redact classic personal access tokens (ghp_)', () => {
13
+ const input = 'Token: ghp_1234567890abcdefghijklmnopqrstuvwxyz';
14
+ const result = redactSensitiveData(input);
15
+ expect(result).toBe('Token: ghp_[REDACTED]');
16
+ expect(result).not.toContain('1234567890');
17
+ });
18
+ it('should redact fine-grained personal access tokens (github_pat_)', () => {
19
+ const input = 'Using github_pat_abcdefghijklmnopqrstuvwx12345';
20
+ const result = redactSensitiveData(input);
21
+ expect(result).toBe('Using github_pat_[REDACTED]');
22
+ });
23
+ it('should redact OAuth tokens (gho_)', () => {
24
+ const input = 'OAuth: gho_1234567890abcdefghijklmnopqrstuvwxyz';
25
+ const result = redactSensitiveData(input);
26
+ expect(result).toBe('OAuth: gho_[REDACTED]');
27
+ });
28
+ it('should redact server-to-server tokens (ghs_)', () => {
29
+ const input = 'Server: ghs_1234567890abcdefghijklmnopqrstuvwxyz';
30
+ const result = redactSensitiveData(input);
31
+ expect(result).toBe('Server: ghs_[REDACTED]');
32
+ });
33
+ it('should redact user-to-server tokens (ghu_)', () => {
34
+ const input = 'User: ghu_1234567890abcdefghijklmnopqrstuvwxyz';
35
+ const result = redactSensitiveData(input);
36
+ expect(result).toBe('User: ghu_[REDACTED]');
37
+ });
38
+ it('should redact refresh tokens (ghr_)', () => {
39
+ const input = 'Refresh: ghr_1234567890abcdefghijklmnopqrstuvwxyz';
40
+ const result = redactSensitiveData(input);
41
+ expect(result).toBe('Refresh: ghr_[REDACTED]');
42
+ });
43
+ });
44
+ describe('Linear API keys', () => {
45
+ it('should redact Linear API keys (lin_api_)', () => {
46
+ const input = 'LINEAR_API_KEY=lin_api_abcdefghijklmnopqrstuvwxyz123456';
47
+ const result = redactSensitiveData(input);
48
+ expect(result).toContain('lin_api_[REDACTED]');
49
+ expect(result).not.toContain('abcdefghij');
50
+ });
51
+ });
52
+ describe('Stripe keys', () => {
53
+ it('should redact live secret keys (sk_live_)', () => {
54
+ const input = 'STRIPE_KEY=sk_live_abcdefghijklmnopqrstuvwx';
55
+ const result = redactSensitiveData(input);
56
+ expect(result).toContain('sk_live_[REDACTED]');
57
+ });
58
+ it('should redact test secret keys (sk_test_)', () => {
59
+ const input = 'STRIPE_KEY=sk_test_abcdefghijklmnopqrstuvwx';
60
+ const result = redactSensitiveData(input);
61
+ expect(result).toContain('sk_test_[REDACTED]');
62
+ });
63
+ it('should redact live publishable keys (pk_live_)', () => {
64
+ const input = 'STRIPE_PK=pk_live_abcdefghijklmnopqrstuvwx';
65
+ const result = redactSensitiveData(input);
66
+ expect(result).toContain('pk_live_[REDACTED]');
67
+ });
68
+ it('should redact test publishable keys (pk_test_)', () => {
69
+ const input = 'STRIPE_PK=pk_test_abcdefghijklmnopqrstuvwx';
70
+ const result = redactSensitiveData(input);
71
+ expect(result).toContain('pk_test_[REDACTED]');
72
+ });
73
+ });
74
+ describe('OpenAI API keys', () => {
75
+ it('should redact OpenAI API keys (sk-)', () => {
76
+ const input = 'OPENAI_API_KEY=sk-abcdefghijklmnopqrstuvwxyz1234567890abcdefghijkl';
77
+ const result = redactSensitiveData(input);
78
+ expect(result).toContain('sk-[REDACTED]');
79
+ expect(result).not.toContain('abcdefghij');
80
+ });
81
+ });
82
+ describe('Anthropic API keys', () => {
83
+ it('should redact Anthropic API keys (sk-ant-)', () => {
84
+ const input = 'ANTHROPIC_API_KEY=sk-ant-api03-abcdefghijklmnopqrstuvwxyz12';
85
+ const result = redactSensitiveData(input);
86
+ expect(result).toContain('sk-ant-[REDACTED]');
87
+ });
88
+ });
89
+ describe('AWS keys', () => {
90
+ it('should redact AWS access key IDs (AKIA)', () => {
91
+ const input = 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE';
92
+ const result = redactSensitiveData(input);
93
+ expect(result).toContain('AKIA[REDACTED]');
94
+ expect(result).not.toContain('IOSFODNN7EXAMPLE');
95
+ });
96
+ });
97
+ describe('Slack tokens', () => {
98
+ it('should redact bot tokens (xoxb-)', () => {
99
+ const input = 'SLACK_TOKEN=xoxb-123456789012-1234567890123-abcdefghijklmnop';
100
+ const result = redactSensitiveData(input);
101
+ expect(result).toContain('xox*-[REDACTED]');
102
+ });
103
+ it('should redact user tokens (xoxp-)', () => {
104
+ const input = 'SLACK_TOKEN=xoxp-123456789012-1234567890123-abcdefghijklmnop';
105
+ const result = redactSensitiveData(input);
106
+ expect(result).toContain('xox*-[REDACTED]');
107
+ });
108
+ });
109
+ describe('npm tokens', () => {
110
+ it('should redact npm tokens (npm_)', () => {
111
+ const input = 'NPM_TOKEN=npm_abcdefghijklmnopqrstuvwxyz1234567890';
112
+ const result = redactSensitiveData(input);
113
+ expect(result).toContain('npm_[REDACTED]');
114
+ });
115
+ });
116
+ describe('Bearer and Basic auth', () => {
117
+ it('should redact Bearer tokens', () => {
118
+ const input = 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
119
+ const result = redactSensitiveData(input);
120
+ expect(result).toContain('Bearer [REDACTED]');
121
+ });
122
+ it('should redact Basic auth credentials', () => {
123
+ const input = 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQxMjM0NTY=';
124
+ const result = redactSensitiveData(input);
125
+ expect(result).toContain('Basic [REDACTED]');
126
+ });
127
+ });
128
+ describe('JWT tokens', () => {
129
+ it('should redact JWT tokens', () => {
130
+ const input = 'Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
131
+ const result = redactSensitiveData(input);
132
+ expect(result).toContain('[JWT_REDACTED]');
133
+ expect(result).not.toContain('eyJzdWI');
134
+ });
135
+ });
136
+ describe('Generic patterns', () => {
137
+ it('should redact api_key assignments', () => {
138
+ const input = 'config.api_key = "my_secret_api_key_12345"';
139
+ const result = redactSensitiveData(input);
140
+ expect(result).toContain('api_key=[REDACTED]');
141
+ });
142
+ it('should redact password assignments', () => {
143
+ const input = 'password: "supersecret123"';
144
+ const result = redactSensitiveData(input);
145
+ expect(result).toContain('password=[REDACTED]');
146
+ });
147
+ it('should redact secret assignments', () => {
148
+ const input = 'client_secret = "abcdefghijklmnop"';
149
+ const result = redactSensitiveData(input);
150
+ expect(result).toContain('secret=[REDACTED]');
151
+ });
152
+ it('should redact token assignments', () => {
153
+ const input = 'auth_token: "my_auth_token_value"';
154
+ const result = redactSensitiveData(input);
155
+ expect(result).toContain('token=[REDACTED]');
156
+ });
157
+ });
158
+ describe('Connection strings', () => {
159
+ it('should redact passwords in connection strings', () => {
160
+ const input = 'postgresql://user:mysecretpassword@localhost:5432/db';
161
+ const result = redactSensitiveData(input);
162
+ expect(result).toBe('postgresql://user:[REDACTED]@localhost:5432/db');
163
+ expect(result).not.toContain('mysecretpassword');
164
+ });
165
+ it('should redact passwords in MongoDB connection strings', () => {
166
+ const input = 'mongodb://admin:p4ssw0rd123@cluster.mongodb.net/mydb';
167
+ const result = redactSensitiveData(input);
168
+ expect(result).toContain('[REDACTED]@cluster');
169
+ expect(result).not.toContain('p4ssw0rd123');
170
+ });
171
+ });
172
+ describe('Private keys', () => {
173
+ it('should redact RSA private keys', () => {
174
+ const input = `-----BEGIN RSA PRIVATE KEY-----
175
+ MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy
176
+ -----END RSA PRIVATE KEY-----`;
177
+ const result = redactSensitiveData(input);
178
+ expect(result).toBe('-----[PRIVATE KEY REDACTED]-----');
179
+ expect(result).not.toContain('MIIEpAIBAAKCAQEA');
180
+ });
181
+ it('should redact generic private keys', () => {
182
+ const input = `-----BEGIN PRIVATE KEY-----
183
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEA
184
+ -----END PRIVATE KEY-----`;
185
+ const result = redactSensitiveData(input);
186
+ expect(result).toBe('-----[PRIVATE KEY REDACTED]-----');
187
+ });
188
+ });
189
+ describe('Edge cases', () => {
190
+ it('should handle empty strings', () => {
191
+ expect(redactSensitiveData('')).toBe('');
192
+ });
193
+ it('should handle null-ish values', () => {
194
+ expect(redactSensitiveData(null)).toBe(null);
195
+ expect(redactSensitiveData(undefined)).toBe(undefined);
196
+ });
197
+ it('should handle text with no sensitive data', () => {
198
+ const input = 'This is a normal log message with no secrets';
199
+ expect(redactSensitiveData(input)).toBe(input);
200
+ });
201
+ it('should handle multiple tokens in same string', () => {
202
+ const input = 'GitHub: ghp_1234567890abcdefghijklmnopqrstuvwxyz, Stripe: sk_live_abcdefghijklmnopqrstuvwx';
203
+ const result = redactSensitiveData(input);
204
+ expect(result).toContain('ghp_[REDACTED]');
205
+ expect(result).toContain('sk_live_[REDACTED]');
206
+ });
207
+ it('should handle repeated calls (regex lastIndex reset)', () => {
208
+ const input = 'Token: ghp_1234567890abcdefghijklmnopqrstuvwxyz';
209
+ // Call multiple times to verify lastIndex is reset
210
+ const result1 = redactSensitiveData(input);
211
+ const result2 = redactSensitiveData(input);
212
+ const result3 = redactSensitiveData(input);
213
+ expect(result1).toBe('Token: ghp_[REDACTED]');
214
+ expect(result2).toBe('Token: ghp_[REDACTED]');
215
+ expect(result3).toBe('Token: ghp_[REDACTED]');
216
+ });
217
+ });
218
+ });
219
+ describe('redactSensitiveObject', () => {
220
+ it('should redact strings in simple objects', () => {
221
+ const input = {
222
+ token: 'ghp_1234567890abcdefghijklmnopqrstuvwxyz',
223
+ name: 'test',
224
+ };
225
+ const result = redactSensitiveObject(input);
226
+ expect(result.token).toBe('ghp_[REDACTED]');
227
+ expect(result.name).toBe('test');
228
+ });
229
+ it('should redact strings in nested objects', () => {
230
+ const input = {
231
+ config: {
232
+ auth: {
233
+ apiKey: 'sk_live_abcdefghijklmnopqrstuvwx',
234
+ },
235
+ },
236
+ };
237
+ const result = redactSensitiveObject(input);
238
+ expect(result.config.auth).toEqual({
239
+ apiKey: 'sk_live_[REDACTED]',
240
+ });
241
+ });
242
+ it('should redact strings in arrays', () => {
243
+ const input = ['ghp_1234567890abcdefghijklmnopqrstuvwxyz', 'normal string'];
244
+ const result = redactSensitiveObject(input);
245
+ expect(result[0]).toBe('ghp_[REDACTED]');
246
+ expect(result[1]).toBe('normal string');
247
+ });
248
+ it('should handle mixed arrays and objects', () => {
249
+ const input = {
250
+ tokens: [
251
+ { key: 'ghp_1234567890abcdefghijklmnopqrstuvwxyz' },
252
+ { key: 'sk_live_abcdefghijklmnopqrstuvwx' },
253
+ ],
254
+ };
255
+ const result = redactSensitiveObject(input);
256
+ const tokens = result.tokens;
257
+ expect(tokens[0].key).toBe('ghp_[REDACTED]');
258
+ expect(tokens[1].key).toBe('sk_live_[REDACTED]');
259
+ });
260
+ it('should handle null and undefined', () => {
261
+ expect(redactSensitiveObject(null)).toBe(null);
262
+ expect(redactSensitiveObject(undefined)).toBe(undefined);
263
+ });
264
+ it('should preserve non-string primitives', () => {
265
+ const input = {
266
+ count: 42,
267
+ enabled: true,
268
+ ratio: 3.14,
269
+ };
270
+ const result = redactSensitiveObject(input);
271
+ expect(result).toEqual(input);
272
+ });
273
+ it('should handle Error objects by converting to plain object', () => {
274
+ const input = {
275
+ error: new Error('Failed with token ghp_1234567890abcdefghijklmnopqrstuvwxyz'),
276
+ };
277
+ const result = redactSensitiveObject(input);
278
+ // Error.message won't be in entries, but if serialized it would be redacted
279
+ expect(result.error).toBeDefined();
280
+ });
281
+ it('should mark a directly circular reference as [Circular] instead of stack-overflowing (SMI-5615 NEW-2)', () => {
282
+ const circular = { name: 'ghp_1234567890abcdefghijklmnopqrstuvwxyz' };
283
+ circular.self = circular;
284
+ const result = redactSensitiveObject(circular);
285
+ expect(result.name).toBe('ghp_[REDACTED]');
286
+ expect(result.self).toBe('[Circular]');
287
+ });
288
+ it('should mark an indirect (two-hop) circular reference as [Circular]', () => {
289
+ const a = {};
290
+ const b = { a };
291
+ a.b = b;
292
+ const result = redactSensitiveObject(a);
293
+ const nestedB = result.b;
294
+ expect(nestedB.a).toBe('[Circular]');
295
+ });
296
+ it('should NOT treat the same object appearing twice at sibling (non-cyclic) positions as circular', () => {
297
+ const shared = { key: 'ghp_1234567890abcdefghijklmnopqrstuvwxyz' };
298
+ const input = { first: shared, second: shared };
299
+ const result = redactSensitiveObject(input);
300
+ expect(result.first.key).toBe('ghp_[REDACTED]');
301
+ expect(result.second.key).toBe('ghp_[REDACTED]');
302
+ });
303
+ });
304
+ describe('stripAnsi (SMI-5615 Mode-B Wave 2 pass)', () => {
305
+ it('should strip chalk-style SGR color codes', () => {
306
+ const chalkRed = '\x1b[31mError:\x1b[39m something failed';
307
+ expect(stripAnsi(chalkRed)).toBe('Error: something failed');
308
+ });
309
+ it('should leave plain text with no escape codes untouched', () => {
310
+ expect(stripAnsi('plain text, no colors')).toBe('plain text, no colors');
311
+ });
312
+ it('should handle empty and null-ish input like redactSensitiveData does', () => {
313
+ expect(stripAnsi('')).toBe('');
314
+ });
315
+ it('closes the leading word-boundary redaction gap: a secret immediately after an SGR code is still redacted once ANSI is stripped first', () => {
316
+ // \x1b[31m ends in 'm' (a word char) directly followed by the secret's
317
+ // 's' (also a word char) — no \b between them, so redactSensitiveData
318
+ // alone would miss this. stripAnsi-then-redact is the fix (see
319
+ // `redactForDisk` in logger.ts).
320
+ const secret = 'sk_live_' + 'c'.repeat(24);
321
+ const chalkWrapped = `\x1b[31m${secret}\x1b[39m`;
322
+ // Demonstrates the gap exists when redacting the raw (un-stripped) text:
323
+ expect(redactSensitiveData(chalkWrapped)).toContain(secret);
324
+ // ...and that stripping ANSI first closes it:
325
+ expect(redactSensitiveData(stripAnsi(chalkWrapped))).not.toContain(secret);
326
+ expect(redactSensitiveData(stripAnsi(chalkWrapped))).toContain('sk_live_[REDACTED]');
327
+ });
328
+ });
329
+ });
330
+ //# sourceMappingURL=redact.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.js","sourceRoot":"","sources":["../../../src/logging/redact.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SMI-5615: Per-surface serialized JSONL writer with daily/size rollover and
3
+ * retention sweep.
4
+ *
5
+ * Write-serialization invariant (F2, from
6
+ * docs/internal/implementation/production-error-logging.md — the plan's
7
+ * original P-5 draft omitted this; it was the Opus `concurrency-auditor`
8
+ * plan-audit's most likely real-world race):
9
+ *
10
+ * A redacted error record (stack ≤20 frames) can exceed `PIPE_BUF`
11
+ * (4096 bytes), so concurrent, independent `appendFile`/`appendFileSync`
12
+ * calls from parallel async tool invocations are NOT safe here — two
13
+ * large concurrent writes can interleave/tear into one invalid JSONL line.
14
+ *
15
+ * Fix: one long-lived `fs.createWriteStream(path, { flags: 'a' })` per
16
+ * surface, created lazily on first write and cached in a module-scoped
17
+ * map. ALL writes for a given surface go through THIS single stream.
18
+ *
19
+ * That alone (Node serializing writes queued on one stream) would already
20
+ * prevent tearing between two writes to the SAME stream object. This
21
+ * module goes one step further for simplicity and an even stronger
22
+ * guarantee: every write is wrapped in an async mutex (`runExclusive`) keyed
23
+ * by surface, so at most one write is ever in flight to a surface's
24
+ * underlying file descriptor at a time — including the write that decides
25
+ * whether a daily/size rollover is needed. That decision-and-possible-swap
26
+ * (open a new stream, `end()` the old one) happens INSIDE the same
27
+ * exclusive section as the write that triggered it, so a rotation can
28
+ * never race a write: no write can land on a stream that has already been
29
+ * told to `end()`, and no two writers can simultaneously decide "today's
30
+ * file doesn't exist yet" and both create a stream.
31
+ *
32
+ * Daily rollover: `skillsmith-<surface>-<YYYY-MM-DD>.jsonl`, keyed off the
33
+ * calendar date at write time (mocked by `vi.setSystemTime` in tests — no
34
+ * separate clock seam needed).
35
+ *
36
+ * Size cap: ~10MB per file. When the currently-open file for TODAY would
37
+ * exceed the cap, roll to a `.1`/`.2`/... suffixed continuation file for the
38
+ * same day (`skillsmith-<surface>-<YYYY-MM-DD>.jsonl.<n>`), through the same
39
+ * serialization point as everything else.
40
+ *
41
+ * Retention: on module init, asynchronously (NOT awaited — never blocks
42
+ * import) sweep `~/.skillsmith/logs/` and delete files whose mtime is older
43
+ * than 14 days. `pruneExpiredLogs` is also exported directly so tests can
44
+ * await a deterministic run against a temp directory instead of racing the
45
+ * fire-and-forget module-init sweep.
46
+ */
47
+ import type { Surface } from './types.js';
48
+ /**
49
+ * Serializes `line` for `surface`: acquires that surface's exclusive queue,
50
+ * resolves (opening/rotating as needed) the current stream, and writes.
51
+ * Resolves once the OS-level write has completed; rejects (never throws
52
+ * synchronously) if the directory/file couldn't be created or the write
53
+ * failed — callers (see `logger.ts`) must handle rejection to honor the
54
+ * "logger never throws" invariant.
55
+ */
56
+ export declare function writeLogLine(surface: Surface, line: string): Promise<void>;
57
+ /**
58
+ * Deletes files under the log directory whose mtime is older than
59
+ * `RETENTION_DAYS`. Best-effort: a missing directory or a per-file stat/
60
+ * unlink failure (permissions, a concurrent process already deleted it,
61
+ * etc.) is swallowed rather than thrown — this must never be able to crash
62
+ * module init or take down a caller that awaits it in a test.
63
+ *
64
+ * Exported (not just fired at module init) so tests can await a
65
+ * deterministic run against a temp directory rather than racing the
66
+ * fire-and-forget call below.
67
+ */
68
+ export declare function pruneExpiredLogs(): Promise<void>;
69
+ /**
70
+ * Closes any open streams and clears all in-memory state. Test-only — used
71
+ * by `rotation.test.ts` between cases so state from one temp-dir scenario
72
+ * never leaks into the next.
73
+ */
74
+ export declare function __resetLoggingStateForTests(): Promise<void>;
75
+ //# sourceMappingURL=rotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../../src/logging/rotation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAqLzC;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO1E;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBtD;AAkBD;;;;GAIG;AACH,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAQjE"}