@oneuptime/common 12.0.12 → 12.0.13

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 (234) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  27. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  28. package/Server/Utils/LogRedaction.ts +576 -0
  29. package/Server/Utils/Logger.ts +123 -46
  30. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  31. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  32. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  33. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  34. package/Server/Utils/StartServer.ts +30 -9
  35. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  36. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  37. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  38. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  39. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  40. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  41. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  42. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  43. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  44. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  45. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  46. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  47. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  48. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  49. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  50. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  51. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  52. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  53. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  54. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  55. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  56. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  57. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  58. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  59. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  60. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  61. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  62. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  63. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  65. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  66. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  67. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  68. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  69. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  70. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  71. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  72. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  73. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  74. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  75. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  76. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  77. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  78. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  79. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  80. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  81. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  82. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  83. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  84. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  85. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  86. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  87. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  88. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  89. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  90. package/Types/API/Hostname.ts +79 -0
  91. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  92. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  93. package/Types/Database/DatabaseProperty.ts +38 -0
  94. package/Types/Database/TableColumn.ts +34 -7
  95. package/Types/Events/Recurring.ts +16 -1
  96. package/Types/Monitor/MonitorStep.ts +13 -1
  97. package/Types/Monitor/MonitorType.ts +345 -40
  98. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  99. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  100. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  101. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  102. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  103. package/UI/Components/Footer/Footer.tsx +32 -6
  104. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  105. package/UI/Components/Forms/Types/Field.ts +8 -0
  106. package/UI/Components/Loader/PageLoader.tsx +29 -8
  107. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  108. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  109. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  110. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  111. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  112. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  113. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  114. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  115. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  116. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  118. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  120. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  122. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  123. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  124. package/build/dist/Server/API/AIChatAPI.js +48 -35
  125. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  126. package/build/dist/Server/API/CommonAPI.js +59 -0
  127. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  128. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  129. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  130. package/build/dist/Server/API/SlackAPI.js +13 -10
  131. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  132. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  133. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  134. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  135. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  136. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  137. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  138. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  139. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  140. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  142. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  143. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  144. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  145. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  146. package/build/dist/Server/Services/DatabaseService.js +15 -6
  147. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  148. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  149. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  150. package/build/dist/Server/Services/LogService.js +16 -0
  151. package/build/dist/Server/Services/LogService.js.map +1 -1
  152. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  153. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  154. package/build/dist/Server/Services/SpanService.js +16 -0
  155. package/build/dist/Server/Services/SpanService.js.map +1 -1
  156. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  157. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  158. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  159. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  160. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  161. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  162. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  163. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  164. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  165. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  166. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  167. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  168. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  169. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  170. package/build/dist/Server/Utils/Logger.js +98 -45
  171. package/build/dist/Server/Utils/Logger.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  173. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  174. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  175. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  176. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  177. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  178. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  179. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  180. package/build/dist/Server/Utils/StartServer.js +10 -7
  181. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  182. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  183. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  184. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  185. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  186. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  187. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  188. package/build/dist/Types/API/Hostname.js +63 -0
  189. package/build/dist/Types/API/Hostname.js.map +1 -1
  190. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  191. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  192. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  193. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  194. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  195. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  196. package/build/dist/Types/Database/TableColumn.js +24 -6
  197. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  198. package/build/dist/Types/Events/Recurring.js +13 -1
  199. package/build/dist/Types/Events/Recurring.js.map +1 -1
  200. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  201. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  202. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  203. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  204. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  205. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  206. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  207. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  208. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  209. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  210. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  211. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  212. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  213. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  214. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  215. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  216. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  217. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  218. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  219. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  220. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  221. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  222. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  223. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  224. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  225. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  226. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  227. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  228. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  229. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  230. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  231. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  232. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  233. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  234. package/package.json +1 -1
@@ -5,6 +5,12 @@ import { SeverityNumber } from "@opentelemetry/api-logs";
5
5
  import Exception from "../../Types/Exception/Exception";
6
6
  import { JSONObject } from "../../Types/JSON";
7
7
  import ConfigLogLevel from "../Types/ConfigLogLevel";
