@jwilleke/ngdpbase-types 0.0.0 → 5.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +16 -2
  3. package/lib/packages/ngdpbase-types/index.d.ts +32 -0
  4. package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
  5. package/lib/packages/ngdpbase-types/testing.js +53 -0
  6. package/lib/src/cache/CacheDependencies.d.ts +50 -0
  7. package/lib/src/cache/CacheDependencies.js +50 -0
  8. package/lib/src/cache/ICacheAdapter.d.ts +86 -0
  9. package/lib/src/cache/RegionCache.d.ts +171 -0
  10. package/lib/src/context/ActorContext.d.ts +48 -0
  11. package/lib/src/context/ActorContext.js +33 -0
  12. package/lib/src/context/ApiContext.d.ts +194 -0
  13. package/lib/src/context/ApiContext.js +273 -0
  14. package/lib/src/context/BaseContext.d.ts +110 -0
  15. package/lib/src/context/BaseContext.js +143 -0
  16. package/lib/src/context/JobContext.d.ts +153 -0
  17. package/lib/src/context/JobContext.js +144 -0
  18. package/lib/src/context/WikiContext.d.ts +404 -0
  19. package/lib/src/context/bootActions.d.ts +64 -0
  20. package/lib/src/context/bootActions.js +111 -0
  21. package/lib/src/converters/HtmlConverter.d.ts +70 -0
  22. package/lib/src/converters/IContentConverter.d.ts +94 -0
  23. package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
  24. package/lib/src/converters/conversionWarning.d.ts +15 -0
  25. package/lib/src/converters/ncm/bridge.d.ts +22 -0
  26. package/lib/src/converters/ncm/existing.d.ts +23 -0
  27. package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
  28. package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
  29. package/lib/src/converters/ncm/fix/index.d.ts +36 -0
  30. package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
  31. package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
  32. package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
  33. package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
  34. package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
  35. package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
  36. package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
  37. package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
  38. package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
  39. package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
  40. package/lib/src/converters/ncm/fix/types.d.ts +33 -0
  41. package/lib/src/converters/ncm/footnotes.d.ts +39 -0
  42. package/lib/src/converters/ncm/images.d.ts +49 -0
  43. package/lib/src/converters/ncm/index.d.ts +20 -0
  44. package/lib/src/converters/ncm/links.d.ts +21 -0
  45. package/lib/src/converters/ncm/normalize.d.ts +39 -0
  46. package/lib/src/converters/ncm/placeholder.d.ts +25 -0
  47. package/lib/src/converters/ncm/tables.d.ts +36 -0
  48. package/lib/src/converters/ncm/types.d.ts +42 -0
  49. package/lib/src/http/egressPolicy.d.ts +52 -0
  50. package/lib/src/http/guardedFetch.d.ts +89 -0
  51. package/lib/src/http/guardedFetch.js +113 -0
  52. package/lib/src/http/guardedLookup.d.ts +48 -0
  53. package/lib/src/http/guardedLookup.js +73 -0
  54. package/lib/src/http/ssrf.d.ts +83 -0
  55. package/lib/src/http/ssrf.js +311 -0
  56. package/lib/src/mail/MailProvider.d.ts +36 -0
  57. package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
  58. package/lib/src/managers/AddonsManager.d.ts +691 -0
  59. package/lib/src/managers/AgentTokenManager.d.ts +269 -0
  60. package/lib/src/managers/AssetManager.d.ts +116 -0
  61. package/lib/src/managers/AttachmentManager.d.ts +1032 -0
  62. package/lib/src/managers/AuthManager.d.ts +1257 -0
  63. package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
  64. package/lib/src/managers/BaseManager.d.ts +427 -0
  65. package/lib/src/managers/BaseManager.js +415 -0
  66. package/lib/src/managers/CacheManager.d.ts +159 -0
  67. package/lib/src/managers/CatalogManager.d.ts +235 -0
  68. package/lib/src/managers/CommentManager.d.ts +75 -0
  69. package/lib/src/managers/ConfigurationManager.d.ts +747 -0
  70. package/lib/src/managers/DatabaseManager.d.ts +188 -0
  71. package/lib/src/managers/DatabaseManager.js +590 -0
  72. package/lib/src/managers/EmailManager.d.ts +47 -0
  73. package/lib/src/managers/ExportManager.d.ts +129 -0
  74. package/lib/src/managers/ExportManager.js +420 -0
  75. package/lib/src/managers/FootnoteManager.d.ts +118 -0
  76. package/lib/src/managers/MediaManager.d.ts +279 -0
  77. package/lib/src/managers/MetricsManager.d.ts +89 -0
  78. package/lib/src/managers/NotificationManager.d.ts +165 -0
  79. package/lib/src/managers/OidcManager.d.ts +210 -0
  80. package/lib/src/managers/OrganizationManager.d.ts +107 -0
  81. package/lib/src/managers/PageManager.d.ts +1645 -0
  82. package/lib/src/managers/PersonManager.d.ts +48 -0
  83. package/lib/src/managers/PluginManager.d.ts +179 -0
  84. package/lib/src/managers/PluginManager.js +393 -0
  85. package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
  86. package/lib/src/managers/RenderingManager.d.ts +358 -0
  87. package/lib/src/managers/RoleManager.d.ts +134 -0
  88. package/lib/src/managers/SearchManager.d.ts +402 -0
  89. package/lib/src/managers/ShareManager.d.ts +251 -0
  90. package/lib/src/managers/TemplateManager.d.ts +176 -0
  91. package/lib/src/managers/ThemeManager.d.ts +56 -0
  92. package/lib/src/managers/UserManager.d.ts +480 -0
  93. package/lib/src/managers/ValidationManager.d.ts +426 -0
  94. package/lib/src/managers/VariableManager.d.ts +145 -0
  95. package/lib/src/middleware/csrf.d.ts +3 -0
  96. package/lib/src/parsers/LinkParser.d.ts +457 -0
  97. package/lib/src/parsers/MarkupParser.d.ts +769 -0
  98. package/lib/src/parsers/context/ParseContext.d.ts +314 -0
  99. package/lib/src/parsers/data/emoji-map.d.ts +18 -0
  100. package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
  101. package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
  102. package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
  103. package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
  104. package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
  105. package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
  106. package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
  107. package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
  108. package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
  109. package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
  110. package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
  111. package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
  112. package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
  113. package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
  114. package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
  115. package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
  116. package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
  117. package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
  118. package/lib/src/plugins/types.d.ts +79 -0
  119. package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
  120. package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
  121. package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
  122. package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
  123. package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
  124. package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
  125. package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
  126. package/lib/src/providers/BaseDatabaseProvider.js +12 -0
  127. package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
  128. package/lib/src/providers/BaseLoggingProvider.js +37 -0
  129. package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
  130. package/lib/src/providers/BasePageProvider.d.ts +337 -0
  131. package/lib/src/providers/BaseProvider.d.ts +98 -0
  132. package/lib/src/providers/BaseProvider.js +74 -0
  133. package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
  134. package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
  135. package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
  136. package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
  137. package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
  138. package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
  139. package/lib/src/providers/FileLoggingProvider.js +120 -0
  140. package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
  141. package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
  142. package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
  143. package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
  144. package/lib/src/providers/NullCacheProvider.d.ts +72 -0
  145. package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
  146. package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
  147. package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
  148. package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
  149. package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
  150. package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
  151. package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
  152. package/lib/src/providers/databaseMigrations.d.ts +8 -0
  153. package/lib/src/providers/databaseMigrations.js +47 -0
  154. package/lib/src/providers/jobState.d.ts +99 -0
  155. package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
  156. package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
  157. package/lib/src/providers/sqliteBackup.d.ts +128 -0
  158. package/lib/src/providers/sqliteBackup.js +365 -0
  159. package/lib/src/providers/sqliteLocation.d.ts +17 -0
  160. package/lib/src/providers/sqliteLocation.js +55 -0
  161. package/lib/src/providers/sqliteMigrations.d.ts +48 -0
  162. package/lib/src/providers/sqliteMigrations.js +72 -0
  163. package/lib/src/rendering/githubAlerts.d.ts +22 -0
  164. package/lib/src/rendering/htmlPolicy.d.ts +76 -0
  165. package/lib/src/rendering/markdownConverter.d.ts +73 -0
  166. package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
  167. package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
  168. package/lib/src/security/pageActions.d.ts +40 -0
  169. package/lib/src/security/permissions.generated.d.ts +78 -0
  170. package/lib/src/types/Asset.d.ts +414 -0
  171. package/lib/src/types/Catalog.d.ts +61 -0
  172. package/lib/src/types/Comment.d.ts +11 -0
  173. package/lib/src/types/Config.d.ts +388 -0
  174. package/lib/src/types/Organization.d.ts +78 -0
  175. package/lib/src/types/OrganizationProvider.d.ts +38 -0
  176. package/lib/src/types/Page.d.ts +220 -0
  177. package/lib/src/types/Person.d.ts +56 -0
  178. package/lib/src/types/PersonProvider.d.ts +25 -0
  179. package/lib/src/types/Policy.d.ts +139 -0
  180. package/lib/src/types/Policy.js +17 -0
  181. package/lib/src/types/Provider.d.ts +810 -0
  182. package/lib/src/types/Role.d.ts +84 -0
  183. package/lib/src/types/RoleProvider.d.ts +35 -0
  184. package/lib/src/types/Schema.d.ts +425 -0
  185. package/lib/src/types/Share.d.ts +172 -0
  186. package/lib/src/types/Share.js +81 -0
  187. package/lib/src/types/User.d.ts +304 -0
  188. package/lib/src/types/Version.d.ts +189 -0
  189. package/lib/src/types/WikiEngine.d.ts +128 -0
  190. package/lib/src/types/express.d.ts +120 -0
  191. package/lib/src/types/express.js +1 -0
  192. package/lib/src/types/guards.d.ts +78 -0
  193. package/lib/src/types/heic-decode.d.ts +16 -0
  194. package/lib/src/types/index.d.ts +19 -0
  195. package/lib/src/types/linkedom.d.ts +77 -0
  196. package/lib/src/types/markdown-it-plugins.d.ts +21 -0
  197. package/lib/src/utils/DeltaStorage.d.ts +118 -0
  198. package/lib/src/utils/LocaleUtils.d.ts +105 -0
  199. package/lib/src/utils/LocaleUtils.js +294 -0
  200. package/lib/src/utils/PageNameMatcher.d.ts +98 -0
  201. package/lib/src/utils/PathPreflight.d.ts +38 -0
  202. package/lib/src/utils/PathPreflight.js +50 -0
  203. package/lib/src/utils/SectionUtils.d.ts +40 -0
  204. package/lib/src/utils/addonConfigLayer.d.ts +144 -0
  205. package/lib/src/utils/addonConfigLayer.js +382 -0
  206. package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
  207. package/lib/src/utils/addonDatabaseRules.js +57 -0
  208. package/lib/src/utils/addonHost.d.ts +51 -0
  209. package/lib/src/utils/addonHost.js +72 -0
  210. package/lib/src/utils/addonInstaller.d.ts +109 -0
  211. package/lib/src/utils/addonLock.d.ts +37 -0
  212. package/lib/src/utils/addonLock.js +48 -0
  213. package/lib/src/utils/addonPageSync.d.ts +45 -0
  214. package/lib/src/utils/addonProvenance.d.ts +48 -0
  215. package/lib/src/utils/addonTarball.d.ts +41 -0
  216. package/lib/src/utils/addonsPathResolver.d.ts +76 -0
  217. package/lib/src/utils/addonsPathResolver.js +129 -0
  218. package/lib/src/utils/assetMetadataTags.d.ts +66 -0
  219. package/lib/src/utils/atomicWrite.d.ts +28 -0
  220. package/lib/src/utils/auditConfigChange.d.ts +57 -0
  221. package/lib/src/utils/auditEventNames.d.ts +50 -0
  222. package/lib/src/utils/auditEventNames.generated.d.ts +169 -0
  223. package/lib/src/utils/auditEventNames.generated.js +174 -0
  224. package/lib/src/utils/auditEventNames.js +49 -0
  225. package/lib/src/utils/auditEvents.d.ts +265 -0
  226. package/lib/src/utils/auditEvents.js +309 -0
  227. package/lib/src/utils/auditRegistry.d.ts +102 -0
  228. package/lib/src/utils/auditRegistry.js +168 -0
  229. package/lib/src/utils/bip39English.d.ts +2 -0
  230. package/lib/src/utils/configEnvKeys.d.ts +68 -0
  231. package/lib/src/utils/configFiles.d.ts +74 -0
  232. package/lib/src/utils/configFiles.js +150 -0
  233. package/lib/src/utils/delegation.d.ts +48 -0
  234. package/lib/src/utils/deriveUsername.d.ts +21 -0
  235. package/lib/src/utils/explainMediaPath.d.ts +123 -0
  236. package/lib/src/utils/formerTitles.d.ts +69 -0
  237. package/lib/src/utils/frontmatterAccess.d.ts +54 -0
  238. package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
  239. package/lib/src/utils/imageTransform.d.ts +52 -0
  240. package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
  241. package/lib/src/utils/jobSlots.d.ts +43 -0
  242. package/lib/src/utils/keywordNormalizer.d.ts +127 -0
  243. package/lib/src/utils/logger.d.ts +70 -0
  244. package/lib/src/utils/logger.js +106 -0
  245. package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
  246. package/lib/src/utils/managerUtils.d.ts +40 -0
  247. package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
  248. package/lib/src/utils/orgFilename.d.ts +22 -0
  249. package/lib/src/utils/pageFeature.d.ts +27 -0
  250. package/lib/src/utils/pageFrontmatter.d.ts +21 -0
  251. package/lib/src/utils/pageNotes.d.ts +150 -0
  252. package/lib/src/utils/pageTitleRule.d.ts +37 -0
  253. package/lib/src/utils/pageToArticle.d.ts +59 -0
  254. package/lib/src/utils/pageUrl.d.ts +18 -0
  255. package/lib/src/utils/passwordChange.d.ts +12 -0
  256. package/lib/src/utils/passwordHash.d.ts +60 -0
  257. package/lib/src/utils/permissionCatalog.d.ts +52 -0
  258. package/lib/src/utils/pluginFormatters.d.ts +452 -0
  259. package/lib/src/utils/pluginFormatters.js +714 -0
  260. package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
  261. package/lib/src/utils/privateStoreAccess.d.ts +57 -0
  262. package/lib/src/utils/privateStoreAccess.js +65 -0
  263. package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
  264. package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
  265. package/lib/src/utils/privateStoreDoor.d.ts +181 -0
  266. package/lib/src/utils/privateStoreExport.d.ts +72 -0
  267. package/lib/src/utils/privateStoreFiles.d.ts +120 -0
  268. package/lib/src/utils/privateStoreMeta.d.ts +64 -0
  269. package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
  270. package/lib/src/utils/privateStorePath.d.ts +259 -0
  271. package/lib/src/utils/privateStorePath.js +436 -0
  272. package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
  273. package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
  274. package/lib/src/utils/proxyClaim.d.ts +59 -0
  275. package/lib/src/utils/proxyClaim.js +30 -0
  276. package/lib/src/utils/redactPrivateNames.d.ts +21 -0
  277. package/lib/src/utils/redactPrivateNames.js +89 -0
  278. package/lib/src/utils/redactSecrets.d.ts +58 -0
  279. package/lib/src/utils/redactSecrets.js +171 -0
  280. package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
  281. package/lib/src/utils/roleGrants.d.ts +35 -0
  282. package/lib/src/utils/safeRedirect.d.ts +32 -0
  283. package/lib/src/utils/schedule.d.ts +54 -0
  284. package/lib/src/utils/secretFileMode.d.ts +17 -0
  285. package/lib/src/utils/seededShippedPages.d.ts +45 -0
  286. package/lib/src/utils/sensitiveValues.d.ts +59 -0
  287. package/lib/src/utils/sensitiveValues.js +106 -0
  288. package/lib/src/utils/sessionEnd.d.ts +12 -0
  289. package/lib/src/utils/sessionGeneration.d.ts +24 -0
  290. package/lib/src/utils/sessionIdle.d.ts +41 -0
  291. package/lib/src/utils/sessionLimits.d.ts +68 -0
  292. package/lib/src/utils/shareLockbox.d.ts +64 -0
  293. package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
  294. package/lib/src/utils/sniffMime.d.ts +21 -0
  295. package/lib/src/utils/storeSearchIndex.d.ts +75 -0
  296. package/lib/src/utils/subjectMayDo.d.ts +21 -0
  297. package/lib/src/utils/systemCategoryRules.d.ts +30 -0
  298. package/lib/src/utils/systemCategoryRules.js +41 -0
  299. package/lib/src/utils/userCreateError.d.ts +62 -0
  300. package/lib/src/utils/username.d.ts +53 -0
  301. package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
  302. package/lib/src/utils/version.d.ts +84 -0
  303. package/lib/src/utils/videoFrame.d.ts +21 -0
  304. package/package.json +54 -3
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Process-level unlock bag for private-store keys (#1384, #1391).
3
+ *
4
+ * Keyed by an opaque random handle — never the session id — created at
5
+ * password login ({@link newPrivateStoreHandle}), stored on the session as
6
+ * `privateStoreHandle`, and carried on the request subject so a caller reaches
7
+ * its keys through the context it was given ({@link dekFor}; security-posture
8
+ * P1). Key bytes never go into express-session JSON, a context, or a record.
9
+ * Logout calls {@link lockPrivateStores}.
10
+ *
11
+ * There is no ambient slot: `AsyncLocalStorage` is refused by P1 because the
12
+ * call site does not show what identity it runs under. Every caller reaches
13
+ * its keys through the context it was handed.
14
+ */
15
+ import { type PrivateStoreLayout } from './privateStorePath.js';
16
+ import { type UserCatalog } from './privateStoreCatalogs.js';
17
+ import type { ActorContext } from '../context/ActorContext.js';
18
+ import type { LinkPublicKey, Lockbox } from './shareLockbox.js';
19
+ export declare function unlockPrivateStores(sessionId: string, username: string, kek: Buffer): void;
20
+ export declare function getSessionUserIndex(sessionId: string): UserCatalog | undefined;
21
+ export declare function lockPrivateStores(sessionId: string): void;
22
+ export declare function getUnlockedKek(sessionId: string): Buffer | undefined;
23
+ export declare function setUnlockedDek(sessionId: string, storeId: string, dek: Buffer): void;
24
+ export declare function getUnlockedDek(sessionId: string, storeId: string): Buffer | undefined;
25
+ /** DEK from the process bag for this request's session (or an explicit id). */
26
+ export declare function sessionDekForStore(storeId: string, handle: string): Buffer | undefined;
27
+ /**
28
+ * After password login: unwrap the user KEK into the bag and load store DEKs.
29
+ * No-ops when the user has no envelope yet. Never writes key bytes to session JSON.
30
+ */
31
+ export declare function unlockPrivateStoresWithPassword(args: {
32
+ /** The session's private-store handle ({@link newPrivateStoreHandle}), not its session id. */
33
+ handle: string;
34
+ username: string;
35
+ password: string;
36
+ pagesDirectory: string;
37
+ }): Promise<void>;
38
+ /**
39
+ * Unlock with the 12 recovery words instead of the password (#1453).
40
+ *
41
+ * The words unwrap the SAME key the password does — the envelope holds it
42
+ * twice, once per wrap — so everything after the unwrap is the password
43
+ * path's, shared. Wrong words fail the authenticated unwrap and return
44
+ * `false`; nothing is unlocked and nothing is written. The words are never
45
+ * logged, stored or returned.
46
+ */
47
+ export declare function unlockPrivateStoresWithMnemonic(args: {
48
+ handle: string;
49
+ username: string;
50
+ words: string;
51
+ pagesDirectory: string;
52
+ }): Promise<boolean>;
53
+ /** Whether `username` has keys at all, i.e. a store that can be encrypted. */
54
+ export declare function hasUserKeys(pagesDirectory: string, username: string): Promise<boolean>;
55
+ /**
56
+ * The public half of `username`'s key pair (#1762 slice 5), or null when they
57
+ * have no keys or no pair yet. Public: anyone may seal to it.
58
+ */
59
+ export declare function personKeyOf(pagesDirectory: string, username: string): Promise<LinkPublicKey | null>;
60
+ /**
61
+ * Seal a copy of this context's OWN unlocked KEK to `recipient` (#1762 slice
62
+ * 5): what a proxy grant made while the owner's store is open carries. Null
63
+ * when the context holds no key — a locked store, a token, a proxy (it holds
64
+ * no KEK of its own through `kekFor`). The KEK copy taken here is zeroed.
65
+ */
66
+ export declare function sealKekCopyFor(ctx: ActorContext, recipient: LinkPublicKey): Promise<Lockbox | null>;
67
+ /**
68
+ * Open the key copy a proxy grant carries (#1762 slice 5) into the actor's
69
+ * own bag (`handle`), when the actor's store is unlocked: their KEK unwraps
70
+ * their private key, which opens the copy of the owner's KEK, which opens the
71
+ * owner's encrypted stores. Replaces any copy the bag held. False — nothing
72
+ * held — when the actor's store is locked, the bag is not the actor's, or the
73
+ * copy does not open (it is then not retried for this bag). No key byte is
74
+ * returned or logged.
75
+ */
76
+ export declare function openKeyCopy(args: {
77
+ handle: string;
78
+ actor: string;
79
+ grantId: string;
80
+ owner: string;
81
+ copy: Lockbox;
82
+ pagesDirectory: string;
83
+ }): Promise<boolean>;
84
+ /** The grant and person whose key copy this handle's bag holds, if any — ids only, no key. */
85
+ export declare function heldKeyCopy(handle: unknown): {
86
+ grantId: string;
87
+ owner: string;
88
+ } | undefined;
89
+ /** Wipe the key copy this handle's bag holds (#1762 slice 5): switching back, or to someone else. The actor's own keys stay. */
90
+ export declare function dropKeyCopy(handle: unknown): void;
91
+ /** Wipe every copy opened under this grant, in every session (#1762 slice 5): the grant was revoked or its account closed. */
92
+ export declare function dropKeyCopiesOfGrant(grantId: string): number;
93
+ /**
94
+ * Password change: re-wrap the user envelope. Recovery wrap is unchanged.
95
+ * No-ops when there is no envelope yet. Not a manager — any caller can use this.
96
+ */
97
+ export declare function rewrapUserKeysOnPasswordChange(args: {
98
+ pagesDirectory: string;
99
+ username: string;
100
+ oldPassword: string;
101
+ newPassword: string;
102
+ }): Promise<void>;
103
+ /**
104
+ * A forgotten password, reset with the recovery words (#1452): the words
105
+ * unwrap the KEK, and the password wrap is replaced with one made from the
106
+ * new password. The recovery wrap and the KEK are untouched.
107
+ *
108
+ * Returns `false` — writing nothing — when the user has no keys or the words
109
+ * do not open them. The words are never logged or stored.
110
+ */
111
+ export declare function resetPasswordWrapWithMnemonic(args: {
112
+ pagesDirectory: string;
113
+ username: string;
114
+ words: string;
115
+ newPassword: string;
116
+ }): Promise<boolean>;
117
+ /** A fresh handle for a session's key bag: random, never the session id. */
118
+ export declare function newPrivateStoreHandle(): string;
119
+ /**
120
+ * The DEK for `owner`'s `store` that this context holds, if any. Only from the
121
+ * owner's own bag — DEKs are keyed by store id, and every user has a
122
+ * `default` — or, for someone acting for the owner, from the key copy their
123
+ * grant carries (#1762 slice 5).
124
+ */
125
+ export declare function dekFor(ctx: ActorContext | undefined, owner: string, store: string): Buffer | undefined;
126
+ /**
127
+ * The ids of the requester's OWN stores whose DEK its session holds (#1400) —
128
+ * the encrypted stores it can open right now. Another user's, or a locked
129
+ * store, is never listed; acting for someone, the represented person's stores
130
+ * the grant's key copy opened (#1762 slice 5). Ids only: no key bytes leave
131
+ * the bag.
132
+ */
133
+ export declare function unlockedStoreIdsFor(ctx: ActorContext): string[];
134
+ /** The unlocked sealed-store page catalog this context's session holds, if any (#1385). */
135
+ export declare function userIndexFor(ctx: ActorContext | undefined): UserCatalog | undefined;
136
+ /**
137
+ * Refuse a write into an encrypted store unless this context holds its DEK
138
+ * (#1394). The context-carrying form of {@link assertCurrentSessionCanWriteStore}.
139
+ */
140
+ export declare function assertContextCanWriteStore(ctx: ActorContext, args: {
141
+ pagesDirectory: string;
142
+ owner: string;
143
+ store: string;
144
+ layout: PrivateStoreLayout;
145
+ }): Promise<void>;
146
+ /**
147
+ * Whether this context's session holds an unlocked user key (#1448).
148
+ *
149
+ * A yes/no, deliberately. {@link kekFor} hands back a COPY of the key bytes,
150
+ * which is right for a caller that is about to use them and wrong for one that
151
+ * only needs to know they exist — every page render asks this, and none of
152
+ * them should be copying key material into a buffer it then has to remember
153
+ * to zero.
154
+ */
155
+ export declare function hasUnlockedKey(ctx: ActorContext | undefined): boolean;
156
+ /** The user KEK this context's session holds, if any. */
157
+ export declare function kekFor(ctx: ActorContext | undefined): Buffer | undefined;
158
+ /**
159
+ * The one door for a session ending (#1626): whatever ends it — logout, idle
160
+ * timeout, a password change, an admin revoke, a new sign-in on the same
161
+ * browser — calls this with the session's private-store handle before the
162
+ * session is gone, so its keys are zeroed and dropped and no recovery words
163
+ * stay pending. Takes the raw session value, so a caller never re-implements
164
+ * the "is there a handle" check.
165
+ */
166
+ export declare function endSessionKeys(handle: unknown): void;
167
+ /**
168
+ * Drop every unlocked bag whose handle no live session holds (#1626): a
169
+ * session that expired in the store, or one destroyed by a path that could
170
+ * not reach endSessionKeys, leaves its keys with no owner. Returns how many
171
+ * bags were dropped.
172
+ */
173
+ export declare function sweepOrphanedKeys(liveHandles: ReadonlySet<string>): number;
174
+ /** Test teardown only. */
175
+ export declare function clearUnlockedPrivateStores(): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The rules about a subject's `viaProxy` that more than one door reads
3
+ * (#1762 slice 2), declared once.
4
+ *
5
+ * The decision itself — the grant still in force, the action in it, the actor
6
+ * holding it — is `PolicyDecisionPoint.ceiling`'s. What lives here is what a
7
+ * door with no decision point to hand needs: the private-container rule
8
+ * (`mayActInPrivateContainer`) and the "does this subject carry a ceiling at
9
+ * all" question the page door and the list filter ask before calling it.
10
+ */
11
+ import type { AgentTokenGrant, ProxyClaim } from '../managers/UserManager.js';
12
+ import type { ShareGrant } from '../types/Share.js';
13
+ /** The delegations a subject may carry, as the rules below read them. */
14
+ interface Delegations {
15
+ viaToken?: AgentTokenGrant;
16
+ viaShare?: ShareGrant;
17
+ viaProxy?: ProxyClaim;
18
+ }
19
+ /**
20
+ * Does this proxy claim carry `permission`? Only a `kind: 'proxy'` claim
21
+ * carries anything: `impersonation` is reserved for #1763 and admits nothing.
22
+ *
23
+ * Read for a permission the grant carries as a marker rather than one policy
24
+ * is asked: `vault-proxy`, which lets the proxy into the represented person's
25
+ * private container (`mayActInPrivateContainer`). Without it a proxy acts on
26
+ * everything else the grant allows and never reaches a vault; an encrypted vault
27
+ * opens only through the key copy the owner sealed to the proxy (slice 5).
28
+ */
29
+ export declare function proxyCarries(claim: ProxyClaim | undefined, permission: string): boolean;
30
+ /**
31
+ * A subject acting for someone AND carrying an agent token or a share link.
32
+ * Never built legitimately (an agent-token request can never act for anyone),
33
+ * so every door refuses it rather than deciding which ceiling wins.
34
+ */
35
+ export declare function mixesProxyWithDelegation(subject: Delegations | null | undefined): boolean;
36
+ /** Does the subject carry any delegation the PDP's ceiling bounds — a token, a share, or a proxy claim? */
37
+ export declare function carriesCeiling(subject: Delegations | null | undefined): boolean;
38
+ /**
39
+ * What a proxy claim adds to an audit record (#1762 slice 3), declared once.
40
+ *
41
+ * Operator decision: a row for something done under a proxy grant names the
42
+ * represented person in `user` — the account acted on, as with agent tokens —
43
+ * and the person who actually did it in a separate top-level `actor`, which
44
+ * the audit search filters on. The grant's id goes in the metadata so the row
45
+ * joins to the `proxy-grant` record that allowed it. A row with no proxy claim
46
+ * gets neither: `actor` is absent, never a copy of `user`.
47
+ *
48
+ * Every builder of an audit row's "who" reads this — `actorOf` for a request
49
+ * subject, `attributedTo` for a job, `PolicyInformationPoint.auditDenial` for a
50
+ * subject that may be anonymous, and the content builders in `auditEvents.ts`.
51
+ */
52
+ export interface ProxyAttribution {
53
+ /** The person who acted; present only under a proxy claim. */
54
+ actor?: string;
55
+ /** `delegated` (the claim's kind) and `proxyGrantId`; empty without a claim. */
56
+ metadata: Record<string, unknown>;
57
+ }
58
+ export declare function proxyAttribution(claim: ProxyClaim | null | undefined): ProxyAttribution;
59
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Does this proxy claim carry `permission`? Only a `kind: 'proxy'` claim
3
+ * carries anything: `impersonation` is reserved for #1763 and admits nothing.
4
+ *
5
+ * Read for a permission the grant carries as a marker rather than one policy
6
+ * is asked: `vault-proxy`, which lets the proxy into the represented person's
7
+ * private container (`mayActInPrivateContainer`). Without it a proxy acts on
8
+ * everything else the grant allows and never reaches a vault; an encrypted vault
9
+ * opens only through the key copy the owner sealed to the proxy (slice 5).
10
+ */
11
+ export function proxyCarries(claim, permission) {
12
+ return claim?.kind === 'proxy' && Array.isArray(claim.scopes) && claim.scopes.includes(permission);
13
+ }
14
+ /**
15
+ * A subject acting for someone AND carrying an agent token or a share link.
16
+ * Never built legitimately (an agent-token request can never act for anyone),
17
+ * so every door refuses it rather than deciding which ceiling wins.
18
+ */
19
+ export function mixesProxyWithDelegation(subject) {
20
+ return Boolean(subject?.viaProxy && (subject.viaToken || subject.viaShare));
21
+ }
22
+ /** Does the subject carry any delegation the PDP's ceiling bounds — a token, a share, or a proxy claim? */
23
+ export function carriesCeiling(subject) {
24
+ return Boolean(subject?.viaToken || subject?.viaShare || subject?.viaProxy);
25
+ }
26
+ export function proxyAttribution(claim) {
27
+ if (!claim)
28
+ return { metadata: {} };
29
+ return { actor: claim.actor, metadata: { delegated: claim.kind, proxyGrantId: claim.grantId } };
30
+ }
@@ -0,0 +1,21 @@
1
+ import type { Logform } from 'winston';
2
+ /** What a struck title is replaced with. */
3
+ export declare const REDACTED_TITLE = "[redacted]";
4
+ /**
5
+ * Strike the title from every private page name in `text`.
6
+ *
7
+ * @param text - a log line, message or audit field
8
+ * @returns the text with each private title replaced by `[redacted]`
9
+ */
10
+ export declare function redactPrivateNames(text: string): string;
11
+ /**
12
+ * The same, over every string in a value — an audit event's fields,
13
+ * metadata and context. Other values are returned as they are.
14
+ */
15
+ export declare function redactPrivateNamesDeep<T>(value: T): T;
16
+ /**
17
+ * Winston format striking private titles from the message, any stack, and
18
+ * the metadata a line carries (`logger.info(msg, { pageName })`). Placed
19
+ * before `printf`, beside redactSecretsFormat, so every transport gets it.
20
+ */
21
+ export declare function redactPrivateNamesFormat(): Logform.Format;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * redactPrivateNames — keep private page titles out of logs and the audit
3
+ * trail (#1461, epic #1454).
4
+ *
5
+ * A private page is named by its path, `vaults/{owner}/{store}/{title}`
6
+ * (#1456; `private/…` before #1506, still matched). Its title belongs to its owner's store; nobody else — admin
7
+ * included — may read it, and `/admin/logs` and the audit view are read by
8
+ * admins. So wherever such a name reaches a log line or an audit event, the
9
+ * title is struck and the owner and store are kept: the line stays
10
+ * diagnosable ("a page in molly's default store") without saying which page.
11
+ *
12
+ * Three shapes are recognised, because a name reaches a line in all three:
13
+ *
14
+ * - as written: `vaults/molly/default/Merger notes`
15
+ * - in a URL path: `/vaults/molly/default/Merger%20notes/edit`
16
+ * - URL-encoded: `vaults%2Fmolly%2Fdefault%2FMerger%20notes`
17
+ *
18
+ * A bare title — `Loaded MergerNotes from …` — cannot be told from any other
19
+ * word, so the code that knows a page is private logs its uuid instead. This
20
+ * catches every line that names the page the way the rest of the system does.
21
+ *
22
+ * Like redactSecrets, this module never imports the logger (bootstrap cycle).
23
+ */
24
+ import { format } from 'winston';
25
+ import { LEGACY_PRIVATE_ROOT, PRIVATE_URL_SEGMENT } from './privateStorePath.js';
26
+ /** What a struck title is replaced with. */
27
+ export const REDACTED_TITLE = '[redacted]';
28
+ /** A page file, index or store folder under a store — a path, not a title. */
29
+ const STORE_FILE = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:\.\w+)?|[\w-]+\.json)$/i;
30
+ const keep = (title) => STORE_FILE.test(title);
31
+ // The first segment of a private page's name: `vaults`, or `private` before #1506.
32
+ const SEGMENT = `(?:${PRIVATE_URL_SEGMENT}|${LEGACY_PRIVATE_ROOT})`;
33
+ // URL-encoded: vaults%2F{owner}%2F{store}%2F{title}
34
+ const ENCODED = new RegExp(`(${PRIVATE_URL_SEGMENT}|${LEGACY_PRIVATE_ROOT})%2F([^%\\s/?#&"']+)%2F([^%\\s/?#&"']+)%2F([^\\s/?#&"']+)`, 'gi');
35
+ // A URL path: /vaults/{owner}/{store}/{title}, the title one path segment.
36
+ const URL_PATH = new RegExp(`(\\/${SEGMENT}\\/[^/\\s?#'"]+\\/[^/\\s?#'"]+\\/)([^/\\s?#'"]+)`, 'g');
37
+ // Any private page name at all — the cheap test before the three rewrites.
38
+ const ANY_NAME = new RegExp(`${SEGMENT}(?:\\/|%2F)`, 'i');
39
+ // As written: the title runs to the end of the line, a quote, or where the
40
+ // next `key=` field, `)` or `]` begins. A title containing one of those loses
41
+ // only what follows it — over- rather than under-redaction is not possible
42
+ // without knowing the title, so the boundary is kept deliberately wide.
43
+ const PLAIN = new RegExp(`(^|[^/\\w%])(${SEGMENT}\\/[^/\\s'"]+\\/[^/\\s'"]+\\/)(.+?)(?=$|\\n|"|'(?=[\\s,.;:)\\]]|$)|\\s[\\w.-]+=|\\)|\\])`, 'gm');
44
+ /**
45
+ * Strike the title from every private page name in `text`.
46
+ *
47
+ * @param text - a log line, message or audit field
48
+ * @returns the text with each private title replaced by `[redacted]`
49
+ */
50
+ export function redactPrivateNames(text) {
51
+ if (typeof text !== 'string' || !ANY_NAME.test(text))
52
+ return text;
53
+ return text
54
+ .replace(ENCODED, (whole, segment, owner, store, title) => (keep(title) ? whole : `${segment}%2F${owner}%2F${store}%2F${REDACTED_TITLE}`))
55
+ .replace(URL_PATH, (whole, prefix, title) => (keep(title) || title === REDACTED_TITLE ? whole : `${prefix}${REDACTED_TITLE}`))
56
+ .replace(PLAIN, (whole, lead, prefix, title) => (keep(title) || title.startsWith('[redacted') ? whole : `${lead}${prefix}${REDACTED_TITLE}`));
57
+ }
58
+ /**
59
+ * The same, over every string in a value — an audit event's fields,
60
+ * metadata and context. Other values are returned as they are.
61
+ */
62
+ export function redactPrivateNamesDeep(value) {
63
+ if (typeof value === 'string')
64
+ return redactPrivateNames(value);
65
+ if (Array.isArray(value))
66
+ return value.map(redactPrivateNamesDeep);
67
+ if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
68
+ const out = {};
69
+ for (const [k, v] of Object.entries(value))
70
+ out[k] = redactPrivateNamesDeep(v);
71
+ return out;
72
+ }
73
+ return value;
74
+ }
75
+ /**
76
+ * Winston format striking private titles from the message, any stack, and
77
+ * the metadata a line carries (`logger.info(msg, { pageName })`). Placed
78
+ * before `printf`, beside redactSecretsFormat, so every transport gets it.
79
+ */
80
+ export function redactPrivateNamesFormat() {
81
+ return format((info) => {
82
+ for (const key of Object.keys(info)) {
83
+ if (key === 'level' || key === 'timestamp')
84
+ continue;
85
+ info[key] = redactPrivateNamesDeep(info[key]);
86
+ }
87
+ return info;
88
+ })();
89
+ }
@@ -0,0 +1,58 @@
1
+ import type { Logform } from 'winston';
2
+ /** Why a listed key contributed no redaction. */
3
+ export interface SkippedSecret {
4
+ key: string;
5
+ reason: 'unset' | 'not-a-string' | 'empty' | 'env-ref' | 'too-short';
6
+ }
7
+ export interface RefreshResult {
8
+ /** How many values are now being redacted. */
9
+ active: number;
10
+ /** Listed keys that contributed nothing, with the reason. */
11
+ skipped: SkippedSecret[];
12
+ }
13
+ /**
14
+ * Minimal shape needed from ConfigurationManager, to avoid importing it — that
15
+ * import is the bootstrap cycle this module exists on the far side of. Matches
16
+ * `ConfigurationManager.getProperty(key, defaultValue?): unknown` exactly; a
17
+ * generic signature here would not accept the real manager.
18
+ */
19
+ export interface SecretConfigReader {
20
+ getProperty(key: string, defaultValue?: unknown): unknown;
21
+ }
22
+ /**
23
+ * Rebuild the redaction table from the current configuration.
24
+ *
25
+ * Call after ConfigurationManager is initialized, and again after any change to
26
+ * the secret keys or their values. Safe to call repeatedly — it replaces the
27
+ * table wholesale rather than accumulating.
28
+ *
29
+ * @param configManager anything exposing `getProperty` (ConfigurationManager)
30
+ * @returns what is now active, and which listed keys were skipped and why
31
+ */
32
+ export declare function refreshRedactedSecrets(configManager: SecretConfigReader | null | undefined): RefreshResult;
33
+ /** Drop all redactions. Intended for tests. */
34
+ export declare function clearRedactedSecrets(): void;
35
+ /** How many values are currently redacted. Intended for tests/diagnostics. */
36
+ export declare function getRedactedSecretCount(): number;
37
+ /**
38
+ * Replace every configured secret in `text` with `[redacted:<key>]`.
39
+ *
40
+ * The key is named so the line stays diagnosable — "the SMTP password appeared
41
+ * here" is useful; an anonymous `***` is not.
42
+ *
43
+ * Uses split/join rather than a RegExp: secrets routinely contain characters
44
+ * that are regex metacharacters, and building a pattern from an untrusted-shape
45
+ * value is how a redactor ends up throwing on the one line that mattered.
46
+ */
47
+ export declare function redactSecrets(text: string): string;
48
+ /**
49
+ * Winston format that redacts secrets from the message and any attached stack.
50
+ *
51
+ * Place it BEFORE `printf` in the pipeline so it applies once, to every
52
+ * transport and every consumer, rather than per output target.
53
+ *
54
+ * Non-string messages are left alone: the printf stage stringifies them, and
55
+ * mutating an object's identity mid-pipeline would surprise a structured/JSON
56
+ * provider. The primary leak shape is an interpolated string.
57
+ */
58
+ export declare function redactSecretsFormat(): Logform.Format;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * redactSecrets — keep configured secrets out of log output (#1030).
3
+ *
4
+ * `ngdpbase.config.sensitive-values` (its `secret` entries) already names the values that must never be
5
+ * shown; it drives masking on `/admin/configuration`. Nothing applied that list
6
+ * to the logs, so a secret reaching a log line was written in the clear — to
7
+ * disk, and to `/admin/logs`, which `admin-read` can open. `app.ts` printed the
8
+ * admin password at startup for exactly this reason (fixed in `2b48d838`). That
9
+ * one line is closed; this closes the class.
10
+ *
11
+ * This is defence in depth, NOT permission to log secrets. It matches literal
12
+ * occurrences only — a value that has been base64-encoded, URL-embedded, hashed
13
+ * or paraphrased still gets through. The rule stays "do not log credentials";
14
+ * this catches the mistakes.
15
+ *
16
+ * ## Why the values are pushed in rather than read
17
+ *
18
+ * The logger bootstraps at module load, long before WikiEngine or
19
+ * ConfigurationManager exist, and is imported by nearly every module —
20
+ * ConfigurationManager included. Reading config from here would create a
21
+ * bootstrap cycle (see the header of BaseLoggingProvider). So the redaction
22
+ * table is a module-level set that starts EMPTY and is filled by
23
+ * {@link refreshRedactedSecrets} once config is resolved, from
24
+ * `WikiEngine.initialize()`.
25
+ *
26
+ * Two consequences worth knowing:
27
+ *
28
+ * - Log lines emitted before that call are not redacted. Nothing has read
29
+ * config at that point, so a config-derived secret cannot be in them.
30
+ * - The format closure reads the live table on every line, which is what
31
+ * makes the deferred fill work at all: `reconfigureLogger()` replaces the
32
+ * logger's transports but NOT its format, so the format object created at
33
+ * module load is the one still running afterwards.
34
+ *
35
+ * For the same cycle reason this module never imports the logger. Callers that
36
+ * want the skipped keys reported get them back from
37
+ * {@link refreshRedactedSecrets} and log them themselves.
38
+ */
39
+ import { format } from 'winston';
40
+ import { secretKeys } from './sensitiveValues.js';
41
+ /**
42
+ * Values shorter than this are never redacted.
43
+ *
44
+ * A short secret would corrupt every line containing it as a substring: were
45
+ * `defaultpassword` set to `admin`, redaction would mangle the word "admin"
46
+ * across unrelated output and make the logs useless — while teaching nobody
47
+ * that the password is weak. Skipping is reported, not silent.
48
+ */
49
+ const MIN_SECRET_LENGTH = 8;
50
+ /**
51
+ * Active redactions, longest value first.
52
+ *
53
+ * Rebuilt only by {@link refreshRedactedSecrets} — never per log line. This is
54
+ * a hot path: every line pays one pass per entry.
55
+ */
56
+ let redactions = [];
57
+ /** Classify a resolved config value, or return null when it is usable. */
58
+ function rejectionReason(value) {
59
+ if (value === undefined || value === null)
60
+ return 'unset';
61
+ if (typeof value !== 'string')
62
+ return 'not-a-string';
63
+ const trimmed = value.trim();
64
+ if (trimmed === '')
65
+ return 'empty';
66
+ // `"$NGDPBASE_SMTP_PASS"` in config is a pointer to an env var, not a secret.
67
+ // The resolved value is what needs redacting; the literal would match the
68
+ // documentation that explains the convention.
69
+ if (trimmed.startsWith('$'))
70
+ return 'env-ref';
71
+ if (trimmed.length < MIN_SECRET_LENGTH)
72
+ return 'too-short';
73
+ return null;
74
+ }
75
+ /**
76
+ * Rebuild the redaction table from the current configuration.
77
+ *
78
+ * Call after ConfigurationManager is initialized, and again after any change to
79
+ * the secret keys or their values. Safe to call repeatedly — it replaces the
80
+ * table wholesale rather than accumulating.
81
+ *
82
+ * @param configManager anything exposing `getProperty` (ConfigurationManager)
83
+ * @returns what is now active, and which listed keys were skipped and why
84
+ */
85
+ export function refreshRedactedSecrets(configManager) {
86
+ const skipped = [];
87
+ if (!configManager || typeof configManager.getProperty !== 'function') {
88
+ redactions = [];
89
+ return { active: 0, skipped };
90
+ }
91
+ const keys = secretKeys((k, d) => configManager.getProperty(k, d));
92
+ const seen = new Set();
93
+ const next = [];
94
+ for (const rawKey of keys) {
95
+ if (typeof rawKey !== 'string')
96
+ continue;
97
+ const key = rawKey.trim();
98
+ if (key === '')
99
+ continue;
100
+ const value = configManager.getProperty(key, undefined);
101
+ const reason = rejectionReason(value);
102
+ if (reason) {
103
+ skipped.push({ key, reason });
104
+ continue;
105
+ }
106
+ const literal = value.trim();
107
+ // Two keys holding the same value redact once; the first key names it.
108
+ if (seen.has(literal))
109
+ continue;
110
+ seen.add(literal);
111
+ next.push({ key, value: literal });
112
+ }
113
+ // Longest first: where one secret contains another, the longer must be
114
+ // struck first or the shorter leaves a mangled remainder of the longer.
115
+ next.sort((a, b) => b.value.length - a.value.length);
116
+ redactions = next;
117
+ return { active: redactions.length, skipped };
118
+ }
119
+ /** Drop all redactions. Intended for tests. */
120
+ export function clearRedactedSecrets() {
121
+ redactions = [];
122
+ }
123
+ /** How many values are currently redacted. Intended for tests/diagnostics. */
124
+ export function getRedactedSecretCount() {
125
+ return redactions.length;
126
+ }
127
+ /**
128
+ * Replace every configured secret in `text` with `[redacted:<key>]`.
129
+ *
130
+ * The key is named so the line stays diagnosable — "the SMTP password appeared
131
+ * here" is useful; an anonymous `***` is not.
132
+ *
133
+ * Uses split/join rather than a RegExp: secrets routinely contain characters
134
+ * that are regex metacharacters, and building a pattern from an untrusted-shape
135
+ * value is how a redactor ends up throwing on the one line that mattered.
136
+ */
137
+ export function redactSecrets(text) {
138
+ if (redactions.length === 0 || typeof text !== 'string' || text === '')
139
+ return text;
140
+ let out = text;
141
+ for (const { key, value } of redactions) {
142
+ if (out.includes(value))
143
+ out = out.split(value).join(`[redacted:${key}]`);
144
+ }
145
+ return out;
146
+ }
147
+ /**
148
+ * Winston format that redacts secrets from the message and any attached stack.
149
+ *
150
+ * Place it BEFORE `printf` in the pipeline so it applies once, to every
151
+ * transport and every consumer, rather than per output target.
152
+ *
153
+ * Non-string messages are left alone: the printf stage stringifies them, and
154
+ * mutating an object's identity mid-pipeline would surprise a structured/JSON
155
+ * provider. The primary leak shape is an interpolated string.
156
+ */
157
+ export function redactSecretsFormat() {
158
+ return format((info) => {
159
+ if (redactions.length === 0)
160
+ return info;
161
+ if (typeof info.message === 'string') {
162
+ info.message = redactSecrets(info.message);
163
+ }
164
+ // Error stacks carry the message, so a credential in a thrown error's text
165
+ // reaches the log through this field rather than `message`.
166
+ if (typeof info.stack === 'string') {
167
+ info.stack = redactSecrets(info.stack);
168
+ }
169
+ return info;
170
+ })();
171
+ }