@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.
- 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/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/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/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/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/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/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/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/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/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/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,576 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Centralized, deny-by-default redaction for everything that reaches a log
|
|
3
|
+
* sink.
|
|
4
|
+
*
|
|
5
|
+
* Every log record this process emits is written to three places: process
|
|
6
|
+
* stdout/stderr, the in-memory recent-log ring buffer that the master-admin
|
|
7
|
+
* support bundle surfaces, and the configured OpenTelemetry log exporter. A
|
|
8
|
+
* credential that reaches a log statement therefore lands in three retention
|
|
9
|
+
* systems at once, and DEBUG is a normal troubleshooting level operators turn
|
|
10
|
+
* on in production.
|
|
11
|
+
*
|
|
12
|
+
* So call sites are not trusted to be careful. Logger runs every message body
|
|
13
|
+
* and every attribute through here first, at every level, and the rules below
|
|
14
|
+
* are the single place that decides what a secret looks like. Removing a
|
|
15
|
+
* careless `logger.debug(requestBody)` is still the right fix at the call
|
|
16
|
+
* site -- this is the net underneath it.
|
|
17
|
+
*
|
|
18
|
+
* Two matching strategies, both applied:
|
|
19
|
+
*
|
|
20
|
+
* 1. Structural. Objects are walked recursively and a value is replaced
|
|
21
|
+
* whenever its KEY names a credential (`client_secret`, `access_token`,
|
|
22
|
+
* `password`, `Authorization`, ...). This is the reliable one: it does
|
|
23
|
+
* not care what the value looks like.
|
|
24
|
+
*
|
|
25
|
+
* 2. Textual. Strings are swept for credentials that were already flattened
|
|
26
|
+
* into text -- `JSON.stringify(req.body)` in a template literal, an OAuth
|
|
27
|
+
* callback URL with `?code=`, an `Authorization: Bearer` header dump -- and
|
|
28
|
+
* for values that are self-identifying regardless of key (JWTs, Slack
|
|
29
|
+
* `xoxb-` tokens, PEM private key blocks).
|
|
30
|
+
*
|
|
31
|
+
* Over-redaction is the intended failure mode. Losing a token count from a
|
|
32
|
+
* debug line costs a little context; leaking a bot token costs the workspace.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export const REDACTED: string = "[REDACTED]";
|
|
36
|
+
|
|
37
|
+
// Deep enough for any real log payload, shallow enough to bound the walk.
|
|
38
|
+
const MAX_DEPTH: number = 12;
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* A key is sensitive when its normalized form (lowercased, separators
|
|
42
|
+
* stripped, so `client_secret`, `clientSecret` and `CLIENT-SECRET` all collapse
|
|
43
|
+
* to `clientsecret`) CONTAINS one of these fragments. Substring matching is
|
|
44
|
+
* deliberate: it catches `slackBotAccessToken`, `x-api-key`,
|
|
45
|
+
* `refresh_token_expires_in` and every other spelling nobody thought to list.
|
|
46
|
+
*/
|
|
47
|
+
const SENSITIVE_KEY_FRAGMENTS: Array<string> = [
|
|
48
|
+
"password",
|
|
49
|
+
"passwd",
|
|
50
|
+
"passphrase",
|
|
51
|
+
"secret",
|
|
52
|
+
"token",
|
|
53
|
+
"credential",
|
|
54
|
+
"authorization",
|
|
55
|
+
"cookie",
|
|
56
|
+
"apikey",
|
|
57
|
+
"privatekey",
|
|
58
|
+
"accesskey",
|
|
59
|
+
"secretkey",
|
|
60
|
+
"signingkey",
|
|
61
|
+
"encryptionkey",
|
|
62
|
+
"assertion",
|
|
63
|
+
"attestation",
|
|
64
|
+
"signature",
|
|
65
|
+
"salt",
|
|
66
|
+
"otp",
|
|
67
|
+
"jwt",
|
|
68
|
+
"bearer",
|
|
69
|
+
"sessionid",
|
|
70
|
+
"authcode",
|
|
71
|
+
"authorizationcode",
|
|
72
|
+
"verificationcode",
|
|
73
|
+
"backupcode",
|
|
74
|
+
"recoverycode",
|
|
75
|
+
"onetimecode",
|
|
76
|
+
"logincode",
|
|
77
|
+
"resetcode",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Keys that contain a sensitive fragment but never carry the secret itself.
|
|
82
|
+
* These are worth keeping: LLM token accounting and expiry timestamps are
|
|
83
|
+
* exactly the sort of thing someone turns DEBUG on to look at.
|
|
84
|
+
*/
|
|
85
|
+
const NON_SENSITIVE_KEYS: Set<string> = new Set<string>([
|
|
86
|
+
"tokencount",
|
|
87
|
+
"tokencounts",
|
|
88
|
+
"tokensused",
|
|
89
|
+
"totaltokens",
|
|
90
|
+
"inputtokens",
|
|
91
|
+
"outputtokens",
|
|
92
|
+
"prompttokens",
|
|
93
|
+
"completiontokens",
|
|
94
|
+
"cachedtokens",
|
|
95
|
+
"reasoningtokens",
|
|
96
|
+
"maxtokens",
|
|
97
|
+
"tokenlimit",
|
|
98
|
+
"tokenusage",
|
|
99
|
+
"tokentype",
|
|
100
|
+
"hastoken",
|
|
101
|
+
"hasaccesstoken",
|
|
102
|
+
"hasrefreshtoken",
|
|
103
|
+
"tokenexpiresat",
|
|
104
|
+
"tokenexpiry",
|
|
105
|
+
"accesstokenexpiresat",
|
|
106
|
+
"refreshtokenexpiresat",
|
|
107
|
+
"expirestokenat",
|
|
108
|
+
"isauthorized",
|
|
109
|
+
"authorizationurl",
|
|
110
|
+
"authorizationendpoint",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* Keys whose sensitivity depends on the value. `code` is both the OAuth
|
|
115
|
+
* authorization code / TOTP code we must never log AND the `ECONNREFUSED` on
|
|
116
|
+
* every network error, so it is decided by looksLikeSecretCode below.
|
|
117
|
+
*/
|
|
118
|
+
const VALUE_DEPENDENT_KEYS: Set<string> = new Set<string>(["code", "codes"]);
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
* Keys that are always secret but carry no fragment above -- WebAuthn
|
|
122
|
+
* assertion material, mostly.
|
|
123
|
+
*/
|
|
124
|
+
const SENSITIVE_EXACT_KEYS: Set<string> = new Set<string>([
|
|
125
|
+
"pin",
|
|
126
|
+
"totp",
|
|
127
|
+
"mfa",
|
|
128
|
+
"rawid",
|
|
129
|
+
"clientdatajson",
|
|
130
|
+
"authenticatordata",
|
|
131
|
+
"userhandle",
|
|
132
|
+
"challenge",
|
|
133
|
+
"twofactorsecret",
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
export type NormalizeLogKeyFunction = (key: string) => string;
|
|
137
|
+
|
|
138
|
+
export const normalizeLogKey: NormalizeLogKeyFunction = (
|
|
139
|
+
key: string,
|
|
140
|
+
): string => {
|
|
141
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* Error codes (`ECONNREFUSED`, `ERR_BAD_REQUEST`), exception codes
|
|
146
|
+
* (`BadDataException`) and short status-ish strings are identifiers, never
|
|
147
|
+
* credentials. Anything else under a `code` key -- digits (a TOTP), or a long
|
|
148
|
+
* opaque string with separators (an OAuth authorization code) -- is treated as
|
|
149
|
+
* a secret.
|
|
150
|
+
*/
|
|
151
|
+
const IDENTIFIER_CODE_REGEX: RegExp = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
152
|
+
|
|
153
|
+
type LooksLikeSecretCodeFunction = (value: unknown) => boolean;
|
|
154
|
+
|
|
155
|
+
const looksLikeSecretCode: LooksLikeSecretCodeFunction = (
|
|
156
|
+
value: unknown,
|
|
157
|
+
): boolean => {
|
|
158
|
+
if (typeof value === "number") {
|
|
159
|
+
// HTTP status / exit codes are small; a 6-digit TOTP is not.
|
|
160
|
+
return Math.abs(value) >= 1000;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (typeof value !== "string") {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (value.length === 0) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return !(IDENTIFIER_CODE_REGEX.test(value) && value.length < 32);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export type IsSensitiveLogKeyFunction = (
|
|
175
|
+
key: string,
|
|
176
|
+
value?: unknown,
|
|
177
|
+
) => boolean;
|
|
178
|
+
|
|
179
|
+
export const isSensitiveLogKey: IsSensitiveLogKeyFunction = (
|
|
180
|
+
key: string,
|
|
181
|
+
value?: unknown,
|
|
182
|
+
): boolean => {
|
|
183
|
+
const normalized: string = normalizeLogKey(key);
|
|
184
|
+
|
|
185
|
+
if (!normalized) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (NON_SENSITIVE_KEYS.has(normalized)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (SENSITIVE_EXACT_KEYS.has(normalized)) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (VALUE_DEPENDENT_KEYS.has(normalized)) {
|
|
198
|
+
return looksLikeSecretCode(value);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return SENSITIVE_KEY_FRAGMENTS.some((fragment: string) => {
|
|
202
|
+
return normalized.includes(fragment);
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/*
|
|
207
|
+
* ---------------------------------------------------------------------------
|
|
208
|
+
* String rules
|
|
209
|
+
* ---------------------------------------------------------------------------
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/*
|
|
213
|
+
* Credential words as they appear INSIDE text. Kept separate from the key
|
|
214
|
+
* fragment list because a couple of the structural fragments (`salt`, `otp`)
|
|
215
|
+
* are too short to match safely against free-form prose.
|
|
216
|
+
*/
|
|
217
|
+
const TEXT_SECRET_WORDS: string =
|
|
218
|
+
"password|passwd|passphrase|secret|token|credential|credentials|authorization|cookie|api[_-]?key|private[_-]?key|access[_-]?key|assertion|attestation|signature|raw[_-]?id|client[_-]?data[_-]?json|authenticator[_-]?data|user[_-]?handle|two[_-]?factor[_-]?secret";
|
|
219
|
+
|
|
220
|
+
// Bounded, so a long non-matching run cannot blow up on backtracking.
|
|
221
|
+
const KEY_PREFIX: string = "[A-Za-z0-9_.-]{0,40}";
|
|
222
|
+
const KEY_SUFFIX: string = "[A-Za-z0-9_.-]{0,40}";
|
|
223
|
+
|
|
224
|
+
interface StringRedactionRule {
|
|
225
|
+
name: string;
|
|
226
|
+
regex: RegExp;
|
|
227
|
+
replacement: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const STRING_REDACTION_RULES: Array<StringRedactionRule> = [
|
|
231
|
+
{
|
|
232
|
+
/*
|
|
233
|
+
* Runs first: a bare `Bearer <token>` with no key in front of it would
|
|
234
|
+
* otherwise survive every rule below.
|
|
235
|
+
*
|
|
236
|
+
* The lookahead requires the value to carry a digit or a separator, so the
|
|
237
|
+
* scheme words keep working as English -- "Basic authentication failed" and
|
|
238
|
+
* "Token exchange completed" are log lines, not credentials.
|
|
239
|
+
*/
|
|
240
|
+
name: "authorization-scheme",
|
|
241
|
+
regex:
|
|
242
|
+
/\b(Bearer|Basic|Token)\s+(?=[A-Za-z0-9._~+/=-]*[0-9._~+/=-])[A-Za-z0-9._~+/=-]{8,}/gi,
|
|
243
|
+
replacement: `$1 ${REDACTED}`,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
/*
|
|
247
|
+
* JSON string members: {"client_secret":"abc"} -- the shape produced by
|
|
248
|
+
* JSON.stringify(req.body) inside a log message. The value pattern allows
|
|
249
|
+
* escaped quotes so an escaped-quote-bearing password cannot end the match
|
|
250
|
+
* early and leave its tail in the clear.
|
|
251
|
+
*/
|
|
252
|
+
name: "json-string-member",
|
|
253
|
+
regex: new RegExp(
|
|
254
|
+
`"(${KEY_PREFIX}(?:${TEXT_SECRET_WORDS})${KEY_SUFFIX})"(\\s*:\\s*)"(?:[^"\\\\]|\\\\.)*"`,
|
|
255
|
+
"gi",
|
|
256
|
+
),
|
|
257
|
+
replacement: `"$1"$2"${REDACTED}"`,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
// JSON numeric members: {"password":123456}
|
|
261
|
+
name: "json-number-member",
|
|
262
|
+
regex: new RegExp(
|
|
263
|
+
`"(${KEY_PREFIX}(?:${TEXT_SECRET_WORDS})${KEY_SUFFIX})"(\\s*:\\s*)-?\\d+(?:\\.\\d+)?`,
|
|
264
|
+
"gi",
|
|
265
|
+
),
|
|
266
|
+
replacement: `"$1"$2"${REDACTED}"`,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
/*
|
|
270
|
+
* Unquoted key/value text: form bodies (`client_secret=abc&...`), query
|
|
271
|
+
* strings, header dumps (`Authorization: Bearer abc`) and prose
|
|
272
|
+
* (`password: hunter2`).
|
|
273
|
+
*
|
|
274
|
+
* The value alternation tries `Bearer <token>` first, so a header dump is
|
|
275
|
+
* consumed whole -- stopping at the scheme word would leave the token
|
|
276
|
+
* itself sitting in the clear right after the placeholder.
|
|
277
|
+
*/
|
|
278
|
+
name: "key-value-text",
|
|
279
|
+
regex: new RegExp(
|
|
280
|
+
`\\b(${KEY_PREFIX}(?:${TEXT_SECRET_WORDS})${KEY_SUFFIX})(\\s*[=:]\\s*)(?:"[^"]*"|'[^']*'|(?:Bearer|Basic|Token)\\s+[^\\s,;&)\\]}"']+|[^\\s,;&)\\]}"']+)`,
|
|
281
|
+
"gi",
|
|
282
|
+
),
|
|
283
|
+
replacement: `$1$2${REDACTED}`,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
/*
|
|
287
|
+
* OAuth authorization codes and OTPs as query/form parameters. Restricted
|
|
288
|
+
* to the `code=` form so ordinary prose ("error code: ECONNREFUSED") keeps
|
|
289
|
+
* its meaning -- structural redaction covers the JSON case.
|
|
290
|
+
*/
|
|
291
|
+
name: "code-parameter",
|
|
292
|
+
regex: /\b(code|pin|otp|totp)(=)[^\s&#"'<>]+/gi,
|
|
293
|
+
replacement: `$1$2${REDACTED}`,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
// {"code":"..."} in flattened JSON.
|
|
297
|
+
name: "json-code-member",
|
|
298
|
+
regex: /"(code|pin|otp|totp)"(\s*:\s*)"(?:[^"\\]|\\.)*"/gi,
|
|
299
|
+
replacement: `"$1"$2"${REDACTED}"`,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "jwt",
|
|
303
|
+
regex: /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}/g,
|
|
304
|
+
replacement: REDACTED,
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "slack-token",
|
|
308
|
+
regex: /\bxox[abeprs]-[A-Za-z0-9-]{8,}/gi,
|
|
309
|
+
replacement: REDACTED,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "slack-app-token",
|
|
313
|
+
regex: /\bxapp-[A-Za-z0-9-]{8,}/gi,
|
|
314
|
+
replacement: REDACTED,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "github-token",
|
|
318
|
+
regex:
|
|
319
|
+
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
320
|
+
replacement: REDACTED,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "aws-access-key-id",
|
|
324
|
+
regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA)[0-9A-Z]{16}\b/g,
|
|
325
|
+
replacement: REDACTED,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "google-api-key",
|
|
329
|
+
regex: /\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
330
|
+
replacement: REDACTED,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: "private-key-block",
|
|
334
|
+
regex:
|
|
335
|
+
/-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY-----/g,
|
|
336
|
+
replacement: REDACTED,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
// Credentials embedded in a URL: postgres://user:password@host/db
|
|
340
|
+
name: "url-basic-auth",
|
|
341
|
+
regex: /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/:@]+):[^\s/@]+@/gi,
|
|
342
|
+
replacement: `$1$2:${REDACTED}@`,
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
* Cheap pre-filter. Almost every log line contains none of these, and skipping
|
|
348
|
+
* fifteen regexes for those keeps redaction off the cost of ordinary logging.
|
|
349
|
+
* It must stay a superset of every rule above -- a word missing here is a
|
|
350
|
+
* credential that never gets redacted.
|
|
351
|
+
*/
|
|
352
|
+
const SENSITIVE_TEXT_HINT_REGEX: RegExp = new RegExp(
|
|
353
|
+
[
|
|
354
|
+
TEXT_SECRET_WORDS,
|
|
355
|
+
"code=",
|
|
356
|
+
"pin=",
|
|
357
|
+
"otp=",
|
|
358
|
+
"totp=",
|
|
359
|
+
'"code"',
|
|
360
|
+
'"pin"',
|
|
361
|
+
'"otp"',
|
|
362
|
+
'"totp"',
|
|
363
|
+
"bearer\\s",
|
|
364
|
+
"basic\\s",
|
|
365
|
+
"eyJ",
|
|
366
|
+
"xox",
|
|
367
|
+
"xapp-",
|
|
368
|
+
"gh[opusr]_",
|
|
369
|
+
"github_pat_",
|
|
370
|
+
"AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA",
|
|
371
|
+
"AIza",
|
|
372
|
+
"-----BEGIN",
|
|
373
|
+
"://",
|
|
374
|
+
].join("|"),
|
|
375
|
+
"i",
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
export type RedactLogStringFunction = (value: string) => string;
|
|
379
|
+
|
|
380
|
+
export const redactLogString: RedactLogStringFunction = (
|
|
381
|
+
value: string,
|
|
382
|
+
): string => {
|
|
383
|
+
if (!value) {
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!SENSITIVE_TEXT_HINT_REGEX.test(value)) {
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let redacted: string = value;
|
|
392
|
+
|
|
393
|
+
for (const rule of STRING_REDACTION_RULES) {
|
|
394
|
+
/*
|
|
395
|
+
* The rules carry the /g flag and are module-level, so lastIndex has to be
|
|
396
|
+
* reset -- String.replace does that itself, but .test() on the hint regex
|
|
397
|
+
* above and any future .test() here would not.
|
|
398
|
+
*/
|
|
399
|
+
rule.regex.lastIndex = 0;
|
|
400
|
+
redacted = redacted.replace(rule.regex, rule.replacement);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return redacted;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/*
|
|
407
|
+
* ---------------------------------------------------------------------------
|
|
408
|
+
* Structural walk
|
|
409
|
+
* ---------------------------------------------------------------------------
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
type RedactValueFunction = (
|
|
413
|
+
value: unknown,
|
|
414
|
+
depth: number,
|
|
415
|
+
seen: Set<unknown>,
|
|
416
|
+
) => unknown;
|
|
417
|
+
|
|
418
|
+
const redactValue: RedactValueFunction = (
|
|
419
|
+
value: unknown,
|
|
420
|
+
depth: number,
|
|
421
|
+
seen: Set<unknown>,
|
|
422
|
+
): unknown => {
|
|
423
|
+
if (value === null || value === undefined) {
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (typeof value === "string") {
|
|
428
|
+
return redactLogString(value);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (
|
|
432
|
+
typeof value === "number" ||
|
|
433
|
+
typeof value === "boolean" ||
|
|
434
|
+
typeof value === "bigint"
|
|
435
|
+
) {
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (typeof value === "function") {
|
|
440
|
+
return "[Function]";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (typeof value === "symbol") {
|
|
444
|
+
return value.toString();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (value instanceof Date) {
|
|
448
|
+
return value;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
452
|
+
// Buffers hold key material as often as they hold anything readable.
|
|
453
|
+
return `[Buffer ${value.length} bytes]`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (depth >= MAX_DEPTH) {
|
|
457
|
+
return "[Truncated]";
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (seen.has(value)) {
|
|
461
|
+
return "[Circular]";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
seen.add(value);
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
if (value instanceof Error) {
|
|
468
|
+
const redactedError: Record<string, unknown> = {
|
|
469
|
+
name: value.name,
|
|
470
|
+
message: redactLogString(value.message),
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
if (value.stack) {
|
|
474
|
+
redactedError["stack"] = redactLogString(value.stack);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
for (const key of Object.keys(value)) {
|
|
478
|
+
redactedError[key] = isSensitiveLogKey(
|
|
479
|
+
key,
|
|
480
|
+
(value as unknown as Record<string, unknown>)[key],
|
|
481
|
+
)
|
|
482
|
+
? REDACTED
|
|
483
|
+
: redactValue(
|
|
484
|
+
(value as unknown as Record<string, unknown>)[key],
|
|
485
|
+
depth + 1,
|
|
486
|
+
seen,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return redactedError;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (Array.isArray(value)) {
|
|
494
|
+
return value.map((item: unknown) => {
|
|
495
|
+
return redactValue(item, depth + 1, seen);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (value instanceof Map) {
|
|
500
|
+
const fromMap: Record<string, unknown> = {};
|
|
501
|
+
|
|
502
|
+
for (const [mapKey, mapValue] of value.entries()) {
|
|
503
|
+
const keyAsString: string = String(mapKey);
|
|
504
|
+
|
|
505
|
+
fromMap[keyAsString] = isSensitiveLogKey(keyAsString, mapValue)
|
|
506
|
+
? REDACTED
|
|
507
|
+
: redactValue(mapValue, depth + 1, seen);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return fromMap;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (value instanceof Set) {
|
|
514
|
+
return Array.from(value).map((item: unknown) => {
|
|
515
|
+
return redactValue(item, depth + 1, seen);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/*
|
|
520
|
+
* Models, ObjectIDs and the other Common types serialize through toJSON.
|
|
521
|
+
* Honouring it keeps redacted output shaped exactly like what
|
|
522
|
+
* JSON.stringify would have produced without this pass.
|
|
523
|
+
*/
|
|
524
|
+
const maybeToJSON: unknown = (value as Record<string, unknown>)["toJSON"];
|
|
525
|
+
|
|
526
|
+
if (typeof maybeToJSON === "function") {
|
|
527
|
+
try {
|
|
528
|
+
const json: unknown = (maybeToJSON as () => unknown).call(value);
|
|
529
|
+
|
|
530
|
+
if (json !== value) {
|
|
531
|
+
return redactValue(json, depth, seen);
|
|
532
|
+
}
|
|
533
|
+
} catch {
|
|
534
|
+
// Fall through to the plain-object walk below.
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const redactedObject: Record<string, unknown> = {};
|
|
539
|
+
|
|
540
|
+
for (const key of Object.keys(value as Record<string, unknown>)) {
|
|
541
|
+
const propertyValue: unknown = (value as Record<string, unknown>)[key];
|
|
542
|
+
|
|
543
|
+
redactedObject[key] = isSensitiveLogKey(key, propertyValue)
|
|
544
|
+
? REDACTED
|
|
545
|
+
: redactValue(propertyValue, depth + 1, seen);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return redactedObject;
|
|
549
|
+
} finally {
|
|
550
|
+
/*
|
|
551
|
+
* Only the path currently being walked is "seen", so a value referenced
|
|
552
|
+
* twice in a tree is redacted twice instead of being reported as circular.
|
|
553
|
+
*/
|
|
554
|
+
seen.delete(value);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Returns a redacted deep copy of any log body. The input is never mutated:
|
|
560
|
+
* logging must not change what the caller is holding.
|
|
561
|
+
*/
|
|
562
|
+
export type RedactLogValueFunction = (value: unknown) => unknown;
|
|
563
|
+
|
|
564
|
+
export const redactLogValue: RedactLogValueFunction = (
|
|
565
|
+
value: unknown,
|
|
566
|
+
): unknown => {
|
|
567
|
+
try {
|
|
568
|
+
return redactValue(value, 0, new Set<unknown>());
|
|
569
|
+
} catch {
|
|
570
|
+
/*
|
|
571
|
+
* A getter that throws, an exotic proxy... nothing here is worth crashing
|
|
572
|
+
* a log call for, but the body cannot be passed through unredacted either.
|
|
573
|
+
*/
|
|
574
|
+
return REDACTED;
|
|
575
|
+
}
|
|
576
|
+
};
|