@jwilleke/ngdpbase-types 0.0.0 → 5.18.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.
- package/LICENSE +201 -0
- package/README.md +16 -2
- package/lib/packages/ngdpbase-types/index.d.ts +32 -0
- package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
- package/lib/packages/ngdpbase-types/testing.js +53 -0
- package/lib/src/cache/CacheDependencies.d.ts +50 -0
- package/lib/src/cache/CacheDependencies.js +50 -0
- package/lib/src/cache/ICacheAdapter.d.ts +86 -0
- package/lib/src/cache/RegionCache.d.ts +171 -0
- package/lib/src/context/ActorContext.d.ts +48 -0
- package/lib/src/context/ActorContext.js +33 -0
- package/lib/src/context/ApiContext.d.ts +194 -0
- package/lib/src/context/ApiContext.js +273 -0
- package/lib/src/context/BaseContext.d.ts +110 -0
- package/lib/src/context/BaseContext.js +143 -0
- package/lib/src/context/JobContext.d.ts +153 -0
- package/lib/src/context/JobContext.js +144 -0
- package/lib/src/context/WikiContext.d.ts +404 -0
- package/lib/src/context/bootActions.d.ts +64 -0
- package/lib/src/context/bootActions.js +111 -0
- package/lib/src/converters/HtmlConverter.d.ts +70 -0
- package/lib/src/converters/IContentConverter.d.ts +94 -0
- package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
- package/lib/src/converters/conversionWarning.d.ts +15 -0
- package/lib/src/converters/ncm/bridge.d.ts +22 -0
- package/lib/src/converters/ncm/existing.d.ts +23 -0
- package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
- package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
- package/lib/src/converters/ncm/fix/index.d.ts +36 -0
- package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
- package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
- package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
- package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
- package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
- package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
- package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
- package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
- package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
- package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
- package/lib/src/converters/ncm/fix/types.d.ts +33 -0
- package/lib/src/converters/ncm/footnotes.d.ts +39 -0
- package/lib/src/converters/ncm/images.d.ts +49 -0
- package/lib/src/converters/ncm/index.d.ts +20 -0
- package/lib/src/converters/ncm/links.d.ts +21 -0
- package/lib/src/converters/ncm/normalize.d.ts +39 -0
- package/lib/src/converters/ncm/placeholder.d.ts +25 -0
- package/lib/src/converters/ncm/tables.d.ts +36 -0
- package/lib/src/converters/ncm/types.d.ts +42 -0
- package/lib/src/http/egressPolicy.d.ts +52 -0
- package/lib/src/http/guardedFetch.d.ts +89 -0
- package/lib/src/http/guardedFetch.js +113 -0
- package/lib/src/http/guardedLookup.d.ts +48 -0
- package/lib/src/http/guardedLookup.js +73 -0
- package/lib/src/http/ssrf.d.ts +83 -0
- package/lib/src/http/ssrf.js +311 -0
- package/lib/src/mail/MailProvider.d.ts +36 -0
- package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
- package/lib/src/managers/AddonsManager.d.ts +691 -0
- package/lib/src/managers/AgentTokenManager.d.ts +269 -0
- package/lib/src/managers/AssetManager.d.ts +116 -0
- package/lib/src/managers/AttachmentManager.d.ts +1032 -0
- package/lib/src/managers/AuthManager.d.ts +1257 -0
- package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
- package/lib/src/managers/BaseManager.d.ts +391 -0
- package/lib/src/managers/BaseManager.js +415 -0
- package/lib/src/managers/CacheManager.d.ts +159 -0
- package/lib/src/managers/CatalogManager.d.ts +235 -0
- package/lib/src/managers/CommentManager.d.ts +75 -0
- package/lib/src/managers/ConfigurationManager.d.ts +747 -0
- package/lib/src/managers/DatabaseManager.d.ts +89 -0
- package/lib/src/managers/DatabaseManager.js +236 -0
- package/lib/src/managers/EmailManager.d.ts +47 -0
- package/lib/src/managers/ExportManager.d.ts +129 -0
- package/lib/src/managers/ExportManager.js +420 -0
- package/lib/src/managers/FootnoteManager.d.ts +118 -0
- package/lib/src/managers/MediaManager.d.ts +279 -0
- package/lib/src/managers/MetricsManager.d.ts +89 -0
- package/lib/src/managers/NotificationManager.d.ts +165 -0
- package/lib/src/managers/OidcManager.d.ts +210 -0
- package/lib/src/managers/OrganizationManager.d.ts +107 -0
- package/lib/src/managers/PageManager.d.ts +1645 -0
- package/lib/src/managers/PersonManager.d.ts +48 -0
- package/lib/src/managers/PluginManager.d.ts +179 -0
- package/lib/src/managers/PluginManager.js +393 -0
- package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
- package/lib/src/managers/RenderingManager.d.ts +358 -0
- package/lib/src/managers/RoleManager.d.ts +134 -0
- package/lib/src/managers/SearchManager.d.ts +402 -0
- package/lib/src/managers/ShareManager.d.ts +251 -0
- package/lib/src/managers/TemplateManager.d.ts +176 -0
- package/lib/src/managers/ThemeManager.d.ts +56 -0
- package/lib/src/managers/UserManager.d.ts +480 -0
- package/lib/src/managers/ValidationManager.d.ts +426 -0
- package/lib/src/managers/VariableManager.d.ts +145 -0
- package/lib/src/middleware/csrf.d.ts +3 -0
- package/lib/src/parsers/LinkParser.d.ts +457 -0
- package/lib/src/parsers/MarkupParser.d.ts +769 -0
- package/lib/src/parsers/context/ParseContext.d.ts +314 -0
- package/lib/src/parsers/data/emoji-map.d.ts +18 -0
- package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
- package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
- package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
- package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
- package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
- package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
- package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
- package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
- package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
- package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
- package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
- package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
- package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
- package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
- package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
- package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
- package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
- package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
- package/lib/src/plugins/types.d.ts +79 -0
- package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
- package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
- package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
- package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
- package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
- package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
- package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
- package/lib/src/providers/BaseDatabaseProvider.js +12 -0
- package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
- package/lib/src/providers/BaseLoggingProvider.js +37 -0
- package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
- package/lib/src/providers/BasePageProvider.d.ts +337 -0
- package/lib/src/providers/BaseProvider.d.ts +98 -0
- package/lib/src/providers/BaseProvider.js +74 -0
- package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
- package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
- package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
- package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
- package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
- package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
- package/lib/src/providers/FileLoggingProvider.js +120 -0
- package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
- package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
- package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
- package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
- package/lib/src/providers/NullCacheProvider.d.ts +72 -0
- package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
- package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
- package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
- package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
- package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
- package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
- package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
- package/lib/src/providers/databaseMigrations.d.ts +8 -0
- package/lib/src/providers/databaseMigrations.js +47 -0
- package/lib/src/providers/jobState.d.ts +99 -0
- package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
- package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
- package/lib/src/providers/sqliteLocation.d.ts +17 -0
- package/lib/src/providers/sqliteLocation.js +55 -0
- package/lib/src/providers/sqliteMigrations.d.ts +48 -0
- package/lib/src/providers/sqliteMigrations.js +72 -0
- package/lib/src/rendering/githubAlerts.d.ts +22 -0
- package/lib/src/rendering/htmlPolicy.d.ts +76 -0
- package/lib/src/rendering/markdownConverter.d.ts +73 -0
- package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
- package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
- package/lib/src/security/pageActions.d.ts +40 -0
- package/lib/src/security/permissions.generated.d.ts +78 -0
- package/lib/src/types/Asset.d.ts +414 -0
- package/lib/src/types/Catalog.d.ts +61 -0
- package/lib/src/types/Comment.d.ts +11 -0
- package/lib/src/types/Config.d.ts +388 -0
- package/lib/src/types/Organization.d.ts +78 -0
- package/lib/src/types/OrganizationProvider.d.ts +38 -0
- package/lib/src/types/Page.d.ts +220 -0
- package/lib/src/types/Person.d.ts +56 -0
- package/lib/src/types/PersonProvider.d.ts +25 -0
- package/lib/src/types/Policy.d.ts +139 -0
- package/lib/src/types/Policy.js +17 -0
- package/lib/src/types/Provider.d.ts +810 -0
- package/lib/src/types/Role.d.ts +84 -0
- package/lib/src/types/RoleProvider.d.ts +35 -0
- package/lib/src/types/Schema.d.ts +425 -0
- package/lib/src/types/Share.d.ts +172 -0
- package/lib/src/types/Share.js +81 -0
- package/lib/src/types/User.d.ts +304 -0
- package/lib/src/types/Version.d.ts +189 -0
- package/lib/src/types/WikiEngine.d.ts +128 -0
- package/lib/src/types/express.d.ts +120 -0
- package/lib/src/types/express.js +1 -0
- package/lib/src/types/guards.d.ts +78 -0
- package/lib/src/types/heic-decode.d.ts +16 -0
- package/lib/src/types/index.d.ts +19 -0
- package/lib/src/types/linkedom.d.ts +77 -0
- package/lib/src/types/markdown-it-plugins.d.ts +21 -0
- package/lib/src/utils/DeltaStorage.d.ts +118 -0
- package/lib/src/utils/LocaleUtils.d.ts +105 -0
- package/lib/src/utils/LocaleUtils.js +294 -0
- package/lib/src/utils/PageNameMatcher.d.ts +98 -0
- package/lib/src/utils/PathPreflight.d.ts +38 -0
- package/lib/src/utils/PathPreflight.js +50 -0
- package/lib/src/utils/SectionUtils.d.ts +40 -0
- package/lib/src/utils/addonConfigLayer.d.ts +144 -0
- package/lib/src/utils/addonConfigLayer.js +382 -0
- package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
- package/lib/src/utils/addonDatabaseRules.js +57 -0
- package/lib/src/utils/addonHost.d.ts +51 -0
- package/lib/src/utils/addonHost.js +72 -0
- package/lib/src/utils/addonInstaller.d.ts +109 -0
- package/lib/src/utils/addonLock.d.ts +37 -0
- package/lib/src/utils/addonLock.js +48 -0
- package/lib/src/utils/addonPageSync.d.ts +45 -0
- package/lib/src/utils/addonProvenance.d.ts +48 -0
- package/lib/src/utils/addonTarball.d.ts +41 -0
- package/lib/src/utils/addonsPathResolver.d.ts +76 -0
- package/lib/src/utils/addonsPathResolver.js +129 -0
- package/lib/src/utils/assetMetadataTags.d.ts +66 -0
- package/lib/src/utils/atomicWrite.d.ts +28 -0
- package/lib/src/utils/auditConfigChange.d.ts +57 -0
- package/lib/src/utils/auditEventNames.d.ts +50 -0
- package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
- package/lib/src/utils/auditEventNames.generated.js +168 -0
- package/lib/src/utils/auditEventNames.js +49 -0
- package/lib/src/utils/auditEvents.d.ts +265 -0
- package/lib/src/utils/auditEvents.js +309 -0
- package/lib/src/utils/auditRegistry.d.ts +102 -0
- package/lib/src/utils/auditRegistry.js +168 -0
- package/lib/src/utils/bip39English.d.ts +2 -0
- package/lib/src/utils/configEnvKeys.d.ts +68 -0
- package/lib/src/utils/configFiles.d.ts +74 -0
- package/lib/src/utils/configFiles.js +150 -0
- package/lib/src/utils/delegation.d.ts +48 -0
- package/lib/src/utils/deriveUsername.d.ts +21 -0
- package/lib/src/utils/explainMediaPath.d.ts +123 -0
- package/lib/src/utils/formerTitles.d.ts +69 -0
- package/lib/src/utils/frontmatterAccess.d.ts +54 -0
- package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
- package/lib/src/utils/imageTransform.d.ts +52 -0
- package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
- package/lib/src/utils/jobSlots.d.ts +43 -0
- package/lib/src/utils/keywordNormalizer.d.ts +127 -0
- package/lib/src/utils/logger.d.ts +70 -0
- package/lib/src/utils/logger.js +106 -0
- package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
- package/lib/src/utils/managerUtils.d.ts +40 -0
- package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
- package/lib/src/utils/orgFilename.d.ts +22 -0
- package/lib/src/utils/pageFeature.d.ts +27 -0
- package/lib/src/utils/pageFrontmatter.d.ts +21 -0
- package/lib/src/utils/pageNotes.d.ts +150 -0
- package/lib/src/utils/pageTitleRule.d.ts +37 -0
- package/lib/src/utils/pageToArticle.d.ts +59 -0
- package/lib/src/utils/pageUrl.d.ts +18 -0
- package/lib/src/utils/passwordChange.d.ts +12 -0
- package/lib/src/utils/passwordHash.d.ts +60 -0
- package/lib/src/utils/permissionCatalog.d.ts +52 -0
- package/lib/src/utils/pluginFormatters.d.ts +452 -0
- package/lib/src/utils/pluginFormatters.js +714 -0
- package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
- package/lib/src/utils/privateStoreAccess.d.ts +57 -0
- package/lib/src/utils/privateStoreAccess.js +65 -0
- package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
- package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
- package/lib/src/utils/privateStoreDoor.d.ts +181 -0
- package/lib/src/utils/privateStoreExport.d.ts +72 -0
- package/lib/src/utils/privateStoreFiles.d.ts +120 -0
- package/lib/src/utils/privateStoreMeta.d.ts +64 -0
- package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
- package/lib/src/utils/privateStorePath.d.ts +259 -0
- package/lib/src/utils/privateStorePath.js +436 -0
- package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
- package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
- package/lib/src/utils/proxyClaim.d.ts +59 -0
- package/lib/src/utils/proxyClaim.js +30 -0
- package/lib/src/utils/redactPrivateNames.d.ts +21 -0
- package/lib/src/utils/redactPrivateNames.js +89 -0
- package/lib/src/utils/redactSecrets.d.ts +58 -0
- package/lib/src/utils/redactSecrets.js +171 -0
- package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
- package/lib/src/utils/roleGrants.d.ts +35 -0
- package/lib/src/utils/safeRedirect.d.ts +32 -0
- package/lib/src/utils/schedule.d.ts +54 -0
- package/lib/src/utils/secretFileMode.d.ts +17 -0
- package/lib/src/utils/seededShippedPages.d.ts +45 -0
- package/lib/src/utils/sensitiveValues.d.ts +59 -0
- package/lib/src/utils/sensitiveValues.js +106 -0
- package/lib/src/utils/sessionEnd.d.ts +12 -0
- package/lib/src/utils/sessionGeneration.d.ts +24 -0
- package/lib/src/utils/sessionIdle.d.ts +41 -0
- package/lib/src/utils/sessionLimits.d.ts +68 -0
- package/lib/src/utils/shareLockbox.d.ts +64 -0
- package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
- package/lib/src/utils/sniffMime.d.ts +21 -0
- package/lib/src/utils/storeSearchIndex.d.ts +75 -0
- package/lib/src/utils/subjectMayDo.d.ts +21 -0
- package/lib/src/utils/systemCategoryRules.d.ts +30 -0
- package/lib/src/utils/systemCategoryRules.js +41 -0
- package/lib/src/utils/userCreateError.d.ts +62 -0
- package/lib/src/utils/username.d.ts +53 -0
- package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
- package/lib/src/utils/version.d.ts +84 -0
- package/lib/src/utils/videoFrame.d.ts +21 -0
- 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
|
+
}
|