@preventive/triage 1.0.0-alpha.5 → 1.0.0-alpha.50

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 (366) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/README.md +16 -0
  3. package/api/reap.ts +17 -0
  4. package/cli.js +6 -0
  5. package/client/dependency-paths.d.ts +3 -0
  6. package/client/dependency-paths.js +36 -0
  7. package/client/finding-link.js +305 -0
  8. package/client/linked-findings.d.ts +1 -0
  9. package/client/linked-findings.js +115 -0
  10. package/common/bundle-commit.d.ts +1 -0
  11. package/common/bundle-commit.js +4 -0
  12. package/common/bundle-compare-diff.d.ts +3 -0
  13. package/common/bundle-compare-diff.js +357 -0
  14. package/common/bundle-compare-inputs.d.ts +2 -0
  15. package/common/bundle-compare-inputs.js +65 -0
  16. package/common/bundle-compare-versions.js +172 -0
  17. package/common/bundle-comparison.d.ts +8 -0
  18. package/common/bundle-comparison.js +62 -0
  19. package/common/bundle-metadata.d.ts +15 -0
  20. package/common/bundle-metadata.js +226 -0
  21. package/common/bundle-pkg-of.d.ts +1 -0
  22. package/common/bundle-pkg-of.js +90 -0
  23. package/common/bundle-reasons.d.ts +2 -0
  24. package/common/bundle-reasons.js +21 -0
  25. package/common/bundle-source-links.d.ts +6 -0
  26. package/common/bundle-source-links.js +216 -0
  27. package/common/bundle-sourcemap.d.ts +10 -0
  28. package/common/bundle-sourcemap.js +100 -0
  29. package/common/bundle-sources.d.ts +7 -0
  30. package/common/bundle-sources.js +333 -0
  31. package/common/bundle-stats.js +41 -0
  32. package/common/bundle-tabs.js +1 -0
  33. package/common/code-language.js +65 -0
  34. package/common/default-scan-models.ts +31 -0
  35. package/common/dependency-reviews.d.ts +18 -0
  36. package/common/dependency-reviews.js +64 -0
  37. package/common/finding-duplicates.js +58 -0
  38. package/common/finding-filters.js +274 -0
  39. package/common/finding-id.js +47 -0
  40. package/common/github-issue-labels.d.ts +1 -0
  41. package/common/github-issue-labels.js +11 -0
  42. package/common/github-pr.ts +56 -0
  43. package/common/gzip.js +59 -0
  44. package/common/line-diff.d.ts +8 -0
  45. package/common/line-diff.js +57 -0
  46. package/common/linked-workspace-groups.js +63 -0
  47. package/common/managed/agent-tasks.ts +292 -0
  48. package/common/managed/comments.ts +34 -0
  49. package/common/managed/finding-detail.d.ts +2 -0
  50. package/common/managed/finding-detail.js +32 -0
  51. package/common/managed/github-agents.ts +114 -0
  52. package/common/managed/github-logins.ts +49 -0
  53. package/common/managed/npm-packages.d.ts +5 -0
  54. package/common/managed/npm-packages.js +34 -0
  55. package/common/managed/permissions.ts +31 -0
  56. package/common/managed/report-content.ts +72 -0
  57. package/common/managed/report-filter.ts +109 -0
  58. package/common/managed/repository-alias.ts +65 -0
  59. package/common/managed/roles.ts +35 -0
  60. package/common/managed/routes.d.ts +2 -0
  61. package/common/managed/routes.js +347 -0
  62. package/common/managed/scan-models.ts +6 -0
  63. package/common/managed/team-app.d.ts +5 -0
  64. package/common/managed/team-app.js +31 -0
  65. package/common/managed/triage.ts +83 -0
  66. package/common/managed/upload-seal.ts +126 -0
  67. package/common/pretty-print.d.ts +5 -0
  68. package/common/pretty-print.js +24 -0
  69. package/common/revalidation-conflicts.js +48 -0
  70. package/common/revalidation-input-groups.js +56 -0
  71. package/common/save-error-reason.ts +20 -7
  72. package/common/scan-server.ts +13 -0
  73. package/common/security-links.d.ts +5 -0
  74. package/common/security-links.js +33 -0
  75. package/common/server-info.ts +46 -0
  76. package/common/utf8.d.ts +3 -0
  77. package/common/utf8.js +45 -0
  78. package/common/workspace-app.js +39 -0
  79. package/common/workspace-groups.js +176 -0
  80. package/out/activity.js +4 -0
  81. package/out/brotli-fallback.js +3 -3
  82. package/out/chunk-2WDZPXVL.js +3 -0
  83. package/out/chunk-2XUEZHIP.js +28 -0
  84. package/out/chunk-2ZDZWLFY.js +1 -0
  85. package/out/chunk-4HI2MSAV.js +17 -0
  86. package/out/chunk-4XMP7YVH.js +36 -0
  87. package/out/chunk-6LNQTKAV.js +52 -0
  88. package/out/chunk-6S2U2UYG.js +232 -0
  89. package/out/chunk-75YLHBCM.js +3 -0
  90. package/out/chunk-7BKKYVWR.js +7 -0
  91. package/out/chunk-7C7DWL7I.js +1 -0
  92. package/out/chunk-7VAWINQF.js +6 -0
  93. package/out/chunk-C4Z6ZBXV.js +10 -0
  94. package/out/chunk-FC65TPUB.js +1 -0
  95. package/out/chunk-FKBXNUW4.js +1 -0
  96. package/out/chunk-GDPCLSOS.js +2 -0
  97. package/out/chunk-GO7FT4OD.js +1 -0
  98. package/out/chunk-HTBYHSPG.js +1 -0
  99. package/out/chunk-IYPY47MC.js +1 -0
  100. package/out/chunk-IZ7POWI3.js +18 -0
  101. package/out/chunk-JCIIBLKR.js +1 -0
  102. package/out/chunk-NRPC74AM.js +1 -0
  103. package/out/chunk-OOJM4CTU.js +1 -0
  104. package/out/chunk-PBZPYIG7.js +1 -0
  105. package/out/chunk-PNHBZGDD.js +1 -0
  106. package/out/chunk-PWFUOLZK.js +17 -0
  107. package/out/chunk-PXK7EU4V.js +1 -0
  108. package/out/chunk-QB4CBOX2.js +1 -0
  109. package/out/chunk-R3NZFXVR.js +1 -0
  110. package/out/chunk-R5O6FAW4.js +2 -0
  111. package/out/chunk-SXV2T5C4.js +80 -0
  112. package/out/chunk-TQJDYHVE.js +2 -0
  113. package/out/chunk-TUII34K2.js +1 -0
  114. package/out/chunk-UK7RGJT6.js +3 -0
  115. package/out/chunk-VFWMQ3LG.js +2 -0
  116. package/out/chunk-VH4XMZIW.js +2 -0
  117. package/out/chunk-WCPJDAYA.js +10 -0
  118. package/out/chunk-WFYLMF6Z.js +10 -0
  119. package/out/chunk-WYLQWYLA.js +1 -0
  120. package/out/client-managed-import.js +4 -0
  121. package/out/client-managed.js +33 -0
  122. package/out/client-sync.js +11 -17
  123. package/out/compare.js +35 -0
  124. package/out/dialogs.js +55 -0
  125. package/out/github-issue-authorized.html +3 -0
  126. package/out/github-issue-authorized.js +1 -0
  127. package/out/graph.js +17 -4
  128. package/out/index.html +90 -8
  129. package/out/prism.js +14 -10
  130. package/out/scan.js +2 -0
  131. package/out/stasis.svg +20 -0
  132. package/out/terminal.js +366 -39
  133. package/out/view.css +1 -1
  134. package/out/view.js +19 -144
  135. package/package.json +362 -59
  136. package/risk/actions/findings.js +161 -0
  137. package/risk/actions/resolve.js +219 -0
  138. package/risk/actions/scripts/commands.js +182 -0
  139. package/risk/actions/scripts/grade.js +113 -0
  140. package/risk/actions/scripts/packages.js +123 -0
  141. package/risk/actions/scripts/shell.js +169 -0
  142. package/risk/actions/uses.js +76 -0
  143. package/risk/actions/workflow.js +110 -0
  144. package/risk/files/characters.js +196 -0
  145. package/risk/files/hosts.js +205 -0
  146. package/risk/files/readability.js +177 -0
  147. package/risk/files/runs-code.js +615 -0
  148. package/risk/files/script.js +118 -0
  149. package/risk/files/stamps.js +47 -0
  150. package/risk/index.d.ts +254 -0
  151. package/risk/index.js +11 -0
  152. package/risk/npm/assess.js +285 -0
  153. package/risk/npm/combined.js +68 -0
  154. package/risk/npm/compare.js +257 -0
  155. package/risk/npm/dependencies.js +172 -0
  156. package/risk/npm/diff.js +79 -0
  157. package/risk/npm/downloads.js +84 -0
  158. package/risk/npm/files.js +445 -0
  159. package/risk/npm/held.js +63 -0
  160. package/risk/npm/install-scripts.js +34 -0
  161. package/risk/npm/malware.js +36 -0
  162. package/risk/npm/manifest.js +65 -0
  163. package/risk/npm/new-dependency.js +60 -0
  164. package/risk/npm/old-version.js +108 -0
  165. package/risk/npm/registry.js +388 -0
  166. package/risk/npm/release.js +85 -0
  167. package/risk/npm/score.js +59 -0
  168. package/risk/npm/specs.js +124 -0
  169. package/risk/npm/tarball.js +96 -0
  170. package/server-common/database-config.ts +16 -0
  171. package/server-common/initialize.ts +18 -0
  172. package/server-common/npm-advisories.ts +101 -0
  173. package/{server → server-common}/origin.ts +5 -5
  174. package/server-common/reap.ts +48 -0
  175. package/server-common/scan-config.ts +19 -0
  176. package/server-common/standalone.ts +29 -0
  177. package/server-common/storage-crypto.ts +182 -0
  178. package/server-common/storage-log.ts +38 -0
  179. package/server-common/vercel-blob.ts +103 -0
  180. package/server-e2e/app.ts +487 -0
  181. package/{server → server-e2e}/bus-receiver.ts +2 -1
  182. package/{server → server-e2e}/cli.js +9 -4
  183. package/{server → server-e2e}/config.ts +50 -42
  184. package/{server → server-e2e}/db-neon.ts +2 -2
  185. package/{server → server-e2e}/db-revision-sql.ts +1 -1
  186. package/{server → server-e2e}/db-stmt.ts +2 -2
  187. package/{server → server-e2e}/db.ts +7 -7
  188. package/{server → server-e2e}/http.ts +44 -13
  189. package/{server → server-e2e}/hub.ts +40 -9
  190. package/server-e2e/index.ts +17 -0
  191. package/server-e2e/lifecycle.ts +95 -0
  192. package/{server → server-e2e}/neon-driver.ts +2 -2
  193. package/{server → server-e2e}/npm-proxy.ts +12 -145
  194. package/{server → server-e2e}/objstore/blob-vercel.ts +10 -114
  195. package/{server → server-e2e}/objstore/blob.ts +1 -1
  196. package/server-e2e/objstore/fetch-mint-guard.ts +74 -0
  197. package/{server → server-e2e}/objstore/handlers.ts +9 -8
  198. package/{server → server-e2e}/objstore/init.ts +19 -27
  199. package/{server → server-e2e}/objstore/reaper.ts +1 -1
  200. package/server-e2e/objstore/rest-deny.ts +28 -0
  201. package/server-e2e/objstore/rest-mint.ts +224 -0
  202. package/{server → server-e2e}/objstore/rest.ts +3 -3
  203. package/{server → server-e2e}/objstore/store-neon.ts +6 -10
  204. package/{server → server-e2e}/objstore/store.ts +7 -7
  205. package/{server → server-e2e}/objstore/tokens.ts +2 -2
  206. package/{server → server-e2e}/pubsub.ts +12 -9
  207. package/{server → server-e2e}/sign.ts +4 -4
  208. package/{server → server-e2e}/sse-server.ts +60 -33
  209. package/{server → server-e2e}/static.ts +31 -21
  210. package/{server → server-e2e}/sync-handlers.ts +64 -30
  211. package/{server → server-e2e}/ws-server.ts +20 -9
  212. package/server-managed/activity.ts +258 -0
  213. package/server-managed/agent-merges.ts +109 -0
  214. package/server-managed/app-agents.ts +21 -0
  215. package/server-managed/avatar-store.ts +13 -0
  216. package/server-managed/blob-store.ts +17 -0
  217. package/server-managed/brotli.ts +10 -0
  218. package/server-managed/bundle-advisories.ts +120 -0
  219. package/server-managed/bundle-advisory-inventory.ts +77 -0
  220. package/server-managed/bundle-build-diagnostics.d.ts +10 -0
  221. package/server-managed/bundle-build-diagnostics.js +23 -0
  222. package/server-managed/bundle-build-leases.ts +41 -0
  223. package/server-managed/bundle-build-worker.js +115 -0
  224. package/server-managed/bundle-build.ts +200 -0
  225. package/server-managed/bundle-cache.ts +211 -0
  226. package/server-managed/bundle-catalog.ts +29 -0
  227. package/server-managed/bundle-commits.ts +140 -0
  228. package/server-managed/bundle-store.ts +24 -0
  229. package/server-managed/bundle-summary-cache.ts +130 -0
  230. package/server-managed/bundle.ts +119 -0
  231. package/server-managed/cache-storage.ts +20 -0
  232. package/server-managed/cli.js +21 -0
  233. package/server-managed/combined.ts +47 -0
  234. package/server-managed/comments.ts +151 -0
  235. package/server-managed/commit-coauthors.ts +106 -0
  236. package/server-managed/commit-lockfiles.ts +73 -0
  237. package/server-managed/comparison.ts +41 -0
  238. package/server-managed/config.ts +164 -0
  239. package/server-managed/content-access.ts +15 -0
  240. package/server-managed/crypto.ts +25 -0
  241. package/server-managed/db-methods.ts +1923 -0
  242. package/server-managed/db-neon.ts +458 -0
  243. package/server-managed/db-schema.ts +238 -0
  244. package/server-managed/db-table-names.ts +22 -0
  245. package/server-managed/db.ts +142 -0
  246. package/server-managed/dependabot-access.ts +60 -0
  247. package/server-managed/dependabot-alerts.ts +185 -0
  248. package/server-managed/dependency-review-comments.ts +100 -0
  249. package/server-managed/dependency-reviews.ts +346 -0
  250. package/server-managed/github-accounts.ts +60 -0
  251. package/server-managed/github-actions.ts +71 -0
  252. package/server-managed/github-app.ts +415 -0
  253. package/server-managed/github-errors.ts +70 -0
  254. package/server-managed/github-graphql.ts +39 -0
  255. package/server-managed/github-issue-links.ts +140 -0
  256. package/server-managed/github-issue-oauth.ts +47 -0
  257. package/server-managed/github-issues.ts +152 -0
  258. package/server-managed/github-link-oauth.ts +69 -0
  259. package/server-managed/github-links.ts +204 -0
  260. package/server-managed/github-metadata.ts +213 -0
  261. package/server-managed/github-oauth.ts +238 -0
  262. package/server-managed/github-pulls.ts +227 -0
  263. package/server-managed/github-requests.ts +39 -0
  264. package/server-managed/github-users.ts +30 -0
  265. package/server-managed/http-response.ts +76 -0
  266. package/server-managed/http.ts +5329 -0
  267. package/server-managed/import-triage.ts +51 -0
  268. package/server-managed/index.ts +192 -0
  269. package/server-managed/kept-briefly.ts +37 -0
  270. package/server-managed/kept.ts +28 -0
  271. package/server-managed/link-reports.ts +162 -0
  272. package/server-managed/locked-packages.ts +129 -0
  273. package/server-managed/lockfile-scope.ts +49 -0
  274. package/server-managed/lru.ts +30 -0
  275. package/server-managed/managed-issues.ts +78 -0
  276. package/server-managed/management-catalog.ts +45 -0
  277. package/server-managed/management.ts +99 -0
  278. package/server-managed/npm-delta-worker.js +23 -0
  279. package/server-managed/npm-delta.ts +194 -0
  280. package/server-managed/npm-insights.ts +449 -0
  281. package/server-managed/npm-kept.ts +215 -0
  282. package/server-managed/npm-loads.ts +264 -0
  283. package/server-managed/npm-manifest.ts +115 -0
  284. package/server-managed/npm-packages.ts +454 -0
  285. package/server-managed/npm-provenance.ts +123 -0
  286. package/server-managed/npm-risk.ts +285 -0
  287. package/server-managed/object-storage-disk.ts +148 -0
  288. package/server-managed/object-storage-vercel.ts +67 -0
  289. package/server-managed/object-storage.ts +75 -0
  290. package/server-managed/package-entry-points.ts +51 -0
  291. package/server-managed/pretty-print.ts +434 -0
  292. package/server-managed/public-workspace.ts +238 -0
  293. package/server-managed/pull-comment-reviews.ts +161 -0
  294. package/server-managed/pull-dependencies.ts +348 -0
  295. package/server-managed/pull-dependency-reviews.ts +328 -0
  296. package/server-managed/pull-reviews.ts +205 -0
  297. package/server-managed/queue.ts +39 -0
  298. package/server-managed/release-commits.ts +84 -0
  299. package/server-managed/rename-diff-worker.js +26 -0
  300. package/server-managed/rename-diff.ts +279 -0
  301. package/server-managed/repo-path.ts +21 -0
  302. package/server-managed/report-migration.ts +35 -0
  303. package/server-managed/report-query.ts +10 -0
  304. package/server-managed/report-response.ts +16 -0
  305. package/server-managed/report-sources.ts +178 -0
  306. package/server-managed/report-store.ts +85 -0
  307. package/server-managed/repository-actions.ts +218 -0
  308. package/server-managed/repository-agents.ts +355 -0
  309. package/server-managed/repository-aliases.ts +91 -0
  310. package/server-managed/repository-browser.ts +294 -0
  311. package/server-managed/repository-changes.ts +284 -0
  312. package/server-managed/repository-code.ts +215 -0
  313. package/server-managed/repository-dependency-reviews.ts +165 -0
  314. package/server-managed/repository-discovery.ts +82 -0
  315. package/server-managed/repository-history.ts +432 -0
  316. package/server-managed/repository-issues.ts +195 -0
  317. package/server-managed/repository-policy.ts +27 -0
  318. package/server-managed/repository-pulls.ts +156 -0
  319. package/server-managed/repository-pulse.ts +460 -0
  320. package/server-managed/repository-risk-assess.ts +105 -0
  321. package/server-managed/repository-risk-audit.ts +79 -0
  322. package/server-managed/repository-risk-read.ts +170 -0
  323. package/server-managed/repository-risk.ts +491 -0
  324. package/server-managed/repository-squashes.ts +168 -0
  325. package/server-managed/request-timing.ts +63 -0
  326. package/server-managed/reviewed-dependencies.ts +150 -0
  327. package/server-managed/revisions.ts +35 -0
  328. package/server-managed/scan-downloads.ts +87 -0
  329. package/server-managed/session.ts +132 -0
  330. package/server-managed/slugs.ts +38 -0
  331. package/server-managed/solidity-entry-points.ts +71 -0
  332. package/server-managed/sql-postgres.ts +34 -0
  333. package/server-managed/sql.ts +88 -0
  334. package/server-managed/static.ts +28 -0
  335. package/server-managed/storage-command.ts +16 -0
  336. package/server-managed/storage-db.ts +183 -0
  337. package/server-managed/storage-encryption.ts +290 -0
  338. package/server-managed/storage-maintenance.ts +259 -0
  339. package/server-managed/storage-payload.ts +121 -0
  340. package/server-managed/storage-stores.ts +91 -0
  341. package/server-managed/storage.ts +82 -0
  342. package/server-managed/team-app.ts +134 -0
  343. package/server-managed/team-catalog.ts +7 -0
  344. package/server-managed/team-feed.ts +180 -0
  345. package/server-managed/team-npm-scopes.ts +106 -0
  346. package/server-managed/team-reports.ts +307 -0
  347. package/server-managed/triage-response.ts +16 -0
  348. package/server-managed/type-only-worker.js +19 -0
  349. package/server-managed/type-only.ts +105 -0
  350. package/server-managed/uploads.ts +106 -0
  351. package/server-managed/upstream-cache.ts +243 -0
  352. package/server-managed/view-as.ts +58 -0
  353. package/server-managed/workspace-shares.ts +169 -0
  354. package/server.ts +70 -0
  355. package/server/index.ts +0 -480
  356. package/server/lifecycle.ts +0 -208
  357. /package/{LICENSE → LICENSE-MIT} +0 -0
  358. /package/{server → server-e2e}/auth.ts +0 -0
  359. /package/{server → server-e2e}/config.example.json +0 -0
  360. /package/{server → server-e2e}/objstore/blob-fs.ts +0 -0
  361. /package/{server → server-e2e}/objstore/fs.ts +0 -0
  362. /package/{server → server-e2e}/objstore/sign.ts +0 -0
  363. /package/{server → server-e2e}/peer.ts +0 -0
  364. /package/{server → server-e2e}/sse-session.ts +0 -0
  365. /package/{server → server-e2e}/util.ts +0 -0
  366. /package/{server → server-e2e}/validation.ts +0 -0
