@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.
Files changed (302) 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 +391 -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 +89 -0
  71. package/lib/src/managers/DatabaseManager.js +236 -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/sqliteLocation.d.ts +17 -0
  158. package/lib/src/providers/sqliteLocation.js +55 -0
  159. package/lib/src/providers/sqliteMigrations.d.ts +48 -0
  160. package/lib/src/providers/sqliteMigrations.js +72 -0
  161. package/lib/src/rendering/githubAlerts.d.ts +22 -0
  162. package/lib/src/rendering/htmlPolicy.d.ts +76 -0
  163. package/lib/src/rendering/markdownConverter.d.ts +73 -0
  164. package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
  165. package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
  166. package/lib/src/security/pageActions.d.ts +40 -0
  167. package/lib/src/security/permissions.generated.d.ts +78 -0
  168. package/lib/src/types/Asset.d.ts +414 -0
  169. package/lib/src/types/Catalog.d.ts +61 -0
  170. package/lib/src/types/Comment.d.ts +11 -0
  171. package/lib/src/types/Config.d.ts +388 -0
  172. package/lib/src/types/Organization.d.ts +78 -0
  173. package/lib/src/types/OrganizationProvider.d.ts +38 -0
  174. package/lib/src/types/Page.d.ts +220 -0
  175. package/lib/src/types/Person.d.ts +56 -0
  176. package/lib/src/types/PersonProvider.d.ts +25 -0
  177. package/lib/src/types/Policy.d.ts +139 -0
  178. package/lib/src/types/Policy.js +17 -0
  179. package/lib/src/types/Provider.d.ts +810 -0
  180. package/lib/src/types/Role.d.ts +84 -0
  181. package/lib/src/types/RoleProvider.d.ts +35 -0
  182. package/lib/src/types/Schema.d.ts +425 -0
  183. package/lib/src/types/Share.d.ts +172 -0
  184. package/lib/src/types/Share.js +81 -0
  185. package/lib/src/types/User.d.ts +304 -0
  186. package/lib/src/types/Version.d.ts +189 -0
  187. package/lib/src/types/WikiEngine.d.ts +128 -0
  188. package/lib/src/types/express.d.ts +120 -0
  189. package/lib/src/types/express.js +1 -0
  190. package/lib/src/types/guards.d.ts +78 -0
  191. package/lib/src/types/heic-decode.d.ts +16 -0
  192. package/lib/src/types/index.d.ts +19 -0
  193. package/lib/src/types/linkedom.d.ts +77 -0
  194. package/lib/src/types/markdown-it-plugins.d.ts +21 -0
  195. package/lib/src/utils/DeltaStorage.d.ts +118 -0
  196. package/lib/src/utils/LocaleUtils.d.ts +105 -0
  197. package/lib/src/utils/LocaleUtils.js +294 -0
  198. package/lib/src/utils/PageNameMatcher.d.ts +98 -0
  199. package/lib/src/utils/PathPreflight.d.ts +38 -0
  200. package/lib/src/utils/PathPreflight.js +50 -0
  201. package/lib/src/utils/SectionUtils.d.ts +40 -0
  202. package/lib/src/utils/addonConfigLayer.d.ts +144 -0
  203. package/lib/src/utils/addonConfigLayer.js +382 -0
  204. package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
  205. package/lib/src/utils/addonDatabaseRules.js +57 -0
  206. package/lib/src/utils/addonHost.d.ts +51 -0
  207. package/lib/src/utils/addonHost.js +72 -0
  208. package/lib/src/utils/addonInstaller.d.ts +109 -0
  209. package/lib/src/utils/addonLock.d.ts +37 -0
  210. package/lib/src/utils/addonLock.js +48 -0
  211. package/lib/src/utils/addonPageSync.d.ts +45 -0
  212. package/lib/src/utils/addonProvenance.d.ts +48 -0
  213. package/lib/src/utils/addonTarball.d.ts +41 -0
  214. package/lib/src/utils/addonsPathResolver.d.ts +76 -0
  215. package/lib/src/utils/addonsPathResolver.js +129 -0
  216. package/lib/src/utils/assetMetadataTags.d.ts +66 -0
  217. package/lib/src/utils/atomicWrite.d.ts +28 -0
  218. package/lib/src/utils/auditConfigChange.d.ts +57 -0
  219. package/lib/src/utils/auditEventNames.d.ts +50 -0
  220. package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
  221. package/lib/src/utils/auditEventNames.generated.js +168 -0
  222. package/lib/src/utils/auditEventNames.js +49 -0
  223. package/lib/src/utils/auditEvents.d.ts +265 -0
  224. package/lib/src/utils/auditEvents.js +309 -0
  225. package/lib/src/utils/auditRegistry.d.ts +102 -0
  226. package/lib/src/utils/auditRegistry.js +168 -0
  227. package/lib/src/utils/bip39English.d.ts +2 -0
  228. package/lib/src/utils/configEnvKeys.d.ts +68 -0
  229. package/lib/src/utils/configFiles.d.ts +74 -0
  230. package/lib/src/utils/configFiles.js +150 -0
  231. package/lib/src/utils/delegation.d.ts +48 -0
  232. package/lib/src/utils/deriveUsername.d.ts +21 -0
  233. package/lib/src/utils/explainMediaPath.d.ts +123 -0
  234. package/lib/src/utils/formerTitles.d.ts +69 -0
  235. package/lib/src/utils/frontmatterAccess.d.ts +54 -0
  236. package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
  237. package/lib/src/utils/imageTransform.d.ts +52 -0
  238. package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
  239. package/lib/src/utils/jobSlots.d.ts +43 -0
  240. package/lib/src/utils/keywordNormalizer.d.ts +127 -0
  241. package/lib/src/utils/logger.d.ts +70 -0
  242. package/lib/src/utils/logger.js +106 -0
  243. package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
  244. package/lib/src/utils/managerUtils.d.ts +40 -0
  245. package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
  246. package/lib/src/utils/orgFilename.d.ts +22 -0
  247. package/lib/src/utils/pageFeature.d.ts +27 -0
  248. package/lib/src/utils/pageFrontmatter.d.ts +21 -0
  249. package/lib/src/utils/pageNotes.d.ts +150 -0
  250. package/lib/src/utils/pageTitleRule.d.ts +37 -0
  251. package/lib/src/utils/pageToArticle.d.ts +59 -0
  252. package/lib/src/utils/pageUrl.d.ts +18 -0
  253. package/lib/src/utils/passwordChange.d.ts +12 -0
  254. package/lib/src/utils/passwordHash.d.ts +60 -0
  255. package/lib/src/utils/permissionCatalog.d.ts +52 -0
  256. package/lib/src/utils/pluginFormatters.d.ts +452 -0
  257. package/lib/src/utils/pluginFormatters.js +714 -0
  258. package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
  259. package/lib/src/utils/privateStoreAccess.d.ts +57 -0
  260. package/lib/src/utils/privateStoreAccess.js +65 -0
  261. package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
  262. package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
  263. package/lib/src/utils/privateStoreDoor.d.ts +181 -0
  264. package/lib/src/utils/privateStoreExport.d.ts +72 -0
  265. package/lib/src/utils/privateStoreFiles.d.ts +120 -0
  266. package/lib/src/utils/privateStoreMeta.d.ts +64 -0
  267. package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
  268. package/lib/src/utils/privateStorePath.d.ts +259 -0
  269. package/lib/src/utils/privateStorePath.js +436 -0
  270. package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
  271. package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
  272. package/lib/src/utils/proxyClaim.d.ts +59 -0
  273. package/lib/src/utils/proxyClaim.js +30 -0
  274. package/lib/src/utils/redactPrivateNames.d.ts +21 -0
  275. package/lib/src/utils/redactPrivateNames.js +89 -0
  276. package/lib/src/utils/redactSecrets.d.ts +58 -0
  277. package/lib/src/utils/redactSecrets.js +171 -0
  278. package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
  279. package/lib/src/utils/roleGrants.d.ts +35 -0
  280. package/lib/src/utils/safeRedirect.d.ts +32 -0
  281. package/lib/src/utils/schedule.d.ts +54 -0
  282. package/lib/src/utils/secretFileMode.d.ts +17 -0
  283. package/lib/src/utils/seededShippedPages.d.ts +45 -0
  284. package/lib/src/utils/sensitiveValues.d.ts +59 -0
  285. package/lib/src/utils/sensitiveValues.js +106 -0
  286. package/lib/src/utils/sessionEnd.d.ts +12 -0
  287. package/lib/src/utils/sessionGeneration.d.ts +24 -0
  288. package/lib/src/utils/sessionIdle.d.ts +41 -0
  289. package/lib/src/utils/sessionLimits.d.ts +68 -0
  290. package/lib/src/utils/shareLockbox.d.ts +64 -0
  291. package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
  292. package/lib/src/utils/sniffMime.d.ts +21 -0
  293. package/lib/src/utils/storeSearchIndex.d.ts +75 -0
  294. package/lib/src/utils/subjectMayDo.d.ts +21 -0
  295. package/lib/src/utils/systemCategoryRules.d.ts +30 -0
  296. package/lib/src/utils/systemCategoryRules.js +41 -0
  297. package/lib/src/utils/userCreateError.d.ts +62 -0
  298. package/lib/src/utils/username.d.ts +53 -0
  299. package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
  300. package/lib/src/utils/version.d.ts +84 -0
  301. package/lib/src/utils/videoFrame.d.ts +21 -0
  302. package/package.json +54 -3
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Bare `%%` style closers → `/%` (#1346).
3
+ *
4
+ * JSPWiki closes a `%%` style block with `/%` or with a bare `%%`. NCM writes
5
+ * only `/%` (operator decision, 2026-09-12); the renderer still tolerates a
6
+ * bare `%%`, so this changes the text, not what the page shows. A `%%` is a
7
+ * closer under Apache's rule (`JSPWikiMarkupParser.handleDiv`): not followed
8
+ * by a letter or `(`, with a block open. That covers a line that is only
9
+ * `%%` and the inline form `%%sub text%%`.
10
+ *
11
+ * Openers are counted to know whether a block is open: `%%` followed by a
12
+ * letter or `(` opens one, `/%` or a bare `%%` closes the innermost. An
13
+ * inline opener (text after it on the line) that is never closed ends with
14
+ * its paragraph, as in Apache, so a blank line drops the inline ones still
15
+ * open; a block-form opener (alone on its line) stays open until closed.
16
+ *
17
+ * Deliberately narrow:
18
+ * - a `%%` with no block open is left alone, for review; Apache drops it;
19
+ * - never a line the renderer treats as code or HTML, and never inside an
20
+ * inline code span.
21
+ *
22
+ * @module converters/ncm/fix/styleClosers
23
+ */
24
+ import type { FixStep } from './types.js';
25
+ export declare const styleClosers: FixStep;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Remove empty lines between list items, so the list renders "tight" (#1271).
3
+ *
4
+ * In CommonMark one empty line anywhere between the items of a list makes the
5
+ * whole list "loose": every item's text is wrapped in a paragraph and the list
6
+ * is spread out. Authors often leave an empty line only to group items in the
7
+ * source — "Antibiotics:" and its sub-items, an empty line, "Antitoxin
8
+ * therapy:" — and get a spaced-out list they did not ask for. An empty line
9
+ * between two items of one list never ends the list, so removing it changes
10
+ * the spacing and nothing else.
11
+ *
12
+ * Works on each top-level list together with the lists nested in it, and
13
+ * deliberately narrow:
14
+ * - only an empty line whose next non-empty line starts an item of the same
15
+ * top-level list;
16
+ * - the list is skipped when any item has a real second block after an
17
+ * empty line (a second paragraph, a code block): there the empty line is
18
+ * meaning, and the list stays loose whatever else is removed;
19
+ * - the list is also skipped when an item's text runs onto a second line.
20
+ * showdown turns that line into a `<br>` only in a loose list and joins it
21
+ * into one line in a tight one, so tightening would lose the break until
22
+ * the converter swap (#1273). markdown-it keeps it either way.
23
+ *
24
+ * The result is parsed again; if the list items are not exactly the ones
25
+ * before, nothing is changed.
26
+ *
27
+ * @module converters/ncm/fix/tightenLists
28
+ */
29
+ import type { FixStep } from './types.js';
30
+ export declare const tightenLists: FixStep;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Types for the Markdown fix module (#1332).
3
+ *
4
+ * @module converters/ncm/fix/types
5
+ */
6
+ /** What one step changed. Empty `lines` means nothing changed. */
7
+ export interface FixStepResult {
8
+ content: string;
9
+ /** 1-based line numbers, in the step's input, that were rewritten or removed. */
10
+ lines: number[];
11
+ }
12
+ /**
13
+ * One conversion step: small, pure and idempotent — running it on its own
14
+ * output changes nothing.
15
+ */
16
+ export interface FixStep {
17
+ /** Stable id, used in reports and to pick steps by name. */
18
+ id: string;
19
+ /** Plain-language summary for the author, e.g. "JSPWiki ** bullets became nested - bullets". */
20
+ summary: string;
21
+ apply(body: string): FixStepResult;
22
+ }
23
+ /** One step's entry in a {@link FixResult}. */
24
+ export interface FixChange {
25
+ step: string;
26
+ summary: string;
27
+ lines: number[];
28
+ }
29
+ export interface FixResult {
30
+ content: string;
31
+ /** Only the steps that changed something, in the order they ran. */
32
+ changes: FixChange[];
33
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * NCM footnote capture (#1125) — the pure half.
3
+ *
4
+ * GFM and NCM share the `[^id]` / `[^id]: text` syntax, and the render
5
+ * pipeline already links refs inline. What the sidecar footnote list
6
+ * (FootnoteManager + FootnotesPlugin, the one with the CRUD UI) needs is the
7
+ * DEFINITIONS as structured records. This module extracts them and rewrites
8
+ * the body; it is deliberately side-effect free so §3.1 determinism holds —
9
+ * the sidecar write belongs to the caller (the convert route), exactly as
10
+ * image localization splits pure rewrite from attachment persistence.
11
+ *
12
+ * Ids are preserved verbatim — the body's `[^note-1]` refs must keep
13
+ * resolving to `#footnote-note-1` in the rendered list, so renumbering would
14
+ * break every ref it touched.
15
+ */
16
+ export interface ExtractedFootnoteDef {
17
+ id: string;
18
+ display: string;
19
+ url: string;
20
+ note: string;
21
+ }
22
+ export interface FootnoteExtraction {
23
+ defs: ExtractedFootnoteDef[];
24
+ /** Body with the definition lines removed. Byte-identical input when there are none. */
25
+ content: string;
26
+ }
27
+ /**
28
+ * Pull every footnote definition (single-line and GFM multi-paragraph) out of
29
+ * the body. Continuation lines — indented by 4+ spaces or a tab, with blank
30
+ * lines allowed between paragraphs of the same definition — join the note
31
+ * with newlines.
32
+ */
33
+ export declare function extractFootnoteDefs(content: string): FootnoteExtraction;
34
+ /**
35
+ * Append a footnote-list section when the page has none, so transferred
36
+ * definitions stay visible. Idempotent: an existing invocation — with or
37
+ * without parameters — is left untouched.
38
+ */
39
+ export declare function ensureFootnotesPlugin(content: string): string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * NCM embedded-image → attachment rule (#728 spec §2.2, Phase 2 Slice 4)
3
+ *
4
+ * `normalizeToNcm` stays synchronous and dependency-free; image localization
5
+ * is inherently async (outbound fetch) and engine-coupled (AttachmentManager,
6
+ * a target page), so it is a **separate post-pass** with **injected I/O**.
7
+ * That keeps S1/S2 pure and this slice unit-testable with fakes; S5 wires the
8
+ * real fetch (`ngdpbase.fetch-timeout-ms`) + `AttachmentManager.uploadAttachment`.
9
+ *
10
+ * For every `![alt](src)` whose `src` is a remote `http(s)` URL or a `data:`
11
+ * URI:
12
+ * - host on the ad/tracker deny-list → drop (`img-rejected: adhost`)
13
+ * - fetch fails / times out → drop (`img-rejected: fetch`)
14
+ * - magic-byte sniff not in the strict raster allowlist (jpeg/png/gif/webp;
15
+ * **SVG/RAW excluded — never trust the extension or `Content-Type`**)
16
+ * → drop (`img-rejected: type`)
17
+ * - decoded bytes exceed the size cap → drop (`img-rejected: size`)
18
+ * - otherwise → store + rewrite to the local ref (`img-attached`)
19
+ * Every drop also leaves a deterministic §3.3 placeholder. Local/relative
20
+ * srcs are left untouched, so the pass is idempotent.
21
+ *
22
+ * @module converters/ncm/images
23
+ */
24
+ import { NcmWarning } from './types.js';
25
+ /** Config for the image rule (size cap reuses ngdpbase.attachment.maxsize). */
26
+ export interface NcmImageConfig {
27
+ maxBytes: number;
28
+ adDenyList: string[];
29
+ fetchTimeoutMs: number;
30
+ }
31
+ /** Injected I/O so the rule is pure-testable; S5 supplies the real impls. */
32
+ export interface NcmImageDeps {
33
+ /** Fetch raw bytes with the given timeout. Reject/throw or return error. */
34
+ fetchBytes(url: string, timeoutMs: number): Promise<Buffer>;
35
+ /** Persist the image; return the local reference to put in `![alt](ref)`. */
36
+ storeAttachment(input: {
37
+ bytes: Buffer;
38
+ mime: string;
39
+ sourceUrl: string;
40
+ }): Promise<string>;
41
+ }
42
+ /**
43
+ * Localize embedded remote/data images in an NCM document.
44
+ * Pure aside from the injected `deps`; idempotent.
45
+ */
46
+ export declare function localizeNcmImages(ncm: string, cfg: NcmImageConfig, deps: NcmImageDeps): Promise<{
47
+ content: string;
48
+ warnings: NcmWarning[];
49
+ }>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ngdp Compatible Markdown (NCM) — module barrel (#728)
3
+ *
4
+ * Spec: docs/NGDP-Compatible-Markdown.md
5
+ * Phase 2: S1 core types + idempotent normalizer + placeholder;
6
+ * S2 HTML/JSPWiki→NCM conversion + §2.4 link normalization.
7
+ *
8
+ * @module converters/ncm
9
+ */
10
+ export * from './types.js';
11
+ export { formatDroppedPlaceholder, isDroppedPlaceholderLine } from './placeholder.js';
12
+ export { normalizeLinks } from './links.js';
13
+ export { upconvertPipeTables, DEFAULT_TABLE_CLASSES } from './tables.js';
14
+ export { localizeNcmImages } from './images.js';
15
+ export type { NcmImageConfig, NcmImageDeps } from './images.js';
16
+ export { normalizeToNcm } from './normalize.js';
17
+ export { ncmToConversionResult } from './bridge.js';
18
+ export { normalizeExistingPageToNcm } from './existing.js';
19
+ export { extractFootnoteDefs, ensureFootnotesPlugin } from './footnotes.js';
20
+ export type { ExtractedFootnoteDef, FootnoteExtraction } from './footnotes.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * NCM link normalization (#728 spec §2.4, Phase 2 Slice 2)
3
+ *
4
+ * The upstream converters (`HtmlConverter` via turndown, `JSPWikiConverter`)
5
+ * emit CommonMark `[text](url)` for external links. NCM does **not** use
6
+ * CommonMark link syntax — it uses the `LinkParserHandler` JSPWiki forms:
7
+ *
8
+ * - external (absolute http/https) → `[text|url|target="_blank"]`
9
+ * (the renderer adds `rel="noopener noreferrer"`)
10
+ * - internal / relative → `[text|target]`
11
+ * - other schemes (mailto:, ftp:) → left as-is (conservative; not NCM-ified)
12
+ *
13
+ * Idempotent by construction: it only matches CommonMark `](…)` which it
14
+ * eliminates, so a second pass is a no-op. Images (`![alt](src)`) are left
15
+ * untouched — image→attachment is the §2.2 rule (#728 S4), not a link concern.
16
+ *
17
+ * @module converters/ncm/links
18
+ */
19
+ import { NcmWarning } from './types.js';
20
+ /** Rewrite CommonMark links in `md` to NCM forms, recording externalizations. */
21
+ export declare function normalizeLinks(md: string, warnings: NcmWarning[]): string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * NCM normalizer — Phase 2 Slice 1 (#728)
3
+ *
4
+ * - `ncm` / `markdown` (S1): parse frontmatter, stamp `ncmVersion` if absent
5
+ * (preserve an existing value — migration is a separate explicit step,
6
+ * never silent rewrite-on-read), re-emit with deterministic (sorted)
7
+ * frontmatter key order; body passed through unchanged. Placeholder lines
8
+ * therefore round-trip byte-identical.
9
+ * - `html` / `jspwiki` (S2): delegate to the existing `HtmlConverter` /
10
+ * `JSPWikiConverter` registry (the spec says NCM *extends* the registry,
11
+ * not reinvents it), map their `string[]` warnings to structured
12
+ * `NcmWarning`, normalize CommonMark links to NCM §2.4 forms, then run the
13
+ * assembled doc through the S1 fixed point so determinism/version/order
14
+ * live in exactly one place.
15
+ *
16
+ * The guarantee S1 establishes (painful to retrofit):
17
+ * `normalize(normalize(x)) === normalize(x)` byte-identical, and an existing
18
+ * `ncmVersion` is never silently changed.
19
+ *
20
+ * @module converters/ncm/normalize
21
+ */
22
+ import { NcmResult, NcmSourceFormat } from './types.js';
23
+ /** Options controlling normalization behaviour. */
24
+ export interface NcmNormalizeOptions {
25
+ /**
26
+ * Style classes to wrap up-converted tables in (§2.1, NCM v2). Defaults to
27
+ * {@link DEFAULT_TABLE_CLASSES}. ImportManager resolves this from
28
+ * `ngdpbase.markdown.ncm.table.default-classes`. Pass `[]` to up-convert the
29
+ * table syntax without a style wrapper.
30
+ */
31
+ tableClasses?: string[];
32
+ }
33
+ /**
34
+ * Normalize input to NCM. See module docs for the Slice-1 scope.
35
+ *
36
+ * @param input Raw source content (may include YAML frontmatter)
37
+ * @param sourceFormat One of {@link NcmSourceFormat}
38
+ */
39
+ export declare function normalizeToNcm(input: string, sourceFormat: NcmSourceFormat, options?: NcmNormalizeOptions): NcmResult;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * NCM dropped-content placeholder (#728 spec §3.3, Phase 2 Slice 1)
3
+ *
4
+ * When the normalizer drops content (stripped HTML, rejected image, …) it
5
+ * leaves a deterministic, NCM-valid blockquote so a reader later sees *that*
6
+ * and *why* something was removed — never a silent gap. This is the durable
7
+ * signal where there is no preview (bulk import, #685, MCP).
8
+ *
9
+ * HARD RULE (idempotence, ties to spec §3.1): the normalizer must recognise
10
+ * its own placeholder and pass it through byte-identical on re-run, so
11
+ * convert-existing-page / #685 re-runs never re-wrap or duplicate it.
12
+ *
13
+ * Single-line blockquote on purpose (not an HTML comment — that would be
14
+ * stripped and invisible; not a plugin — that would add a render path).
15
+ *
16
+ * @module converters/ncm/placeholder
17
+ */
18
+ /**
19
+ * Build the placeholder line for dropped content.
20
+ * `tag`/`reason` are flattened to a single line so the placeholder stays one
21
+ * deterministic line that round-trips.
22
+ */
23
+ export declare function formatDroppedPlaceholder(tag: string, reason: string): string;
24
+ /** True if `line` is an NCM dropped-content placeholder (trailing space tolerant). */
25
+ export declare function isDroppedPlaceholderLine(line: string): boolean;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * NCM table up-conversion — GFM pipe tables → JSPWiki styled tables (#728, NCM v2)
3
+ *
4
+ * NCM v1 left tables as passthrough (both GFM pipe tables and JSPWiki `||`/`|`
5
+ * tables rendered independently). NCM v2 makes the rich JSPWiki form the single
6
+ * canonical table: every GFM pipe table found in a normalized body is rewritten
7
+ * to a `||header||` / `|cell|` table wrapped in the configured style blocks
8
+ * (default `%%table-fit %%table-bordered %%table-striped %%table-hover
9
+ * %%sortable`). This gives imported markdown tables fit/border/zebra/hover and
10
+ * client-side sorting for free, via the existing `JSPWikiPreprocessor`.
11
+ *
12
+ * Two invariants the rest of NCM relies on:
13
+ * - **Idempotent.** The output has no GFM separator row (`|---|`), and detection
14
+ * requires a separator row, so a second pass converts nothing — the body is a
15
+ * fixed point. JSPWiki tables already in the body are likewise never matched.
16
+ * - **Delimiter shape.** `JSPWikiPreprocessor.parseTableRow` slices off the
17
+ * first and last cell (the empties around the outer delimiters), so rows MUST
18
+ * lead AND trail with their delimiter: `||a||b||c||` for headers, `|a|b|c|`
19
+ * for data. Omitting the trailing delimiter silently drops the last column.
20
+ *
21
+ * @module converters/ncm/tables
22
+ */
23
+ /** The five style classes NCM wraps an up-converted table in by default. */
24
+ export declare const DEFAULT_TABLE_CLASSES: string[];
25
+ /**
26
+ * Up-convert every GFM pipe table in a Markdown body to the JSPWiki styled form.
27
+ *
28
+ * @param body Markdown body (no frontmatter)
29
+ * @param classes Style classes to wrap each table in (default {@link DEFAULT_TABLE_CLASSES}).
30
+ * Pass `[]` to convert the table syntax without any style wrapper.
31
+ * @returns The rewritten body and the count of tables converted.
32
+ */
33
+ export declare function upconvertPipeTables(body: string, classes?: string[]): {
34
+ body: string;
35
+ converted: number;
36
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ngdp Compatible Markdown (NCM) — core types (#728, Phase 2 Slice 1)
3
+ *
4
+ * Spec: docs/NGDP-Compatible-Markdown.md
5
+ *
6
+ * These are NCM-local types. The shared `ConversionResult.warnings`
7
+ * (string[]) used by ImportManager/ValidationManager/the converters is NOT
8
+ * changed here — migrating that to structured warnings is a separate, broad
9
+ * slice (#728 S3). NCM functions return their own {@link NcmResult} so this
10
+ * slice has zero blast radius on existing consumers.
11
+ *
12
+ * @module converters/ncm/types
13
+ */
14
+ /** Current NCM profile version. Bumped only when the profile changes; pages
15
+ * are re-normalized to a new version ONLY via an explicit migration, never
16
+ * silently on read (spec §3, Q4).
17
+ *
18
+ * v2 (#728 table profile): GFM pipe tables are up-converted to the JSPWiki
19
+ * styled form (`%%table-*` wrapped `||header||`/`|cell|`) — v1 left tables as
20
+ * passthrough. See docs/NGDP-Compatible-Markdown.md §2.1. */
21
+ export declare const NCM_VERSION = 2;
22
+ /** Source content formats the normalizer accepts. */
23
+ export type NcmSourceFormat = 'ncm' | 'markdown' | 'html' | 'jspwiki';
24
+ /**
25
+ * Stable warning/error code. Free text alone is not aggregatable — the code
26
+ * is the prerequisite for the conversion-metrics work (#738) and tightens
27
+ * determinism (enum > prose). Detail strings carry the specifics.
28
+ */
29
+ export type NcmWarningKind = 'html-dropped' | 'img-attached' | 'img-rejected' | 'link-externalized' | 'placeholder-inserted' | 'converter-note' | 'source-unsupported';
30
+ export interface NcmWarning {
31
+ kind: NcmWarningKind;
32
+ detail: string;
33
+ }
34
+ /** Result of normalizing arbitrary input to NCM. */
35
+ export interface NcmResult {
36
+ /** Full NCM document including frontmatter. */
37
+ content: string;
38
+ /** Structured warnings (see {@link NcmWarningKind}). */
39
+ warnings: NcmWarning[];
40
+ /** The `ncmVersion` stamped on / read from the document. */
41
+ ncmVersion: number;
42
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Configuration to policy (#1133).
3
+ *
4
+ * The operator sets two CIDR lists; this turns them into the `EgressPolicy`
5
+ * the guard enforces, and reconciles them at BOOT so a contradiction is a
6
+ * startup message rather than one dropped image months later — which would
7
+ * surface as a `fetch` warning indistinguishable from the remote host being
8
+ * down.
9
+ *
10
+ * Contradictions resolve by FIREWALL CONVENTION and none of them stops the
11
+ * boot (#1144, D8). #1133 chose longest-prefix match — the routing rule,
12
+ * because the values are routes — so a general overlap is not a conflict at
13
+ * all. The cases longest prefix cannot decide are settled here: an allow entry
14
+ * reaching the mechanism is unsatisfiable and dropped, and a range in both
15
+ * lists is a prefix-length tie the DENY wins.
16
+ *
17
+ * `ngdpbase.security.profile` used to decide whether a contradiction stopped
18
+ * the boot. That was the profile looking for a job: `iptables` rejects a bad
19
+ * rule and keeps the chain, and the Kubernetes API server rejects an invalid
20
+ * NetworkPolicy while the others keep applying. Neither takes the workload
21
+ * down. The one case with no safe silent resolution — a malformed range, where
22
+ * dropping a DENY fails open — is reported separately for #1152.
23
+ */
24
+ import { type EgressPolicy } from './ssrf.js';
25
+ /** The shape of `ConfigurationManager.getProperty`. */
26
+ export type ConfigReader = (key: string, fallback?: unknown) => unknown;
27
+ export declare const DENIED_RANGES_KEY = "ngdpbase.security.egress.denied-ranges";
28
+ export declare const ALLOWED_RANGES_KEY = "ngdpbase.security.egress.allowed-ranges";
29
+ export interface ResolvedEgressPolicy {
30
+ policy: EgressPolicy;
31
+ /** Human-readable contradictions, empty when the configuration is coherent. */
32
+ conflicts: string[];
33
+ /**
34
+ * Ranges that do not parse as CIDR.
35
+ *
36
+ * Separate from `conflicts` because it is the only case without a safe
37
+ * silent resolution: dropping a malformed ALLOW fails closed and is
38
+ * harmless, while dropping a malformed DENY fails open — the operator wrote
39
+ * a restriction, it did not apply, and nothing looks wrong. #1152 turns this
40
+ * into a maintenance boot with a route to the fix.
41
+ */
42
+ malformed: string[];
43
+ }
44
+ /**
45
+ * Read the configured ranges, reconcile them, and report what could not be
46
+ * resolved.
47
+ *
48
+ * A contradiction never widens access. Where the two lists disagree the
49
+ * stricter reading wins, which is the only direction a security control should
50
+ * fail when nobody can be asked.
51
+ */
52
+ export declare function resolveEgressPolicy(read: ConfigReader): ResolvedEgressPolicy;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The outbound client (#1133).
3
+ *
4
+ * Node's built-in `fetch` is not used here, and that is the point rather than
5
+ * an inconvenience. It is undici, which ignores `http.Agent` and so ignores
6
+ * the guarded lookup in ./guardedLookup.ts that is the actual control — a
7
+ * guard installed on an Agent and then bypassed by `fetch()` looks correct in
8
+ * review and does nothing. `fetch` also follows redirects internally, which is
9
+ * exactly the step that has to be inspected rather than delegated.
10
+ *
11
+ * So requests go through node:http / node:https with the guarded agents, and
12
+ * redirects are followed by hand. Every hop is a fresh guarded request: the
13
+ * validated first URL is public and the server answers
14
+ * `302 http://169.254.169.254/…`, which no amount of checking the original URL
15
+ * would catch.
16
+ *
17
+ * Forbidding `fetch` outside this module is also what makes the boundary
18
+ * enforceable rather than merely present — "calling fetch is fine provided a
19
+ * dispatcher was installed first" is not something a CI check can see. That
20
+ * check is #1139.
21
+ *
22
+ * Adapted from `src/http/guarded-fetch.ts` in jwilleke/yourphr, sole-authored
23
+ * by the copyright holder and contributed here under this repository's
24
+ * Apache-2.0 licence.
25
+ */
26
+ import { Agent as HttpAgent } from 'node:http';
27
+ import { Agent as HttpsAgent } from 'node:https';
28
+ import { type EgressPolicy } from './ssrf.js';
29
+ import { type Resolver } from './guardedLookup.js';
30
+ export interface TransportResponse {
31
+ status: number;
32
+ headers: Record<string, string | string[] | undefined>;
33
+ /** Chunks, so the byte cap can stop a large body instead of buffering it. */
34
+ body: AsyncIterable<Buffer>;
35
+ }
36
+ export interface TransportInit {
37
+ method: string;
38
+ headers: Record<string, string>;
39
+ agent: HttpAgent | HttpsAgent;
40
+ timeoutMs: number;
41
+ /** A request body (a POST's form), sent as given. */
42
+ body?: Buffer;
43
+ }
44
+ /**
45
+ * One request, one response, no redirect handling.
46
+ *
47
+ * Injected so the redirect and limit logic can be tested without a socket.
48
+ * Tier 1 in ./ssrf.ts is absolute, so a test cannot open loopback to reach a
49
+ * fixture server — and a production flag that could is precisely the switch
50
+ * that gets left on. The seam is a parameter a caller supplies, never a mode
51
+ * the shipped module can be put into.
52
+ */
53
+ export type Transport = (url: URL, init: TransportInit) => Promise<TransportResponse>;
54
+ export interface GuardedFetchOptions {
55
+ policy: EgressPolicy;
56
+ maxRedirects?: number;
57
+ maxBytes?: number;
58
+ timeoutMs?: number;
59
+ headers?: Record<string, string>;
60
+ method?: 'GET' | 'HEAD' | 'POST';
61
+ /**
62
+ * A POST body (#1743: an OpenID Connect token request). A request that
63
+ * carries one is never redirected: a redirect answer is returned as it is,
64
+ * so a body (a code, a client secret) is never re-sent to a host the caller
65
+ * did not name.
66
+ */
67
+ body?: string | Buffer;
68
+ /** Test seam — see `Transport`. */
69
+ transport?: Transport;
70
+ /** Test seam — see `Resolver`. */
71
+ resolver?: Resolver;
72
+ }
73
+ export interface GuardedResponse {
74
+ status: number;
75
+ headers: Record<string, string | string[] | undefined>;
76
+ body: Buffer;
77
+ /** Where the response actually came from, after redirects. */
78
+ finalUrl: string;
79
+ /** Every URL in the chain. A redirect is a disclosure of where you went. */
80
+ chain: string[];
81
+ }
82
+ /**
83
+ * Fetch `target`, refusing at every hop to connect anywhere the policy does
84
+ * not permit.
85
+ *
86
+ * Each hop is validated as a URL first (a courtesy, and it catches a scheme
87
+ * change) and then at connect time by the guarded lookup, which is the control.
88
+ */
89
+ export declare function guardedFetch(target: string, options: GuardedFetchOptions): Promise<GuardedResponse>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The outbound client (#1133).
3
+ *
4
+ * Node's built-in `fetch` is not used here, and that is the point rather than
5
+ * an inconvenience. It is undici, which ignores `http.Agent` and so ignores
6
+ * the guarded lookup in ./guardedLookup.ts that is the actual control — a
7
+ * guard installed on an Agent and then bypassed by `fetch()` looks correct in
8
+ * review and does nothing. `fetch` also follows redirects internally, which is
9
+ * exactly the step that has to be inspected rather than delegated.
10
+ *
11
+ * So requests go through node:http / node:https with the guarded agents, and
12
+ * redirects are followed by hand. Every hop is a fresh guarded request: the
13
+ * validated first URL is public and the server answers
14
+ * `302 http://169.254.169.254/…`, which no amount of checking the original URL
15
+ * would catch.
16
+ *
17
+ * Forbidding `fetch` outside this module is also what makes the boundary
18
+ * enforceable rather than merely present — "calling fetch is fine provided a
19
+ * dispatcher was installed first" is not something a CI check can see. That
20
+ * check is #1139.
21
+ *
22
+ * Adapted from `src/http/guarded-fetch.ts` in jwilleke/yourphr, sole-authored
23
+ * by the copyright holder and contributed here under this repository's
24
+ * Apache-2.0 licence.
25
+ */
26
+ import { Agent as HttpAgent, request as httpRequest } from 'node:http';
27
+ import { Agent as HttpsAgent, request as httpsRequest } from 'node:https';
28
+ import { validateUrl } from './ssrf.js';
29
+ import { guardedLookup } from './guardedLookup.js';
30
+ const DEFAULTS = { maxRedirects: 5, maxBytes: 8 * 1024 * 1024, timeoutMs: 30_000 };
31
+ /** Statuses that carry a `Location` worth following. */
32
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
33
+ /** The real transport: node:http / node:https, whose agents carry the guarded lookup. */
34
+ const nodeTransport = (url, init) => new Promise((resolve, reject) => {
35
+ const send = url.protocol === 'https:' ? httpsRequest : httpRequest;
36
+ const req = send(url, { method: init.method, headers: init.headers, agent: init.agent, timeout: init.timeoutMs }, (res) => {
37
+ resolve({
38
+ status: res.statusCode ?? 0,
39
+ headers: res.headers,
40
+ body: res
41
+ });
42
+ });
43
+ req.on('timeout', () => req.destroy(new Error(`timed out after ${init.timeoutMs}ms`)));
44
+ req.on('error', reject);
45
+ req.end(init.body);
46
+ });
47
+ /** Read chunks up to `maxBytes`, refusing — and stopping — rather than buffering past it. */
48
+ async function readCapped(body, maxBytes) {
49
+ const parts = [];
50
+ let total = 0;
51
+ for await (const chunk of body) {
52
+ total += chunk.length;
53
+ if (total > maxBytes) {
54
+ throw new Error(`response body too large: exceeded maxBytes ${maxBytes}`);
55
+ }
56
+ parts.push(chunk);
57
+ }
58
+ return Buffer.concat(parts);
59
+ }
60
+ /** The first header value, since node presents repeated headers as an array. */
61
+ function headerValue(value) {
62
+ return Array.isArray(value) ? value[0] : value;
63
+ }
64
+ /**
65
+ * Fetch `target`, refusing at every hop to connect anywhere the policy does
66
+ * not permit.
67
+ *
68
+ * Each hop is validated as a URL first (a courtesy, and it catches a scheme
69
+ * change) and then at connect time by the guarded lookup, which is the control.
70
+ */
71
+ export async function guardedFetch(target, options) {
72
+ const maxRedirects = options.maxRedirects ?? DEFAULTS.maxRedirects;
73
+ const maxBytes = options.maxBytes ?? DEFAULTS.maxBytes;
74
+ const timeoutMs = options.timeoutMs ?? DEFAULTS.timeoutMs;
75
+ const transport = options.transport ?? nodeTransport;
76
+ const body = options.body === undefined ? undefined : Buffer.from(options.body);
77
+ // keepAlive: false is load-bearing. A pooled connection skips resolution on
78
+ // reuse, so the guard would run for the first request to a host and never
79
+ // again — every later request riding a connection validated once, long ago.
80
+ const agentOptions = { lookup: guardedLookup(options.policy, options.resolver), keepAlive: false };
81
+ const agents = { http: new HttpAgent(agentOptions), https: new HttpsAgent(agentOptions) };
82
+ const chain = [];
83
+ let current = target;
84
+ for (let hop = 0; hop <= maxRedirects; hop++) {
85
+ const checked = validateUrl(current, options.policy);
86
+ if (!checked.ok) {
87
+ throw new Error(checked.reason);
88
+ }
89
+ const url = checked.url;
90
+ chain.push(url.toString());
91
+ const res = await transport(url, {
92
+ method: options.method ?? 'GET',
93
+ headers: body ? { ...options.headers, 'Content-Length': String(body.length) } : (options.headers ?? {}),
94
+ agent: url.protocol === 'https:' ? agents.https : agents.http,
95
+ timeoutMs,
96
+ ...(body ? { body } : {})
97
+ });
98
+ const location = headerValue(res.headers['location']);
99
+ if (!REDIRECT_STATUSES.has(res.status) || !location || body) {
100
+ return {
101
+ status: res.status,
102
+ headers: res.headers,
103
+ body: await readCapped(res.body, maxBytes),
104
+ finalUrl: url.toString(),
105
+ chain
106
+ };
107
+ }
108
+ // Relative Locations are legal and common; resolve against the hop that
109
+ // issued them, then validate the result like any other target.
110
+ current = new URL(location, url).toString();
111
+ }
112
+ throw new Error(`too many redirects: stopped after ${maxRedirects} (${chain.join(' -> ')})`);
113
+ }