@oneuptime/common 12.0.11 → 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.
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
- package/Models/DatabaseModels/NetworkDevice.ts +64 -0
- package/Models/DatabaseModels/StatusPage.ts +59 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
- package/Server/API/AIChatAPI.ts +55 -56
- package/Server/API/CommonAPI.ts +89 -0
- package/Server/API/MicrosoftTeamsAPI.ts +20 -9
- package/Server/API/SlackAPI.ts +13 -10
- package/Server/API/StatusPageAPI.ts +2197 -2128
- package/Server/API/TelemetryAPI.ts +72 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/UserAuthorization.ts +11 -2
- package/Server/Services/ApiKeyPermissionService.ts +116 -2
- package/Server/Services/DatabaseService.ts +17 -6
- package/Server/Services/LogAggregationService.ts +34 -0
- package/Server/Services/LogService.ts +21 -0
- package/Server/Services/NetworkSiteService.ts +12 -0
- package/Server/Services/SpanService.ts +21 -0
- package/Server/Services/TraceAggregationService.ts +15 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
- package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
- package/Server/Utils/APIKey/AccessPermission.ts +16 -40
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
- package/Server/Utils/LogRedaction.ts +576 -0
- package/Server/Utils/Logger.ts +123 -46
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
- package/Server/Utils/Monitor/MonitorResource.ts +29 -9
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
- package/Server/Utils/StartServer.ts +30 -9
- package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
- package/Server/Utils/VM/VMRunner.ts +693 -874
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
- package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
- package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
- package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
- package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
- package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
- package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
- package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
- package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
- package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
- package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
- package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
- package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
- package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
- package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
- package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
- package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
- package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
- package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
- package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
- package/Tests/UI/Components/MasterPage.test.tsx +36 -1
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
- package/Types/API/Hostname.ts +79 -0
- package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
- package/Types/Database/DatabaseProperty.ts +38 -0
- package/Types/Database/TableColumn.ts +34 -7
- package/Types/Events/Recurring.ts +16 -1
- package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
- package/Types/Monitor/MonitorStep.ts +13 -1
- package/Types/Monitor/MonitorType.ts +345 -40
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
- package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
- package/UI/Components/CardSelect/CardSelect.tsx +714 -105
- package/UI/Components/EmptyState/EmptyState.tsx +8 -1
- package/UI/Components/Footer/Footer.tsx +32 -6
- package/UI/Components/Forms/Fields/FormField.tsx +3 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Loader/PageLoader.tsx +29 -8
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
- package/UI/Components/MasterPage/MasterPage.tsx +24 -11
- package/Utils/Monitor/MonitorMetricType.ts +16 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
- package/Utils/Telemetry/CrossSignalScope.ts +75 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +48 -35
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +59 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +13 -10
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +745 -673
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +34 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +15 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogService.js +16 -0
- package/build/dist/Server/Services/LogService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +12 -0
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +16 -0
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +449 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Logger.js +98 -45
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -7
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +63 -0
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +38 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js +24 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +13 -1
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +9 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +330 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +7 -4
- package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
- package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REDACTED,
|
|
3
|
+
isSensitiveLogKey,
|
|
4
|
+
redactLogString,
|
|
5
|
+
redactLogValue,
|
|
6
|
+
} from "../../../Server/Utils/LogRedaction";
|
|
7
|
+
import { describe, expect, it } from "@jest/globals";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* These are the regression tests for GHSA-3x69-fj58-3pc6 (Slack / Microsoft
|
|
11
|
+
* Teams OAuth client secrets and access tokens written to DEBUG logs) and
|
|
12
|
+
* GHSA-c4c2-r4hm-6pjj (login passwords and MFA material written to DEBUG
|
|
13
|
+
* logs).
|
|
14
|
+
*
|
|
15
|
+
* The call sites that produced those two advisories have been removed, but the
|
|
16
|
+
* class of bug is "somebody logs a request body". So the assertions here are
|
|
17
|
+
* written against the payloads those handlers actually pass around -- a Slack
|
|
18
|
+
* token exchange, a Microsoft Graph token response, a login body -- and they
|
|
19
|
+
* check the property that matters: the secret is not in the output, in any
|
|
20
|
+
* form, whether it arrived as an object or as text somebody had already
|
|
21
|
+
* stringified.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const SENTINEL: string = "sentinel-secret-value-a1b2c3d4";
|
|
25
|
+
|
|
26
|
+
type SerializeFunction = (value: unknown) => string;
|
|
27
|
+
|
|
28
|
+
// What the logger would end up writing for this body.
|
|
29
|
+
const serialize: SerializeFunction = (value: unknown): string => {
|
|
30
|
+
return JSON.stringify(redactLogValue(value));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe("isSensitiveLogKey", () => {
|
|
34
|
+
it("matches credential keys in every spelling", () => {
|
|
35
|
+
for (const key of [
|
|
36
|
+
"password",
|
|
37
|
+
"Password",
|
|
38
|
+
"newPassword",
|
|
39
|
+
"client_secret",
|
|
40
|
+
"clientSecret",
|
|
41
|
+
"CLIENT-SECRET",
|
|
42
|
+
"access_token",
|
|
43
|
+
"accessToken",
|
|
44
|
+
"slackBotAccessToken",
|
|
45
|
+
"refresh_token",
|
|
46
|
+
"id_token",
|
|
47
|
+
"authorization",
|
|
48
|
+
"Authorization",
|
|
49
|
+
"x-api-key",
|
|
50
|
+
"apiKey",
|
|
51
|
+
"Cookie",
|
|
52
|
+
"set-cookie",
|
|
53
|
+
"credential",
|
|
54
|
+
"passwordSalt",
|
|
55
|
+
"twoFactorSecret",
|
|
56
|
+
"clientDataJSON",
|
|
57
|
+
"authenticatorData",
|
|
58
|
+
"userHandle",
|
|
59
|
+
"rawId",
|
|
60
|
+
"signature",
|
|
61
|
+
]) {
|
|
62
|
+
expect(isSensitiveLogKey(key)).toBe(true);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("leaves ordinary keys alone", () => {
|
|
67
|
+
for (const key of [
|
|
68
|
+
"email",
|
|
69
|
+
"projectId",
|
|
70
|
+
"userId",
|
|
71
|
+
"client_id",
|
|
72
|
+
"redirect_uri",
|
|
73
|
+
"team",
|
|
74
|
+
"statusCode",
|
|
75
|
+
"expires_in",
|
|
76
|
+
"scope",
|
|
77
|
+
"name",
|
|
78
|
+
"url",
|
|
79
|
+
"isAuthorized",
|
|
80
|
+
]) {
|
|
81
|
+
expect(isSensitiveLogKey(key)).toBe(false);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("keeps token accounting, which is not a token", () => {
|
|
86
|
+
expect(isSensitiveLogKey("tokenCount", 42)).toBe(false);
|
|
87
|
+
expect(isSensitiveLogKey("totalTokens", 42)).toBe(false);
|
|
88
|
+
expect(isSensitiveLogKey("inputTokens", 42)).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* `code` is the one key that has to be decided by its value: it is the OAuth
|
|
93
|
+
* authorization code and the TOTP code, and it is also the `ECONNREFUSED` on
|
|
94
|
+
* every failed socket.
|
|
95
|
+
*/
|
|
96
|
+
it("treats `code` as secret only when the value looks like one", () => {
|
|
97
|
+
expect(isSensitiveLogKey("code", "123456")).toBe(true);
|
|
98
|
+
expect(isSensitiveLogKey("code", "1234.5678.abcdef0123456789")).toBe(true);
|
|
99
|
+
expect(isSensitiveLogKey("code", 123456)).toBe(true);
|
|
100
|
+
|
|
101
|
+
expect(isSensitiveLogKey("code", "ECONNREFUSED")).toBe(false);
|
|
102
|
+
expect(isSensitiveLogKey("code", "BadDataException")).toBe(false);
|
|
103
|
+
expect(isSensitiveLogKey("code", 500)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("redactLogValue - Slack OAuth payloads (GHSA-3x69-fj58-3pc6)", () => {
|
|
108
|
+
it("redacts the token request body the callback used to log", () => {
|
|
109
|
+
// Exactly the object Common/Server/API/SlackAPI.ts builds.
|
|
110
|
+
const requestBody: Record<string, unknown> = {
|
|
111
|
+
code: "1234567890.9876543210." + SENTINEL,
|
|
112
|
+
client_id: "1234567890.1111111111",
|
|
113
|
+
client_secret: SENTINEL,
|
|
114
|
+
redirect_uri: "https://oneuptime.com/api/slack/auth/project/user",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const output: string = serialize(requestBody);
|
|
118
|
+
|
|
119
|
+
expect(output).not.toContain(SENTINEL);
|
|
120
|
+
expect(JSON.parse(output)).toEqual({
|
|
121
|
+
code: REDACTED,
|
|
122
|
+
client_id: "1234567890.1111111111",
|
|
123
|
+
client_secret: REDACTED,
|
|
124
|
+
redirect_uri: "https://oneuptime.com/api/slack/auth/project/user",
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("redacts the oauth.v2.access response, bot and user tokens both", () => {
|
|
129
|
+
const responseBody: Record<string, unknown> = {
|
|
130
|
+
ok: true,
|
|
131
|
+
access_token: "xoxb-1111-2222-" + SENTINEL,
|
|
132
|
+
token_type: "bot",
|
|
133
|
+
bot_user_id: "U0123BOT",
|
|
134
|
+
team: { id: "T0123", name: "Acme" },
|
|
135
|
+
authed_user: {
|
|
136
|
+
id: "U0123USER",
|
|
137
|
+
access_token: "xoxp-3333-4444-" + SENTINEL,
|
|
138
|
+
refresh_token: "xoxe-1-" + SENTINEL,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const output: string = serialize(responseBody);
|
|
143
|
+
|
|
144
|
+
expect(output).not.toContain(SENTINEL);
|
|
145
|
+
// Non-secret context survives, so the log line is still worth reading.
|
|
146
|
+
expect(output).toContain("U0123BOT");
|
|
147
|
+
expect(output).toContain("Acme");
|
|
148
|
+
expect(JSON.parse(output).authed_user.access_token).toBe(REDACTED);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("redacts a decoded Slack ID token", () => {
|
|
152
|
+
const decoded: Record<string, unknown> = {
|
|
153
|
+
sub: "U0123",
|
|
154
|
+
email: "user@example.com",
|
|
155
|
+
"https://slack.com/team_id": "T0123",
|
|
156
|
+
at_hash: SENTINEL,
|
|
157
|
+
nonce: "abcd",
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// The id_token key itself is enough to redact the whole subtree.
|
|
161
|
+
expect(serialize({ id_token: decoded })).not.toContain(SENTINEL);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("redactLogValue - Microsoft Teams OAuth payloads (GHSA-3x69-fj58-3pc6)", () => {
|
|
166
|
+
it("redacts the token request body", () => {
|
|
167
|
+
const tokenRequestBody: Record<string, unknown> = {
|
|
168
|
+
grant_type: "authorization_code",
|
|
169
|
+
code: "0.AAAA" + SENTINEL,
|
|
170
|
+
client_id: "00000000-0000-0000-0000-000000000000",
|
|
171
|
+
client_secret: SENTINEL,
|
|
172
|
+
redirect_uri: "https://oneuptime.com/api/microsoft-teams/auth",
|
|
173
|
+
scope: "https://graph.microsoft.com/User.Read",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const output: string = serialize(tokenRequestBody);
|
|
177
|
+
|
|
178
|
+
expect(output).not.toContain(SENTINEL);
|
|
179
|
+
expect(output).toContain("authorization_code");
|
|
180
|
+
expect(output).toContain("https://graph.microsoft.com/User.Read");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("redacts the token response, including the application access token", () => {
|
|
184
|
+
const tokenData: Record<string, unknown> = {
|
|
185
|
+
token_type: "Bearer",
|
|
186
|
+
expires_in: 3599,
|
|
187
|
+
access_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey" + SENTINEL,
|
|
188
|
+
refresh_token: "0.AQ" + SENTINEL,
|
|
189
|
+
id_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey" + SENTINEL,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const output: string = serialize(tokenData);
|
|
193
|
+
|
|
194
|
+
expect(output).not.toContain(SENTINEL);
|
|
195
|
+
expect(output).toContain("3599");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("redactLogValue - login payloads (GHSA-c4c2-r4hm-6pjj)", () => {
|
|
200
|
+
it("redacts the password out of a login body", () => {
|
|
201
|
+
const body: Record<string, unknown> = {
|
|
202
|
+
data: {
|
|
203
|
+
email: "user@example.com",
|
|
204
|
+
password: SENTINEL,
|
|
205
|
+
},
|
|
206
|
+
miscDataProps: { captchaToken: "captcha-" + SENTINEL },
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const output: string = serialize(body);
|
|
210
|
+
|
|
211
|
+
expect(output).not.toContain(SENTINEL);
|
|
212
|
+
expect(output).toContain("user@example.com");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("redacts a TOTP code", () => {
|
|
216
|
+
const body: Record<string, unknown> = {
|
|
217
|
+
data: { code: "123456", twoFactorAuthId: "6543210987654321" },
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const output: string = serialize(body);
|
|
221
|
+
|
|
222
|
+
expect(output).not.toContain("123456");
|
|
223
|
+
expect(output).toContain("6543210987654321");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("redacts WebAuthn assertion material", () => {
|
|
227
|
+
const body: Record<string, unknown> = {
|
|
228
|
+
data: {
|
|
229
|
+
credential: {
|
|
230
|
+
id: "credential-id",
|
|
231
|
+
rawId: SENTINEL,
|
|
232
|
+
response: {
|
|
233
|
+
clientDataJSON: SENTINEL,
|
|
234
|
+
authenticatorData: SENTINEL,
|
|
235
|
+
signature: SENTINEL,
|
|
236
|
+
userHandle: SENTINEL,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
expect(serialize(body)).not.toContain(SENTINEL);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("redactLogString", () => {
|
|
247
|
+
it("redacts a stringified login body - the exact shape of the advisory", () => {
|
|
248
|
+
const message: string =
|
|
249
|
+
"Login request data: " +
|
|
250
|
+
JSON.stringify(
|
|
251
|
+
{ data: { email: "user@example.com", password: SENTINEL } },
|
|
252
|
+
null,
|
|
253
|
+
2,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const output: string = redactLogString(message);
|
|
257
|
+
|
|
258
|
+
expect(output).not.toContain(SENTINEL);
|
|
259
|
+
expect(output).toContain("user@example.com");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("redacts a password whose value contains an escaped quote", () => {
|
|
263
|
+
const message: string = JSON.stringify({
|
|
264
|
+
password: `${SENTINEL}"still-secret`,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(redactLogString(message)).not.toContain(SENTINEL);
|
|
268
|
+
expect(redactLogString(message)).not.toContain("still-secret");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("redacts a stringified WebAuthn assertion", () => {
|
|
272
|
+
const message: string =
|
|
273
|
+
"Login request data: " +
|
|
274
|
+
JSON.stringify({
|
|
275
|
+
data: {
|
|
276
|
+
credential: {
|
|
277
|
+
id: "credential-id",
|
|
278
|
+
rawId: SENTINEL,
|
|
279
|
+
response: {
|
|
280
|
+
clientDataJSON: SENTINEL,
|
|
281
|
+
authenticatorData: SENTINEL,
|
|
282
|
+
signature: SENTINEL,
|
|
283
|
+
userHandle: SENTINEL,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(redactLogString(message)).not.toContain(SENTINEL);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("redacts form-encoded and query-string credentials", () => {
|
|
293
|
+
expect(
|
|
294
|
+
redactLogString(`client_secret=${SENTINEL}&client_id=1234&grant_type=x`),
|
|
295
|
+
).toBe(`client_secret=${REDACTED}&client_id=1234&grant_type=x`);
|
|
296
|
+
|
|
297
|
+
expect(
|
|
298
|
+
redactLogString(
|
|
299
|
+
`https://oneuptime.com/api/slack/auth/p/u?code=${SENTINEL}&state=abc`,
|
|
300
|
+
),
|
|
301
|
+
).toBe(
|
|
302
|
+
`https://oneuptime.com/api/slack/auth/p/u?code=${REDACTED}&state=abc`,
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("redacts the whole token after an authorization scheme, not just the scheme", () => {
|
|
307
|
+
const output: string = redactLogString(
|
|
308
|
+
`Authorization: Bearer ${SENTINEL}-1234567890`,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(output).not.toContain(SENTINEL);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("redacts self-identifying secrets regardless of their key", () => {
|
|
315
|
+
const jwt: string =
|
|
316
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.abcdefghijk";
|
|
317
|
+
|
|
318
|
+
expect(redactLogString(`token is ${jwt}`)).not.toContain(jwt);
|
|
319
|
+
expect(
|
|
320
|
+
redactLogString("bot token xoxb-1111-2222-abcdefghijklmnop"),
|
|
321
|
+
).not.toContain("xoxb-1111-2222-abcdefghijklmnop");
|
|
322
|
+
expect(
|
|
323
|
+
redactLogString(
|
|
324
|
+
"-----BEGIN RSA PRIVATE KEY-----\nMIIabc123\n-----END RSA PRIVATE KEY-----",
|
|
325
|
+
),
|
|
326
|
+
).not.toContain("MIIabc123");
|
|
327
|
+
expect(
|
|
328
|
+
redactLogString("postgres://oneuptime:hunter2@postgres:5432/oneuptimedb"),
|
|
329
|
+
).not.toContain("hunter2");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("leaves ordinary log lines untouched", () => {
|
|
333
|
+
for (const message of [
|
|
334
|
+
"Slack token exchange completed. ok: true",
|
|
335
|
+
"Token exchange completed",
|
|
336
|
+
"Basic authentication failed for this request",
|
|
337
|
+
"Exchanging Slack authorization code for an access token.",
|
|
338
|
+
"Microsoft Teams token exchange completed.",
|
|
339
|
+
"User logged in: user@example.com",
|
|
340
|
+
"Monitor probe finished in 42ms",
|
|
341
|
+
"GET https://oneuptime.com/api/status-page/1234 -> 200",
|
|
342
|
+
]) {
|
|
343
|
+
expect(redactLogString(message)).toBe(message);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe("redactLogValue - safety properties", () => {
|
|
349
|
+
it("does not mutate the caller's object", () => {
|
|
350
|
+
const body: Record<string, unknown> = { password: SENTINEL };
|
|
351
|
+
|
|
352
|
+
redactLogValue(body);
|
|
353
|
+
|
|
354
|
+
expect(body["password"]).toBe(SENTINEL);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("survives circular references", () => {
|
|
358
|
+
const body: Record<string, unknown> = { password: SENTINEL, name: "loop" };
|
|
359
|
+
body["self"] = body;
|
|
360
|
+
|
|
361
|
+
const output: string = serialize(body);
|
|
362
|
+
|
|
363
|
+
expect(output).not.toContain(SENTINEL);
|
|
364
|
+
expect(output).toContain("[Circular]");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("redacts a value repeated in two branches, rather than calling it circular", () => {
|
|
368
|
+
const shared: Record<string, unknown> = { access_token: SENTINEL };
|
|
369
|
+
const output: string = serialize({ first: shared, second: shared });
|
|
370
|
+
|
|
371
|
+
expect(output).not.toContain(SENTINEL);
|
|
372
|
+
expect(output).not.toContain("[Circular]");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("redacts secrets nested in arrays", () => {
|
|
376
|
+
expect(
|
|
377
|
+
serialize({ workspaces: [{ name: "a", authToken: SENTINEL }] }),
|
|
378
|
+
).not.toContain(SENTINEL);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("redacts secrets carried on an Error", () => {
|
|
382
|
+
const error: Error & { client_secret?: string } = new Error(
|
|
383
|
+
`token exchange failed for client_secret=${SENTINEL}`,
|
|
384
|
+
);
|
|
385
|
+
error.client_secret = SENTINEL;
|
|
386
|
+
|
|
387
|
+
expect(serialize(error)).not.toContain(SENTINEL);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("honours toJSON so model logging keeps its shape", () => {
|
|
391
|
+
const model: { toJSON: () => Record<string, unknown> } = {
|
|
392
|
+
toJSON: (): Record<string, unknown> => {
|
|
393
|
+
return { _id: "1234", password: SENTINEL };
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const output: string = serialize(model);
|
|
398
|
+
|
|
399
|
+
expect(output).not.toContain(SENTINEL);
|
|
400
|
+
expect(JSON.parse(output)._id).toBe("1234");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("stops at a depth limit instead of recursing forever", () => {
|
|
404
|
+
let deep: Record<string, unknown> = { password: SENTINEL };
|
|
405
|
+
|
|
406
|
+
for (let i: number = 0; i < 40; i++) {
|
|
407
|
+
deep = { nested: deep };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const output: string = serialize(deep);
|
|
411
|
+
|
|
412
|
+
expect(output).not.toContain(SENTINEL);
|
|
413
|
+
expect(output).toContain("[Truncated]");
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import ConfigLogLevel from "../../../Server/Types/ConfigLogLevel";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
it,
|
|
8
|
+
jest,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* GHSA-3x69-fj58-3pc6 / GHSA-c4c2-r4hm-6pjj.
|
|
13
|
+
*
|
|
14
|
+
* A log record does not go to one place. It goes to process stdout, to the
|
|
15
|
+
* in-memory recent-log ring buffer that the master-admin support bundle reads
|
|
16
|
+
* back, and to the OpenTelemetry log exporter. A credential that survives any
|
|
17
|
+
* one of those three is still leaked, so every test here asserts against all
|
|
18
|
+
* three at once, at every log level -- including the levels that stay on in
|
|
19
|
+
* production.
|
|
20
|
+
*
|
|
21
|
+
* The bodies below are the ones the removed call sites used to pass. Deleting
|
|
22
|
+
* those calls is the fix; this file is the guarantee that re-adding one cannot
|
|
23
|
+
* leak.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface EmittedRecord {
|
|
27
|
+
body: string;
|
|
28
|
+
attributes?: Record<string, unknown> | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const emitted: Array<EmittedRecord> = [];
|
|
32
|
+
|
|
33
|
+
jest.mock("../../../Server/Utils/Telemetry", () => {
|
|
34
|
+
return {
|
|
35
|
+
__esModule: true,
|
|
36
|
+
default: {
|
|
37
|
+
getLogger: (): unknown => {
|
|
38
|
+
return {
|
|
39
|
+
emit: (record: EmittedRecord): void => {
|
|
40
|
+
emitted.push(record);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
import logger from "../../../Server/Utils/Logger";
|
|
49
|
+
|
|
50
|
+
const SENTINEL: string = "sentinel-secret-value-a1b2c3d4";
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* A TOTP code is six digits and has no distinctive shape, so it gets its own
|
|
54
|
+
* sentinel -- the recent-log ring buffer is process-wide and outlives a single
|
|
55
|
+
* test, and a common digit run would collide with the ids other cases log.
|
|
56
|
+
*/
|
|
57
|
+
const TOTP_SENTINEL: string = "907351";
|
|
58
|
+
|
|
59
|
+
type LogMethod = "info" | "error" | "warn" | "debug" | "trace";
|
|
60
|
+
|
|
61
|
+
const ALL_LEVELS: Array<LogMethod> = [
|
|
62
|
+
"info",
|
|
63
|
+
"error",
|
|
64
|
+
"warn",
|
|
65
|
+
"debug",
|
|
66
|
+
"trace",
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Only the recorded calls matter here, so the spies are read through this.
|
|
70
|
+
interface ConsoleSpy {
|
|
71
|
+
mock: { calls: Array<Array<unknown>> };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type ConsoleSpies = Record<LogMethod, ConsoleSpy>;
|
|
75
|
+
|
|
76
|
+
let consoleSpies: ConsoleSpies;
|
|
77
|
+
|
|
78
|
+
type CollectSinkTextFunction = () => string;
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Everything the process wrote for the log calls made so far: what went to
|
|
82
|
+
* console, what the recent-log buffer kept, and what telemetry received.
|
|
83
|
+
*/
|
|
84
|
+
const collectSinkText: CollectSinkTextFunction = (): string => {
|
|
85
|
+
const parts: Array<string> = [];
|
|
86
|
+
|
|
87
|
+
for (const level of ALL_LEVELS) {
|
|
88
|
+
for (const call of consoleSpies[level].mock.calls) {
|
|
89
|
+
parts.push(
|
|
90
|
+
call
|
|
91
|
+
.map((argument: unknown) => {
|
|
92
|
+
if (typeof argument === "string") {
|
|
93
|
+
return argument;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (argument instanceof Error) {
|
|
97
|
+
return `${argument.message} ${argument.stack || ""}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return JSON.stringify(argument);
|
|
102
|
+
} catch {
|
|
103
|
+
return String(argument);
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.join(" "),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const entry of logger.getRecentLogs()) {
|
|
112
|
+
parts.push(entry.message);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const record of emitted) {
|
|
116
|
+
parts.push(record.body);
|
|
117
|
+
parts.push(JSON.stringify(record.attributes || {}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return parts.join("\n");
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type LogAtEveryLevelFunction = (body: unknown, attributes?: unknown) => void;
|
|
124
|
+
|
|
125
|
+
const logAtEveryLevel: LogAtEveryLevelFunction = (
|
|
126
|
+
body: unknown,
|
|
127
|
+
attributes?: unknown,
|
|
128
|
+
): void => {
|
|
129
|
+
for (const level of ALL_LEVELS) {
|
|
130
|
+
(logger[level] as unknown as (body: unknown, attributes?: unknown) => void)(
|
|
131
|
+
body,
|
|
132
|
+
attributes,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
describe("logger never writes credentials to any sink", () => {
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
emitted.length = 0;
|
|
140
|
+
|
|
141
|
+
// DEBUG is the level both advisories are about: on for troubleshooting.
|
|
142
|
+
jest.spyOn(logger, "getLogLevel").mockReturnValue(ConfigLogLevel.DEBUG);
|
|
143
|
+
|
|
144
|
+
consoleSpies = {
|
|
145
|
+
info: jest.spyOn(console, "info").mockImplementation(() => {}),
|
|
146
|
+
error: jest.spyOn(console, "error").mockImplementation(() => {}),
|
|
147
|
+
warn: jest.spyOn(console, "warn").mockImplementation(() => {}),
|
|
148
|
+
debug: jest.spyOn(console, "debug").mockImplementation(() => {}),
|
|
149
|
+
trace: jest.spyOn(console, "trace").mockImplementation(() => {}),
|
|
150
|
+
} as unknown as ConsoleSpies;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
afterEach(() => {
|
|
154
|
+
jest.restoreAllMocks();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("keeps a Slack token exchange out of stdout, recent logs and telemetry", () => {
|
|
158
|
+
logAtEveryLevel({
|
|
159
|
+
code: "1234567890.9876543210." + SENTINEL,
|
|
160
|
+
client_id: "1234567890.1111111111",
|
|
161
|
+
client_secret: SENTINEL,
|
|
162
|
+
redirect_uri: "https://oneuptime.com/api/slack/auth/p/u",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
logAtEveryLevel({
|
|
166
|
+
ok: true,
|
|
167
|
+
access_token: "xoxb-1111-2222-" + SENTINEL,
|
|
168
|
+
authed_user: { id: "U1", access_token: "xoxp-3333-" + SENTINEL },
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(collectSinkText()).not.toContain(SENTINEL);
|
|
172
|
+
// The sinks did receive the records -- this is not passing by logging nothing.
|
|
173
|
+
expect(emitted.length).toBe(ALL_LEVELS.length * 2);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("keeps a Microsoft Teams token response out of every sink", () => {
|
|
177
|
+
logAtEveryLevel({
|
|
178
|
+
token_type: "Bearer",
|
|
179
|
+
expires_in: 3599,
|
|
180
|
+
access_token: "eyJ0eXAiOiJKV1QifQ.ey" + SENTINEL,
|
|
181
|
+
refresh_token: "0.AQ" + SENTINEL,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(collectSinkText()).not.toContain(SENTINEL);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("keeps a stringified login body out of every sink", () => {
|
|
188
|
+
logAtEveryLevel(
|
|
189
|
+
"Login request data: " +
|
|
190
|
+
JSON.stringify(
|
|
191
|
+
{ data: { email: "user@example.com", password: SENTINEL } },
|
|
192
|
+
null,
|
|
193
|
+
2,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const output: string = collectSinkText();
|
|
198
|
+
|
|
199
|
+
expect(output).not.toContain(SENTINEL);
|
|
200
|
+
// Redaction removed the secret, not the log line.
|
|
201
|
+
expect(output).toContain("Login request data");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("keeps MFA material out of every sink", () => {
|
|
205
|
+
logAtEveryLevel({
|
|
206
|
+
data: {
|
|
207
|
+
code: TOTP_SENTINEL,
|
|
208
|
+
credential: { rawId: SENTINEL, response: { signature: SENTINEL } },
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const output: string = collectSinkText();
|
|
213
|
+
|
|
214
|
+
expect(output).not.toContain(SENTINEL);
|
|
215
|
+
expect(output).not.toContain(TOTP_SENTINEL);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("redacts credentials carried in log attributes", () => {
|
|
219
|
+
logAtEveryLevel("some message", {
|
|
220
|
+
projectId: "1234",
|
|
221
|
+
accessToken: SENTINEL,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const output: string = collectSinkText();
|
|
225
|
+
|
|
226
|
+
expect(output).not.toContain(SENTINEL);
|
|
227
|
+
expect(output).toContain("1234");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("redacts a credential inside an Error message and its stack", () => {
|
|
231
|
+
logAtEveryLevel(
|
|
232
|
+
new Error(`Slack token exchange failed: client_secret=${SENTINEL}`),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
expect(collectSinkText()).not.toContain(SENTINEL);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("still logs an ordinary message unchanged", () => {
|
|
239
|
+
logger.debug("Exchanging Slack authorization code for an access token.");
|
|
240
|
+
|
|
241
|
+
expect(logger.getRecentLogs(1)[0]?.message).toBe(
|
|
242
|
+
"Exchanging Slack authorization code for an access token.",
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
});
|