8
+ import {
9
+ REDACTED,
10
+ isSensitiveLogKey,
11
+ redactLogString,
12
+ redactLogValue,
13
+ } from "./LogRedaction";
8
14
 
9
15
  export type LogBody = string | JSONObject | Exception | Error | unknown;
10
16
 
@@ -68,9 +74,10 @@ export default class logger {
68
74
  private static readonly recentLogTrimSlack: number = 256;
69
75
  private static readonly maxRecentLogMessageLength: number = 4000;
70
76
 
77
+ // `body` has already been through redactBody -- see the level methods.
71
78
  private static record(level: string, body: LogBody): void {
72
79
  try {
73
- const message: string = this.serializeLogBody(body);
80
+ const message: string = this.serializeRedactedBody(body);
74
81
 
75
82
  this.recentLogs.push({
76
83
  time: new Date().toISOString(),
@@ -114,13 +121,71 @@ export default class logger {
114
121
  return LogLevel;
115
122
  }
116
123
 
117
- public static serializeLogBody(body: LogBody): string {
118
- if (typeof body === "string") {
119
- return body;
120
- } else if (body instanceof Exception || body instanceof Error) {
121
- return body.message;
124
+ /*
125
+ * Turns a body into a redacted copy, once, before it is handed to any sink.
126
+ * Every level method calls this and then passes the RESULT to console, to
127
+ * the recent-log buffer and to telemetry, so a log record is redacted a
128
+ * single time no matter how many places it ends up in.
129
+ *
130
+ * Objects come back as plain redacted copies, so console keeps printing them
131
+ * as objects rather than as a JSON string.
132
+ *
133
+ * Errors are special-cased: when nothing in the message or the stack needed
134
+ * redacting -- the overwhelmingly common case -- the original Error is
135
+ * returned untouched, so console still renders a real stack trace.
136
+ */
137
+ private static redactBody(body: LogBody): LogBody {
138
+ try {
139
+ if (typeof body === "string") {
140
+ return redactLogString(body);
141
+ }
142
+
143
+ if (body instanceof Exception || body instanceof Error) {
144
+ const originalStack: string = body.stack || "";
145
+ const message: string = redactLogString(body.message);
146
+ const stack: string = redactLogString(originalStack);
147
+
148
+ if (message === body.message && stack === originalStack) {
149
+ return body;
150
+ }
151
+
152
+ return stack || `${body.name}: ${message}`;
153
+ }
154
+
155
+ return redactLogValue(body) as LogBody;
156
+ } catch {
157
+ // Never pass on a body we could not prove was redacted.
158
+ return REDACTED;
122
159
  }
123
- return JSON.stringify(body);
160
+ }
161
+
162
+ // Serialization only. The body must already have been through redactBody.
163
+ private static serializeRedactedBody(body: LogBody): string {
164
+ try {
165
+ if (typeof body === "string") {
166
+ return body;
167
+ } else if (body instanceof Exception || body instanceof Error) {
168
+ return body.message;
169
+ }
170
+
171
+ /*
172
+ * redactBody swept every string inside this copy already, so stringifying
173
+ * it needs no second textual pass.
174
+ */
175
+ const serialized: string | undefined = JSON.stringify(body);
176
+
177
+ return serialized === undefined ? "" : serialized;
178
+ } catch {
179
+ return REDACTED;
180
+ }
181
+ }
182
+
183
+ /*
184
+ * Redacts and serializes in one step, for callers outside the level methods
185
+ * that hold a raw body.
186
+ */
187
+ public static serializeLogBody(body: LogBody): string {
188
+ return this.serializeRedactedBody(this.redactBody(body));
124
189
  }
125
190
 
126
191
  private static sanitizeAttributes(
@@ -136,7 +201,16 @@ export default class logger {
136
201
  for (const key in attributes) {
137
202
  const value: string | number | boolean | undefined = attributes[key];
138
203
 
139
- if (value !== undefined && value !== null) {
204
+ if (value === undefined || value === null) {
205
+ continue;
206
+ }
207
+
208
+ // Attributes are a log sink too, and they travel to telemetry verbatim.
209
+ if (isSensitiveLogKey(key, value)) {
210
+ sanitized[key] = REDACTED;
211
+ } else if (typeof value === "string") {
212
+ sanitized[key] = redactLogString(value);
213
+ } else {
140
214
  sanitized[key] = value;
141
215
  }
142
216
  }
@@ -151,16 +225,14 @@ export default class logger {
151
225
  const logLevel: ConfigLogLevel = this.getLogLevel();
152
226
 
153
227
  if (logLevel === ConfigLogLevel.DEBUG || logLevel === ConfigLogLevel.INFO) {
228
+ const body: LogBody = this.redactBody(message);
229
+
154
230
  // eslint-disable-next-line no-console
155
- console.info(message);
231
+ console.info(body);
156
232
 
157
- this.record("INFO", message);
233
+ this.record("INFO", body);
158
234
 
159
- this.emit({
160
- body: message,
161
- severityNumber: SeverityNumber.INFO,
162
- attributes,
163
- });
235
+ this.emitRedacted(body, SeverityNumber.INFO, attributes);
164
236
  }
165
237
  }
166
238
 
@@ -173,16 +245,14 @@ export default class logger {
173
245
  logLevel === ConfigLogLevel.WARN ||
174
246
  logLevel === ConfigLogLevel.ERROR
175
247
  ) {
248
+ const body: LogBody = this.redactBody(message);
249
+
176
250
  // eslint-disable-next-line no-console
177
- console.error(message);
251
+ console.error(body);
178
252
 
179
- this.record("ERROR", message);
253
+ this.record("ERROR", body);
180
254
 
181
- this.emit({
182
- body: message,
183
- severityNumber: SeverityNumber.ERROR,
184
- attributes,
185
- });
255
+ this.emitRedacted(body, SeverityNumber.ERROR, attributes);
186
256
  }
187
257
  }
188
258
 
@@ -194,16 +264,14 @@ export default class logger {
194
264
  logLevel === ConfigLogLevel.INFO ||
195
265
  logLevel === ConfigLogLevel.WARN
196
266
  ) {
267
+ const body: LogBody = this.redactBody(message);
268
+
197
269
  // eslint-disable-next-line no-console
198
- console.warn(message);
270
+ console.warn(body);
199
271
 
200
- this.record("WARN", message);
272
+ this.record("WARN", body);
201
273
 
202
- this.emit({
203
- body: message,
204
- severityNumber: SeverityNumber.WARN,
205
- attributes,
206
- });
274
+ this.emitRedacted(body, SeverityNumber.WARN, attributes);
207
275
  }
208
276
  }
209
277
 
@@ -211,16 +279,14 @@ export default class logger {
211
279
  const logLevel: ConfigLogLevel = this.getLogLevel();
212
280
 
213
281
  if (logLevel === ConfigLogLevel.DEBUG) {
282
+ const body: LogBody = this.redactBody(message);
283
+
214
284
  // eslint-disable-next-line no-console
215
- console.debug(message);
285
+ console.debug(body);
216
286
 
217
- this.record("DEBUG", message);
287
+ this.record("DEBUG", body);
218
288
 
219
- this.emit({
220
- body: message,
221
- severityNumber: SeverityNumber.DEBUG,
222
- attributes,
223
- });
289
+ this.emitRedacted(body, SeverityNumber.DEBUG, attributes);
224
290
  }
225
291
  }
226
292
 
@@ -229,6 +295,19 @@ export default class logger {
229
295
  severityNumber: SeverityNumber;
230
296
  attributes?: LogAttributes | undefined;
231
297
  }): void {
298
+ this.emitRedacted(
299
+ this.redactBody(data.body),
300
+ data.severityNumber,
301
+ data.attributes,
302
+ );
303
+ }
304
+
305
+ // `body` must already have been through redactBody.
306
+ private static emitRedacted(
307
+ body: LogBody,
308
+ severityNumber: SeverityNumber,
309
+ attributes?: LogAttributes | undefined,
310
+ ): void {
232
311
  try {
233
312
  const logger: TelemetryLogger | null = OneUptimeTelemetry.getLogger();
234
313
 
@@ -243,7 +322,7 @@ export default class logger {
243
322
  */
244
323
  const mergedAttributes: LogAttributes = {
245
324
  ...TelemetryContext.getAttributes(),
246
- ...(data.attributes || {}),
325
+ ...(attributes || {}),
247
326
  };
248
327
 
249
328
  const sanitizedAttributes:
@@ -251,8 +330,8 @@ export default class logger {
251
330
  | undefined = this.sanitizeAttributes(mergedAttributes);
252
331
 
253
332
  logger.emit({
254
- body: this.serializeLogBody(data.body),
255
- severityNumber: data.severityNumber,
333
+ body: this.serializeRedactedBody(body),
334
+ severityNumber: severityNumber,
256
335
  ...(sanitizedAttributes ? { attributes: sanitizedAttributes } : {}),
257
336
  });
258
337
  } catch {
@@ -264,16 +343,14 @@ export default class logger {
264
343
  const logLevel: ConfigLogLevel = this.getLogLevel();
265
344
 
266
345
  if (logLevel === ConfigLogLevel.DEBUG) {
346
+ const body: LogBody = this.redactBody(message);
347
+
267
348
  // eslint-disable-next-line no-console
268
- console.trace(message);
349
+ console.trace(body);
269
350
 
270
- this.record("TRACE", message);
351
+ this.record("TRACE", body);
271
352
 
272
- this.emit({
273
- body: message,
274
- severityNumber: SeverityNumber.DEBUG,
275
- attributes,
276
- });
353
+ this.emitRedacted(body, SeverityNumber.DEBUG, attributes);
277
354
  }
278
355
  }
279
356
  }
@@ -78,16 +78,8 @@ export default class ServerMonitorCriteria {
78
78
  }
79
79
 
80
80
  if (input.criteriaFilter.checkOn === CheckOn.IsValidCertificate) {
81
- const isValidCertificate: boolean = Boolean(
82
- sslResponse &&
83
- dataToProcess.isOnline &&
84
- sslResponse.expiresAt &&
85
- !sslResponse.isSelfSigned &&
86
- OneUptimeDate.isAfter(
87
- sslResponse.expiresAt,
88
- OneUptimeDate.getCurrentDate(),
89
- ),
90
- );
81
+ const isValidCertificate: boolean =
82
+ ServerMonitorCriteria.isValidCertificate(dataToProcess);
91
83
 
92
84
  const isTrue: boolean =
93
85
  input.criteriaFilter.filterType === FilterType.True;
@@ -100,7 +92,7 @@ export default class ServerMonitorCriteria {
100
92
  }
101
93
 
102
94
  if (!isValidCertificate && isFalse) {
103
- return "SSL certificate is not valid.";
95
+ return ServerMonitorCriteria.invalidCertificateReason(dataToProcess);
104
96
  }
105
97
  }
106
98
 
@@ -149,18 +141,19 @@ export default class ServerMonitorCriteria {
149
141
  }
150
142
 
151
143
  if (input.criteriaFilter.checkOn === CheckOn.IsNotAValidCertificate) {
144
+ /*
145
+ * The exact complement of IsValidCertificate, by construction.
146
+ *
147
+ * These two used to be computed independently, and both came out
148
+ * false whenever expiresAt was missing or unparseable - so a
149
+ * certificate the probe could not fully read satisfied NEITHER
150
+ * "valid" nor "not valid", every criterion went unmatched, and the
151
+ * monitor silently stayed at its default status. Deriving one from
152
+ * the other makes that state unreachable.
153
+ */
152
154
  const isNotValid: boolean =
153
- !sslResponse ||
154
- !dataToProcess.isOnline ||
155
- Boolean(
156
- sslResponse &&
157
- sslResponse.expiresAt &&
158
- (sslResponse.isSelfSigned ||
159
- OneUptimeDate.isBefore(
160
- sslResponse.expiresAt,
161
- OneUptimeDate.getCurrentDate(),
162
- )),
163
- );
155
+ !ServerMonitorCriteria.isValidCertificate(dataToProcess);
156
+
164
157
  const isTrue: boolean =
165
158
  input.criteriaFilter.filterType === FilterType.True;
166
159
 
@@ -168,7 +161,7 @@ export default class ServerMonitorCriteria {
168
161
  input.criteriaFilter.filterType === FilterType.False;
169
162
 
170
163
  if (isNotValid && isTrue) {
171
- return "SSL certificate is not valid.";
164
+ return ServerMonitorCriteria.invalidCertificateReason(dataToProcess);
172
165
  }
173
166
 
174
167
  if (!isNotValid && isFalse) {
@@ -230,4 +223,82 @@ export default class ServerMonitorCriteria {
230
223
 
231
224
  return null;
232
225
  }
226
+
227
+ /*
228
+ * The single source of truth for "is this certificate trustworthy",
229
+ * shared by IsValidCertificate and IsNotAValidCertificate so the two can
230
+ * never both be false for the same response.
231
+ *
232
+ * The probe now records the strict-TLS verdict explicitly on
233
+ * sslResponse.isValidCertificate. Responses written before that field
234
+ * existed (rows already in MonitorProbe.lastMonitoringLog, and any probe
235
+ * still running an older build) do not carry it, so the pre-existing
236
+ * heuristic is kept as the fallback rather than treating those as
237
+ * invalid and flipping healthy monitors to Down on upgrade.
238
+ */
239
+ private static isValidCertificate(
240
+ dataToProcess: ProbeMonitorResponse,
241
+ ): boolean {
242
+ const sslResponse: SslMonitorResponse | undefined =
243
+ dataToProcess.sslResponse;
244
+
245
+ if (!sslResponse || !dataToProcess.isOnline) {
246
+ return false;
247
+ }
248
+
249
+ if (sslResponse.isValidCertificate !== undefined) {
250
+ return Boolean(sslResponse.isValidCertificate);
251
+ }
252
+
253
+ // Legacy payload: fall back to what could be inferred before.
254
+ return Boolean(
255
+ sslResponse.expiresAt &&
256
+ !sslResponse.isSelfSigned &&
257
+ OneUptimeDate.isAfter(
258
+ sslResponse.expiresAt,
259
+ OneUptimeDate.getCurrentDate(),
260
+ ),
261
+ );
262
+ }
263
+
264
+ /*
265
+ * Names WHY the certificate is not trustworthy. The root cause is what
266
+ * reaches the incident and the person paged by it, so "SSL certificate is
267
+ * not valid" on its own is not good enough when the probe knows it was a
268
+ * hostname mismatch.
269
+ */
270
+ private static invalidCertificateReason(
271
+ dataToProcess: ProbeMonitorResponse,
272
+ ): string {
273
+ const sslResponse: SslMonitorResponse | undefined =
274
+ dataToProcess.sslResponse;
275
+
276
+ if (!dataToProcess.isOnline) {
277
+ return "SSL certificate could not be checked because the endpoint is not reachable.";
278
+ }
279
+
280
+ if (!sslResponse) {
281
+ return "SSL certificate is not valid.";
282
+ }
283
+
284
+ if (sslResponse.certificateValidationError) {
285
+ return `SSL certificate is not valid: ${sslResponse.certificateValidationError}`;
286
+ }
287
+
288
+ if (sslResponse.isSelfSigned) {
289
+ return "SSL certificate is not valid: the certificate is self signed.";
290
+ }
291
+
292
+ if (
293
+ sslResponse.expiresAt &&
294
+ OneUptimeDate.isBefore(
295
+ sslResponse.expiresAt,
296
+ OneUptimeDate.getCurrentDate(),
297
+ )
298
+ ) {
299
+ return "SSL certificate is not valid: the certificate has expired.";
300
+ }
301
+
302
+ return "SSL certificate is not valid.";
303
+ }
233
304
  }
@@ -636,7 +636,16 @@ export default class MonitorResourceUtil {
636
636
  }
637
637
  }
638
638
 
639
- const monitorStep: MonitorStep | undefined =
639
+ /*
640
+ * The step this result was actually produced for. A probe result carries
641
+ * the monitorStepId it ran, and a monitor can have several steps whose
642
+ * criteria differ - so the step must be looked up, not assumed to be the
643
+ * first one. The lookup used to be performed and its result thrown away,
644
+ * which silently evaluated every result against step 0's criteria and fed
645
+ * step 0's id into checkProbeAgreement (so agreement compared this
646
+ * result against other probes' logs for the wrong step).
647
+ */
648
+ let monitorStep: MonitorStep | undefined =
640
649
  monitorSteps.data.monitorStepsInstanceArray[0];
641
650
 
642
651
  logger.debug(
@@ -644,14 +653,25 @@ export default class MonitorResourceUtil {
644
653
  );
645
654
 
646
655
  if ((dataToProcess as ProbeMonitorResponse).monitorStepId) {
647
- monitorSteps.data.monitorStepsInstanceArray.find(
648
- (monitorStep: MonitorStep) => {
649
- return (
650
- monitorStep.id.toString() ===
651
- (dataToProcess as ProbeMonitorResponse).monitorStepId.toString()
652
- );
653
- },
654
- );
656
+ const ingestedMonitorStep: MonitorStep | undefined =
657
+ monitorSteps.data.monitorStepsInstanceArray.find(
658
+ (step: MonitorStep) => {
659
+ return (
660
+ step.id.toString() ===
661
+ (dataToProcess as ProbeMonitorResponse).monitorStepId.toString()
662
+ );
663
+ },
664
+ );
665
+
666
+ /*
667
+ * An id that matches no step is stale - the step was deleted after the
668
+ * probe was scheduled. Keep the existing behaviour there and fall back
669
+ * to the first step rather than dropping the result.
670
+ */
671
+ if (ingestedMonitorStep) {
672
+ monitorStep = ingestedMonitorStep;
673
+ }
674
+
655
675
  logger.debug(
656
676
  `Found Monitor Step ID: ${(dataToProcess as ProbeMonitorResponse).monitorStepId}`,
657
677
  );
@@ -83,12 +83,9 @@ export default class NetworkInventoryUtil {
83
83
  const now: Date = OneUptimeDate.getCurrentDate();
84
84
 
85
85
  /*
86
- * lastSeenAt is "last time the device actually answered", not "last time
87
- * we tried": the device list's status pill and the topology map both
88
- * derive up/down from its freshness, so bumping it on a failed poll
89
- * would paint an unreachable device green. A poll where the probe could
90
- * not reach the device reports isOnline === false; treat anything else
91
- * (reachable, or a walk that reports no reachability) as seen.
86
+ * A poll where the probe could not reach the device reports
87
+ * isOnline === false; treat anything else (reachable, or a walk that
88
+ * reports no reachability at all) as answered.
92
89
  */
93
90
  const isDeviceReachable: boolean = data.isOnline !== false;
94
91
 
@@ -96,6 +93,26 @@ export default class NetworkInventoryUtil {
96
93
  // --- Device enrichment + cached counts ---
97
94
  const deviceUpdate: Record<string, unknown> = {};
98
95
 
96
+ /*
97
+ * Three columns, three different questions, and the difference
98
+ * between them is the whole reason a reachable device used to show as
99
+ * down:
100
+ *
101
+ * lastPolledAt - when we last ASKED. Always stamped, success or
102
+ * failure. Without it there is no way to tell a
103
+ * device that did not answer from one nothing got
104
+ * round to polling, which is what made a fleet
105
+ * bigger than one probe's claim rate read as a
106
+ * fleet-wide outage.
107
+ * isReachable - what the answer WAS. This is what the device list,
108
+ * the topology graph and the site rollup read.
109
+ * lastSeenAt - when the device last ANSWERED. Only moves on a
110
+ * successful walk, so it stays honest as "last
111
+ * contact" and never paints a dead device green.
112
+ */
113
+ deviceUpdate["lastPolledAt"] = now;
114
+ deviceUpdate["isReachable"] = isDeviceReachable;
115
+
99
116
  if (isDeviceReachable) {
100
117
  deviceUpdate["lastSeenAt"] = now;
101
118
  }
@@ -181,7 +198,11 @@ export default class NetworkInventoryUtil {
181
198
  ).length;
182
199
  }
183
200
 
184
- // An unreachable poll with no walk data leaves nothing worth writing.
201
+ /*
202
+ * Always non-empty now: even a walk that reached nothing has to
203
+ * record that we tried, or the staleness backstop cannot tell a
204
+ * failing device from an unpolled one.
205
+ */
185
206
  if (Object.keys(deviceUpdate).length > 0) {
186
207
  await NetworkDeviceService.updateOneById({
187
208
  id: deviceId,
@@ -24,6 +24,15 @@ import ObjectID from "../../../Types/ObjectID";
24
24
  export const POLICY_CACHE_TTL_MS: number = 60 * 1000;
25
25
  export const KILL_KEY_CACHE_TTL_MS: number = 5 * 1000;
26
26
 
27
+ /*
28
+ * The policy cache key includes a client-supplied appIdentifier, so without a
29
+ * bound anyone holding an ingestion key could grow this map forever. Caps
30
+ * match the InMemoryTTLCache house default; that class is not used directly
31
+ * because clearCache(projectId) needs the prefix key scan it lacks.
32
+ */
33
+ export const MAX_POLICY_CACHE_ENTRIES: number = 10_000;
34
+ export const MAX_KILL_KEY_CACHE_ENTRIES: number = 10_000;
35
+
27
36
  const KILL_KEY_PREFIX: string = "replay:gate:off:";
28
37
 
29
38
  /*
@@ -52,6 +61,29 @@ interface KillKeyCacheEntry {
52
61
  const policyCache: Map<string, PolicyCacheEntry<unknown>> = new Map();
53
62
  const killKeyCache: Map<string, KillKeyCacheEntry> = new Map();
54
63
 
64
+ /*
65
+ * Coarse-LRU bounded write (mirrors InMemoryTTLCache): evict the oldest entry
66
+ * only when at capacity AND the key is new, so rewriting an existing key never
67
+ * evicts a different entry; delete-before-set refreshes insertion order. The
68
+ * entry being written always lands, which markProjectDisabled's local kill
69
+ * marker depends on.
70
+ */
71
+ function boundedSet<TValue>(
72
+ map: Map<string, TValue>,
73
+ key: string,
74
+ value: TValue,
75
+ maxEntries: number,
76
+ ): void {
77
+ if (map.size >= maxEntries && !map.has(key)) {
78
+ const oldest: string | undefined = map.keys().next().value;
79
+ if (oldest !== undefined) {
80
+ map.delete(oldest);
81
+ }
82
+ }
83
+ map.delete(key);
84
+ map.set(key, value);
85
+ }
86
+
55
87
  export default class SessionReplayGateCacheStore {
56
88
  public static getPolicyEntry<TPolicy>(
57
89
  cacheKey: string,
@@ -63,7 +95,12 @@ export default class SessionReplayGateCacheStore {
63
95
  cacheKey: string,
64
96
  policy: TPolicy | null,
65
97
  ): void {
66
- policyCache.set(cacheKey, { policy: policy, loadedAt: Date.now() });
98
+ boundedSet(
99
+ policyCache,
100
+ cacheKey,
101
+ { policy: policy, loadedAt: Date.now() },
102
+ MAX_POLICY_CACHE_ENTRIES,
103
+ );
67
104
  }
68
105
 
69
106
  /*
@@ -93,10 +130,12 @@ export default class SessionReplayGateCacheStore {
93
130
  }
94
131
  }
95
132
 
96
- killKeyCache.set(projectId.toString(), {
97
- isDisabled: true,
98
- loadedAt: Date.now(),
99
- });
133
+ boundedSet(
134
+ killKeyCache,
135
+ projectId.toString(),
136
+ { isDisabled: true, loadedAt: Date.now() },
137
+ MAX_KILL_KEY_CACHE_ENTRIES,
138
+ );
100
139
  }
101
140
 
102
141
  /*
@@ -159,7 +198,12 @@ export default class SessionReplayGateCacheStore {
159
198
  * Failing closed here would make a Redis blip stop replay ingest for
160
199
  * every project that had correctly opted in.
161
200
  */
162
- killKeyCache.set(cacheKey, { isDisabled: false, loadedAt: Date.now() });
201
+ boundedSet(
202
+ killKeyCache,
203
+ cacheKey,
204
+ { isDisabled: false, loadedAt: Date.now() },
205
+ MAX_KILL_KEY_CACHE_ENTRIES,
206
+ );
163
207
  return false;
164
208
  }
165
209
 
@@ -178,10 +222,12 @@ export default class SessionReplayGateCacheStore {
178
222
  isDisabled = false;
179
223
  }
180
224
 
181
- killKeyCache.set(cacheKey, {
182
- isDisabled: isDisabled,
183
- loadedAt: Date.now(),
184
- });
225
+ boundedSet(
226
+ killKeyCache,
227
+ cacheKey,
228
+ { isDisabled: isDisabled, loadedAt: Date.now() },
229
+ MAX_KILL_KEY_CACHE_ENTRIES,
230
+ );
185
231
 
186
232
  return isDisabled;
187
233
  }
@@ -76,30 +76,51 @@ app.set("view engine", "ejs");
76
76
  app.set("trust proxy", TrustedProxyHops);
77
77
  app.use(CookieParser());
78
78
 
79
- const jsonBodyParserMiddleware: RequestHandler = ExpressJson({
79
+ export type BodyParserVerify = (
80
+ req: ExpressRequest,
81
+ res: ExpressResponse,
82
+ buf: Buffer,
83
+ ) => void;
84
+
85
+ export interface BodyParserOptions {
86
+ limit: string;
87
+ extended: boolean;
88
+ verify: BodyParserVerify;
89
+ }
90
+
91
+ export const jsonBodyParserOptions: BodyParserOptions = {
80
92
  limit: "50mb",
81
93
  extended: true,
82
- verify: (req: ExpressRequest, _res: ExpressResponse, buf: Buffer) => {
94
+ verify: (req: ExpressRequest, _res: ExpressResponse, buf: Buffer): void => {
83
95
  (req as OneUptimeRequest).rawBody = buf.toString();
84
96
  logger.debug(
85
97
  `Raw JSON Body for signature verification captured`,
86
98
  getLogAttributesFromRequest(req as OneUptimeRequest),
87
99
  );
88
100
  },
89
- }); // 50 MB limit.
101
+ };
102
+
103
+ const jsonBodyParserMiddleware: RequestHandler = ExpressJson(
104
+ jsonBodyParserOptions,
105
+ ); // 50 MB limit.
90
106
 
91
- const urlEncodedMiddleware: RequestHandler = ExpressUrlEncoded({
107
+ export const urlEncodedBodyParserOptions: BodyParserOptions = {
92
108
  limit: "50mb",
93
109
  extended: true,
94
- verify: (req: ExpressRequest, _res: ExpressResponse, buf: Buffer) => {
95
- (req as OneUptimeRequest).rawFormUrlEncodedBody = buf.toString();
96
- (req as OneUptimeRequest).rawBody = buf.toString(); // Also set rawBody for consistency
110
+ verify: (req: ExpressRequest, _res: ExpressResponse, buf: Buffer): void => {
111
+ const raw: string = buf.toString();
112
+ (req as OneUptimeRequest).rawFormUrlEncodedBody = raw;
113
+ (req as OneUptimeRequest).rawBody = raw; // Also set rawBody for consistency
97
114
  logger.debug(
98
- `Raw Form Url Encoded Body: ${(req as OneUptimeRequest).rawFormUrlEncodedBody}`,
115
+ `Raw Form Url Encoded Body for signature verification captured`,
99
116
  getLogAttributesFromRequest(req as OneUptimeRequest),
100
117
  );
101
118
  },
102
- }); // 50 MB limit.
119
+ };
120
+
121
+ const urlEncodedMiddleware: RequestHandler = ExpressUrlEncoded(
122
+ urlEncodedBodyParserOptions,
123
+ ); // 50 MB limit.
103
124
 
104
125
  const setDefaultHeaders: RequestHandler = (
105
126
  req: ExpressRequest,