package/LICENSE-APACHE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @preventive/triage
2
+
3
+ [![](https://img.shields.io/npm/v/@preventive/triage?style=flat-square)](https://npmjs.org/package/@preventive/triage)
4
+ [![](https://img.shields.io/npm/l/@preventive/triage?style=flat-square&color=blue)](#license)
5
+
6
+ Client & relay server for triaging automated reports.
7
+
8
+ ```sh
9
+ npx -p @preventive/triage triage-server --mode e2e # or managed, managed-e2e, e2e-managed
10
+ ```
11
+
12
+ `triage-server --help` lists the environment each mode reads.
13
+
14
+ ## License
15
+
16
+ [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE), at your option.
package/api/reap.ts ADDED
@@ -0,0 +1,17 @@
1
+ // E2e-only Vercel deployment: no persistent relay is running in this function.
2
+ // The managed deployment routes /api/reap through its top-level app instead.
3
+ import { createReapHandler } from '../server-common/reap.ts'
4
+ import { databaseUrls } from '../server-common/database-config.ts'
5
+ import { reapOrphans } from '../server-e2e/objstore/reaper.ts'
6
+ import { openNeonObjstore } from '../server-e2e/objstore/store-neon.ts'
7
+ import { openVercelBlobBackend } from '../server-e2e/objstore/blob-vercel.ts'
8
+
9
+ const handler = createReapHandler({ e2e: async () => {
10
+ const url = databaseUrls().e2e
11
+ const token = process.env['BLOB_READ_WRITE_TOKEN']
12
+ if (!url || !token) throw new Error('E2e cron requires DATABASE_URL or E2E_DATABASE_URL, plus BLOB_READ_WRITE_TOKEN')
13
+ const blob = await openVercelBlobBackend({ token })
14
+ // Stateless Neon HTTP handle: nothing to close, no relay listener or timer.
15
+ await reapOrphans(await openNeonObjstore(url, blob))
16
+ } })
17
+ export default handler
package/cli.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import './strip-types-loader.js'
3
+
4
+ // Load TypeScript only after the stripping hook is registered.
5
+ const { start } = await import('./server.ts')
6
+ await start()
@@ -0,0 +1,3 @@
1
+ export function isDependencyFile(file: unknown, directory: string): boolean
2
+ export function dependencyDirectory(reports: Iterable<{ groups?: Iterable<Iterable<{ file?: unknown }>>; tree?: unknown; treeDependencies?: unknown }>): 'node_modules' | 'vendor' | 'dependencies'
3
+ export function treeDependencyDirectory(tree: unknown): 'node_modules' | 'vendor' | null
@@ -0,0 +1,36 @@
1
+ // Match whole path segments, using the same directory precedence as the
2
+ // findings dependency filter. Less-specific directory names can be own source.
3
+ export function isDependencyFile(file, directory) {
4
+ if (typeof file !== 'string') return false
5
+ const path = file.replaceAll('\\', '/')
6
+ return path.startsWith(`${directory}/`) || path.includes(`/${directory}/`)
7
+ }
8
+
9
+ // A team's reports tell the one their tree names (`treeDependencies`) in place
10
+ // of the tree, which is asked for apart (server-managed/team-reports.ts).
11
+ export function dependencyDirectory(reports) {
12
+ let vendor = false
13
+ for (const report of reports) {
14
+ for (const group of report.groups ?? []) {
15
+ for (const finding of group) {
16
+ if (isDependencyFile(finding.file, 'node_modules')) return 'node_modules'
17
+ if (isDependencyFile(finding.file, 'vendor')) vendor = true
18
+ }
19
+ }
20
+ const named = report.tree ? treeDependencyDirectory(report.tree) : report.treeDependencies
21
+ if (named === 'node_modules') return 'node_modules'
22
+ if (named === 'vendor') vendor = true
23
+ }
24
+ return vendor ? 'vendor' : 'dependencies'
25
+ }
26
+
27
+ // The dependency directory a report's file tree names: node_modules, else
28
+ // vendor, else none.
29
+ export function treeDependencyDirectory(tree) {
30
+ let vendor = false
31
+ for (const file of Object.keys(tree ?? {})) {
32
+ if (isDependencyFile(file, 'node_modules')) return 'node_modules'
33
+ if (isDependencyFile(file, 'vendor')) vendor = true
34
+ }
35
+ return vendor ? 'vendor' : null
36
+ }
@@ -0,0 +1,305 @@
1
+ // Deep links to a single finding — pure logic side. Encodes the
2
+ // finding's id plus a "where does it live" hint into the URL hash
3
+ // (`#finding=<id>&v=<hints>`) and parses the same back on the receiving
4
+ // side, so "look at THIS issue" is a link you can paste into a chat
5
+ // instead of a screenshot plus directions.
6
+ //
7
+ // Sibling of `workspace-share-link.js`, and deliberately unlike it in
8
+ // one respect: the id here is NOT a secret. A share link carries the
9
+ // workspace's private key and is therefore encrypted; a finding id is
10
+ // meaningless without the report the recipient must already have, so it
11
+ // rides in the clear and the link stays legible in a chat client.
12
+ //
13
+ // The two LOCATION hints are a different matter. A report filename
14
+ // ("acme-corp-pentest-2024.json") and a workspace id are the sender's
15
+ // own metadata, and a URL is the most-forwarded, most-logged, most-
16
+ // screenshotted string there is — so they ship as 3-byte digests
17
+ // (4 base64url chars) rather than plaintext. That is a fingerprint, not
18
+ // a sealed box: 24 bits only narrows the field, and anyone holding a
19
+ // candidate name can confirm it by hashing. What it does buy is that
20
+ // the name itself never appears in the URL, and a recipient who does
21
+ // NOT hold the report learns nothing from the link but its length.
22
+ //
23
+ // Cheap on the receiving side too: matching a hint means hashing the
24
+ // names already on disk, not reading a single file (see
25
+ // `finding-locate.js`).
26
+ //
27
+ // The hint is a HINT, not an address. The receiver tries the named report
28
+ // or workspace, then a scan of everything stored locally (see
29
+ // `ui/view/finding-link-nav.js`) — so a link built in a workspace still
30
+ // lands for a recipient who only has the single report, and a stale
31
+ // hint costs a scan rather than the finding.
32
+ //
33
+ // Param names live in the same `&`-joined fragment namespace as
34
+ // `share=`, and the parser has the same shape as `extractShareEncoded`
35
+ // — a fragment can therefore only ever be one kind of link, and adding
36
+ // a fourth param later doesn't disturb either.
37
+
38
+ import { encodeUtf8 } from '../common/utf8.js'
39
+ import { managedRoutePath, parseManagedRoute } from '../common/managed/routes.js'
40
+ import { MAX_FINDING_ID_LENGTH, isLinkableFindingId } from '../common/finding-id.js'
41
+ export { isLinkableFindingId } from '../common/finding-id.js'
42
+
43
+ // ── location hints ───────────────────────────────────────────────────
44
+
45
+ // 3 bytes → exactly 4 base64url characters, no padding. Sized to keep
46
+ // the fragment short while still being selective enough that a hint
47
+ // usually names one report out of a user's collection: with 16.7M
48
+ // values, a library of 100 reports has a ~0.03% chance of any collision
49
+ // at all, and a collision costs only a wrong first guess (the receiver
50
+ // re-checks that the finding is actually there, then scans).
51
+ const HINT_BYTES = 3
52
+ const HINT_CHARS = 4
53
+ const HINT_RE = /^[\w-]{4}$/u
54
+
55
+ // Both hints travel as ONE `v=` parameter: 6 bytes, 8 base64url
56
+ // characters, report half first. Workspace-only links use `w` followed
57
+ // by their 3-byte / 4-character hint. Purely a length decision — two named
58
+ // params spend 20 characters (`&report=aB3-&ws=x_9Z`) to carry 8
59
+ // characters of payload, and a link is something people paste into
60
+ // chat, tickets and commit messages.
61
+ //
62
+ // Concatenating the two 4-character tokens IS the base64 of the 6-byte
63
+ // buffer: 3 bytes is exactly one base64 group, so nothing carries
64
+ // across the halves and either framing produces identical text. That's
65
+ // what lets the packing be a string join and the unpacking a slice,
66
+ // with no encode / decode round trip.
67
+ //
68
+ // `AAAA` (3 zero bytes) marks a half that isn't known — a single-file
69
+ // view has no workspace, and a report whose hint hasn't been hashed yet
70
+ // omits its own. A genuine digest of `AAAA` therefore reads as absent,
71
+ // once per 16.7M values per half; the cost is that the receiver skips
72
+ // straight to the scan, which is where an unmatched hint lands anyway.
73
+ const COMBINED_RE = /^[\w-]{8}$/u
74
+ const NO_HINT = 'AAAA'
75
+
76
+ // Domain-separated per kind so the same string can't produce a hint
77
+ // that matches in the other namespace — a workspace whose id happened
78
+ // to equal a report's filename would otherwise cross-resolve. Same
79
+ // convention as `workspace-id.js`'s derivation domain.
80
+ const HINT_DOMAINS = {
81
+ report: 'deepview/finding-link/report/v1\n',
82
+ workspace: 'deepview/finding-link/workspace/v1\n',
83
+ }
84
+
85
+ // `${kind}\0${value}` → token. Memoised because the LINK BUILDER must
86
+ // be synchronous: the Link button writes to the clipboard inside the
87
+ // click handler, and Safari drops the clipboard grant if the write
88
+ // happens after an await. Ingest primes this cache for every report it
89
+ // loads (and `switchToWorkspace` for the workspace), so by the time a
90
+ // card is on screen its hint is already here. A cold entry is not a
91
+ // failure — the link is simply built without that hint, and the
92
+ // receiver falls back to the scan.
93
+ const hintCache = new Map()
94
+
95
+ function hintKey(kind, value) { return `${kind}\0${value}` }
96
+
97
+ // Whether a value is a well-formed hint token, i.e. something
98
+ // `computeLinkHint` could have produced.
99
+ export function isLinkHint(value) {
100
+ return typeof value === 'string' && HINT_RE.test(value)
101
+ }
102
+
103
+ // Derive (and memoise) the hint token for a report filename or a
104
+ // workspace id. Resolves to null — never throws — when the kind is
105
+ // unknown, the value is empty, or `crypto.subtle` is unavailable (some
106
+ // `file://` setups): a missing hint degrades the link, it doesn't break
107
+ // it, so the caller shouldn't have to guard.
108
+ export async function computeLinkHint(kind, value) {
109
+ const domain = HINT_DOMAINS[kind]
110
+ if (!domain || typeof value !== 'string' || !value) return null
111
+ const key = hintKey(kind, value)
112
+ const cached = hintCache.get(key)
113
+ if (cached !== undefined) return cached
114
+ let token
115
+ try {
116
+ const digest = await crypto.subtle.digest('SHA-256', encodeUtf8(domain + value))
117
+ token = new Uint8Array(digest).slice(0, HINT_BYTES)
118
+ .toBase64({ alphabet: 'base64url', omitPadding: true })
119
+ } catch {
120
+ // Not cached: an environment without crypto.subtle stays broken,
121
+ // but a one-off failure (a lone surrogate in a filename tripping
122
+ // encodeUtf8) shouldn't poison the entry forever.
123
+ return null
124
+ }
125
+ hintCache.set(key, token)
126
+ return token
127
+ }
128
+
129
+ // Synchronous companion — the memoised token, or null if it hasn't been
130
+ // computed yet. The link builder uses this; see `hintCache` above for
131
+ // why it can't just await.
132
+ export function knownLinkHint(kind, value) {
133
+ if (typeof value !== 'string' || !value) return null
134
+ return hintCache.get(hintKey(kind, value)) ?? null
135
+ }
136
+
137
+ // Pack both hints into the `v=` value, or null when neither is known
138
+ // (in which case the param is omitted entirely rather than shipping
139
+ // eight characters of "nothing"). Throws on a non-empty value that
140
+ // isn't a token, so a caller passing a raw filename finds out
141
+ // immediately instead of shipping a hint-less link.
142
+ function packLinkHints(report, workspace) {
143
+ const halves = []
144
+ for (const [what, hint] of [['report', report], ['workspace', workspace]]) {
145
+ if (hint === null || hint === undefined || hint === '') { halves.push(NO_HINT); continue }
146
+ if (!isLinkHint(hint)) {
147
+ throw new TypeError(`encodeFindingRef: ${what} must be a link hint token, not a name`)
148
+ }
149
+ halves.push(hint)
150
+ }
151
+ if (halves.every((h) => h === NO_HINT)) return null
152
+ // Workspace → finding needs only the workspace half. Report links
153
+ // retain their original six-byte / eight-character encoding.
154
+ if (halves[0] === NO_HINT) return `w${halves[1]}`
155
+ return halves.join('')
156
+ }
157
+
158
+ // Split a validated `v=` value back into its two halves, mapping the
159
+ // `AAAA` filler to null.
160
+ function unpackLinkHints(value) {
161
+ const halves = [value.slice(0, HINT_CHARS), value.slice(HINT_CHARS)]
162
+ .map((h) => (h === NO_HINT ? null : h))
163
+ return { report: halves[0], workspace: halves[1] }
164
+ }
165
+
166
+ // ── fragment codec ───────────────────────────────────────────────────
167
+
168
+ // Build the fragment body (no leading '#') for a finding reference.
169
+ // `report` / `workspace` are hint TOKENS (from `computeLinkHint` /
170
+ // `knownLinkHint`), not names; they leave as the single packed `v=`
171
+ // param: `w` + four characters for a workspace, eight for a report with its
172
+ // optional workspace. Omitted when neither is known.
173
+ //
174
+ // The id is percent-encoded: usually a uuid with nothing to escape, but
175
+ // the codex importer's finding-URL ids carry `/`, `:`, `?` and —
176
+ // decisively — `&` and `=`, which would otherwise split into phantom
177
+ // params on the way back. The packed hints need no encoding (base64url
178
+ // is already fragment-safe) and are emitted verbatim.
179
+ export function encodeFindingRef({ id, report, workspace } = {}) {
180
+ if (!isLinkableFindingId(id)) {
181
+ throw new TypeError('encodeFindingRef: a persistent finding id is required')
182
+ }
183
+ const parts = [`finding=${encodeURIComponent(id)}`]
184
+ const packed = packLinkHints(report, workspace)
185
+ if (packed) parts.push(`v=${packed}`)
186
+ return parts.join('&')
187
+ }
188
+
189
+ // Full shareable URL for the current page origin + pathname, mirroring
190
+ // `buildShareUrl`. `location.search` is dropped for the same reason it
191
+ // is there: the target page takes no query params, so dragging the
192
+ // sender's current `?foo=bar` into the recipient's URL would be a
193
+ // surprising leak.
194
+ export function buildFindingUrl(ref, pathname) {
195
+ const encoded = encodeFindingRef(ref)
196
+ if (typeof location === 'undefined') return `${pathname ?? ''}#${encoded}`
197
+ return `${location.origin}${pathname ?? location.pathname}#${encoded}`
198
+ }
199
+
200
+ // Extract `{ id, report, workspace }` from a hash string (or
201
+ // `location.hash` when called with no argument), unpacking `v=` back
202
+ // into the two hint tokens. Returns null when the fragment carries no
203
+ // usable `finding=` param — a share link, an in-page anchor, an empty
204
+ // hash, or a payload that fails validation.
205
+ //
206
+ // HASH ONLY, matching `extractShareEncoded`. The id isn't secret, so the
207
+ // Referer-leak argument that motivates it there doesn't apply; the
208
+ // reason is uniformity — one place in the app reads deep links, and a
209
+ // caller that reached for `?finding=` would quietly bypass it.
210
+ //
211
+ // A malformed `v=` drops both hints rather than the whole ref: the id is
212
+ // what identifies the finding, and the receiver's scan can still land
213
+ // it. That also covers a link from an older build, whose separate
214
+ // `report=` / `ws=` params simply go unread. An un-decodable ID, on the
215
+ // other hand, fails the whole parse — there is nothing left to look up.
216
+ export function extractFindingRef(hash) {
217
+ const raw = typeof hash === 'string' ? hash : (typeof location === 'undefined' ? '' : location.hash)
218
+ if (!raw) return null
219
+ const stripped = raw.replace(/^#/u, '')
220
+ if (!stripped) return null
221
+ const found = { id: null, report: null, workspace: null }
222
+ for (const part of stripped.split('&')) {
223
+ const eq = part.indexOf('=')
224
+ if (eq < 0) continue
225
+ const key = part.slice(0, eq)
226
+ const rawValue = part.slice(eq + 1)
227
+ if (!rawValue) continue
228
+ // The packed hints are one workspace token or a token pair — validated
229
+ // verbatim rather than decoded, since base64url has nothing
230
+ // `encodeURIComponent` would have touched. Anything else is a link
231
+ // from another era or a mangled paste; drop it and let the scan
232
+ // take over.
233
+ if (key === 'v') {
234
+ if (rawValue.length === 5 && rawValue[0] === 'w' && isLinkHint(rawValue.slice(1))) {
235
+ const workspace = rawValue.slice(1)
236
+ Object.assign(found, { report: null, workspace: workspace === NO_HINT ? null : workspace })
237
+ continue
238
+ }
239
+ if (!COMBINED_RE.test(rawValue)) continue
240
+ Object.assign(found, unpackLinkHints(rawValue))
241
+ continue
242
+ }
243
+ if (key !== 'finding') continue
244
+ // Cheap length bound BEFORE decoding: percent-encoding expands at
245
+ // most 3:1, so nothing under this cap can decode to something over
246
+ // MAX_FINDING_ID_LENGTH, and a megabyte fragment never reaches
247
+ // decodeURIComponent. `isLinkableFindingId` re-checks the decoded
248
+ // length, so this is a guard, not the rule.
249
+ if (rawValue.length > MAX_FINDING_ID_LENGTH * 3) continue
250
+ let value
251
+ // decodeURIComponent throws URIError on a truncated / invalid escape
252
+ // (`%`, `%zz`) — common when a chat client mangles a pasted link.
253
+ try { value = decodeURIComponent(rawValue) } catch { continue }
254
+ if (isLinkableFindingId(value)) found.id = value
255
+ }
256
+ if (!found.id) return null
257
+ return found
258
+ }
259
+
260
+ // Recognise a finding deep link pasted into free text — the reverse of
261
+ // `buildFindingUrl`, used to linkify comments (see `parseCommentRefs` in
262
+ // `ui/view/format.js`). Returns `{ id, fragment, path }` with the E2E fragment
263
+ // re-emitted canonically and a managed destination URL, or null
264
+ // for anything that isn't one of OUR links.
265
+ //
266
+ // "Ours" means the CURRENT host, scheme included. A finding id resolves
267
+ // only against the reader's available reports, so a link to some other
268
+ // deployment couldn't be followed usefully even if it were offered — and
269
+ // a same-name look-alike on another host is exactly what a linkifier
270
+ // must not present as the real thing. Credentials are refused for the
271
+ // same reason (`https://user@triage.space/…` reads as ours but isn't
272
+ // something the app ever emits).
273
+ //
274
+ // Managed team/report paths are preserved to identify the intended copy.
275
+ // E2E entry links resolve from `/` in managed mode, without inheriting
276
+ // the current report. E2E consumers use just the fragment to stay on their
277
+ // current deployment, including `/index.html` and subpaths.
278
+ //
279
+ // The anti-mutation guard is the one from `githubRefToken`: `new URL`
280
+ // silently rewrites its input (resolving `..`, lower-casing, punycoding
281
+ // IDN homographs, dropping a default port), any of which can let a
282
+ // look-alike round up into a passing link. A candidate that isn't
283
+ // already canonical is refused rather than linkified. Every link this
284
+ // app emits round-trips unchanged, since `buildFindingUrl` builds from
285
+ // `location` itself.
286
+ export function parseFindingUrl(candidate, { managed = false } = {}) {
287
+ if (typeof location === 'undefined') return null
288
+ let u
289
+ try { u = new URL(candidate) } catch { return null }
290
+ if (u.href !== candidate) return null
291
+ if (u.protocol !== location.protocol) return null
292
+ if (u.host !== location.host) return null
293
+ if (u.username || u.password) return null
294
+ const route = parseManagedRoute(u)
295
+ if (route?.finding) {
296
+ return { id: route.finding.id, fragment: encodeFindingRef(route.finding), path: managedRoutePath(route) }
297
+ }
298
+ // Only E2E entry pages carry finding hashes. Unknown managed page URLs
299
+ // must not turn into a search for another accessible copy of the finding.
300
+ if (managed && route?.view !== 'home' && !/\/(?:index|view)\.html$/u.test(u.pathname)) return null
301
+ const ref = extractFindingRef(u.hash)
302
+ if (!ref) return null
303
+ const fragment = encodeFindingRef(ref)
304
+ return { id: ref.id, fragment, path: `/#${fragment}` }
305
+ }
@@ -0,0 +1 @@
1
+ export function parseLinkedFindings(content: string): { groups: string[][]; skipped: number } | null