@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
@@ -0,0 +1,182 @@
1
+ // Storage envelope v1: header, then 64 KiB plaintext chunks with full AEAD tags.
2
+ // The nonce layout follows age's STREAM construction (counter || final flag).
3
+ // Every write derives a new key, including rewrites of the same logical object.
4
+ import { Buffer } from 'node:buffer'
5
+ import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes } from 'node:crypto'
6
+ import { Readable } from 'node:stream'
7
+
8
+ export const STORAGE_MAGIC = Buffer.from('DeepView.storage')
9
+ const VERSION = 1
10
+ export const STORAGE_CHUNK_BYTES = 65_536
11
+ export const STORAGE_HEADER_BYTES = 57
12
+ const TAG_BYTES = 16
13
+ const UNKNOWN_SIZE = 0xffff_ffff_ffff_ffffn
14
+
15
+ export interface StorageKey { bytes: Buffer; id: string }
16
+
17
+ export function parseStorageKey(value: string | null | undefined): StorageKey | null {
18
+ if (value == null || value === '') return null
19
+ const bytes = Buffer.from(value, 'base64')
20
+ if (bytes.length !== 32 || bytes.toString('base64') !== value) {
21
+ throw new Error('MANAGED_STORAGE_ENCRYPTION_KEY must be canonical base64 encoding of 32 random bytes')
22
+ }
23
+ const id = createHash('sha256').update('deepview.storage.key.v1\0').update(bytes).digest('hex').slice(0, 32)
24
+ return { bytes, id }
25
+ }
26
+
27
+ // Re-chunk without accumulating a file, regardless of the input's read sizes.
28
+ async function* chunks(source: AsyncIterable<Uint8Array>, size: number): AsyncGenerator<Buffer> {
29
+ let length = 0, pending: Buffer[] = []
30
+ for await (const value of source) {
31
+ const input = Buffer.from(value.buffer, value.byteOffset, value.byteLength)
32
+ for (let offset = 0; offset < input.length;) {
33
+ const n = Math.min(size - length, input.length - offset)
34
+ pending.push(input.subarray(offset, offset + n)); length += n; offset += n
35
+ if (length === size) { yield Buffer.concat(pending, length); pending = []; length = 0 }
36
+ }
37
+ }
38
+ if (length) yield Buffer.concat(pending, length)
39
+ }
40
+
41
+ async function* frames(source: AsyncIterable<Uint8Array>, size: number) {
42
+ const iterator = chunks(source, size)[Symbol.asyncIterator]()
43
+ try {
44
+ let current = await iterator.next()
45
+ if (current.done) { yield { bytes: Buffer.alloc(0), last: true }; return }
46
+ while (!current.done) {
47
+ const next = await iterator.next()
48
+ yield { bytes: current.value, last: next.done === true }
49
+ current = next
50
+ }
51
+ } finally { await iterator.return(undefined) }
52
+ }
53
+
54
+ function nonce(index: number, last: boolean): Buffer {
55
+ if (!Number.isSafeInteger(index) || index < 0 || index > 0xffff_ffff) throw new Error('Storage object has too many chunks')
56
+ const value = Buffer.alloc(12)
57
+ value.writeUInt32BE(index, 7)
58
+ value[11] = last ? 1 : 0
59
+ return value
60
+ }
61
+
62
+ function objectKey(key: Uint8Array, header: Buffer, identity: string): Buffer {
63
+ return Buffer.from(hkdfSync('sha256', key, header.subarray(17, 49),
64
+ Buffer.from(JSON.stringify(['deepview.storage.v1', identity])), 32))
65
+ }
66
+
67
+ function makeHeader(size: number | null): Buffer {
68
+ if (size !== null && (!Number.isSafeInteger(size) || size < 0)) throw new Error('Invalid storage size')
69
+ const header = Buffer.alloc(STORAGE_HEADER_BYTES)
70
+ STORAGE_MAGIC.copy(header); header[16] = VERSION
71
+ randomBytes(32).copy(header, 17)
72
+ header.writeBigUInt64BE(size === null ? UNKNOWN_SIZE : BigInt(size), 49)
73
+ return header
74
+ }
75
+
76
+ export function encryptStorageStream(source: Readable, key: Uint8Array, identity: string, size: number | null): Readable {
77
+ async function* encrypt() {
78
+ const header = makeHeader(size)
79
+ const derived = objectKey(key, header, identity)
80
+ let index = 0, length = 0
81
+ try {
82
+ yield header
83
+ for await (const { bytes, last } of frames(source, STORAGE_CHUNK_BYTES)) {
84
+ length += bytes.length
85
+ if (last && size !== null && length !== size) throw new Error('Storage size changed while encrypting')
86
+ const cipher = createCipheriv('chacha20-poly1305', derived, nonce(index++, last), { authTagLength: TAG_BYTES })
87
+ cipher.setAAD(header)
88
+ yield Buffer.concat([cipher.update(bytes), cipher.final(), cipher.getAuthTag()])
89
+ }
90
+ } finally { derived.fill(0); source.destroy() }
91
+ }
92
+ const stream = Readable.from(encrypt(), { objectMode: false })
93
+ stream.once('close', () => source.destroy())
94
+ return stream
95
+ }
96
+
97
+ // Read just the envelope and authenticate the first frame before returning.
98
+ // In particular, never trust a corrupted size or release update() output
99
+ // before final() authenticates the complete chunk.
100
+ export async function decryptStorageStream(source: Readable, key: Uint8Array, identity: string) {
101
+ const iterator = source[Symbol.asyncIterator]()
102
+ let done = false
103
+ let peek: Buffer = Buffer.alloc(0), remainder: Buffer = Buffer.alloc(0)
104
+ async function read(size: number): Promise<Buffer> {
105
+ const parts: Buffer[] = peek.length > 0 ? [peek] : []
106
+ let length = peek.length
107
+ peek = Buffer.alloc(0)
108
+ while (length < size) {
109
+ if (remainder.length === 0 && !done) {
110
+ const next = await iterator.next(); done = next.done === true
111
+ remainder = done ? Buffer.alloc(0) : next.value
112
+ }
113
+ if (remainder.length === 0 && done) break
114
+ const n = Math.min(size - length, remainder.length)
115
+ parts.push(remainder.subarray(0, n)); remainder = remainder.subarray(n); length += n
116
+ }
117
+ return Buffer.concat(parts, length)
118
+ }
119
+ let derived: Buffer | undefined
120
+ try {
121
+ const header = await read(STORAGE_HEADER_BYTES)
122
+ if (header.length !== STORAGE_HEADER_BYTES || !header.subarray(0, 16).equals(STORAGE_MAGIC) || header[16] !== VERSION) {
123
+ throw new Error('Invalid encrypted storage envelope')
124
+ }
125
+ const rawSize = header.readBigUInt64BE(49)
126
+ if (rawSize !== UNKNOWN_SIZE && rawSize > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error('Invalid encrypted storage size')
127
+ const size = rawSize === UNKNOWN_SIZE ? null : Number(rawSize)
128
+ derived = objectKey(key, header, identity)
129
+ const derivedKey = derived
130
+ let index = 0, length = 0
131
+ const nextFrame = async () => {
132
+ const bytes = await read(STORAGE_CHUNK_BYTES + TAG_BYTES)
133
+ // Peek beyond the frame: EOF authenticates the final flag, including
134
+ // exact chunk boundaries. Keep the byte for the next read.
135
+ peek = await read(1)
136
+ const last = peek.length === 0
137
+ if (bytes.length < TAG_BYTES || (index > 0 && bytes.length === TAG_BYTES)) throw new Error('Truncated encrypted storage object')
138
+ const decipher = createDecipheriv('chacha20-poly1305', derivedKey, nonce(index++, last), { authTagLength: TAG_BYTES })
139
+ decipher.setAAD(header); decipher.setAuthTag(bytes.subarray(-TAG_BYTES))
140
+ const plain = Buffer.concat([decipher.update(bytes.subarray(0, -TAG_BYTES)), decipher.final()])
141
+ length += plain.length
142
+ if (size !== null && (length > size || (last && length !== size))) throw new Error('Encrypted storage size mismatch')
143
+ return { plain, last }
144
+ }
145
+ const first = await nextFrame()
146
+ const decrypt = async function* () {
147
+ try {
148
+ yield first.plain
149
+ let last = first.last
150
+ while (!last) { const frame = await nextFrame(); last = frame.last; yield frame.plain }
151
+ } finally { derivedKey.fill(0); await iterator.return?.(); source.destroy() }
152
+ }
153
+ const stream = Readable.from(decrypt(), { objectMode: false })
154
+ stream.once('close', () => { derivedKey.fill(0); source.destroy() })
155
+ return { size, stream }
156
+ } catch (err) { derived?.fill(0); source.destroy(); throw err }
157
+ }
158
+
159
+ // Small SQL values (data keys, OAuth tokens and the installation sentinel).
160
+ // Fresh salt derives a one-use wrapping key, so the nonce can be all zeroes.
161
+ // The version and master-key ID are authenticated along with the row/field.
162
+ export function wrapStorageValue(key: StorageKey, identity: string, value: Uint8Array): string {
163
+ const header = Buffer.concat([Buffer.from([1]), Buffer.from(key.id, 'hex'), randomBytes(32)])
164
+ const derived = Buffer.from(hkdfSync('sha256', key.bytes, header.subarray(17), 'deepview.wrap.v1', 32))
165
+ try {
166
+ const cipher = createCipheriv('chacha20-poly1305', derived, Buffer.alloc(12), { authTagLength: TAG_BYTES })
167
+ cipher.setAAD(Buffer.concat([header, Buffer.from(identity)]))
168
+ return Buffer.concat([header, cipher.update(value), cipher.final(), cipher.getAuthTag()]).toString('base64')
169
+ } finally { derived.fill(0) }
170
+ }
171
+
172
+ export function unwrapStorageValue(key: StorageKey, identity: string, value: string): Buffer {
173
+ const bytes = Buffer.from(value, 'base64'), header = bytes.subarray(0, 49)
174
+ if (bytes.length < 65 || bytes.toString('base64') !== value || header[0] !== 1
175
+ || header.subarray(1, 17).toString('hex') !== key.id) throw new Error('Invalid wrapped storage value or encryption key')
176
+ const derived = Buffer.from(hkdfSync('sha256', key.bytes, header.subarray(17), 'deepview.wrap.v1', 32))
177
+ try {
178
+ const decipher = createDecipheriv('chacha20-poly1305', derived, Buffer.alloc(12), { authTagLength: TAG_BYTES })
179
+ decipher.setAAD(Buffer.concat([header, Buffer.from(identity)])); decipher.setAuthTag(bytes.subarray(-TAG_BYTES))
180
+ return Buffer.concat([decipher.update(bytes.subarray(49, -TAG_BYTES)), decipher.final()])
181
+ } finally { derived.fill(0) }
182
+ }
@@ -0,0 +1,38 @@
1
+ import { dirname, resolve } from 'node:path'
2
+
3
+ type DatabaseConfig = { dbPath: string; neonUrl?: string | null }
4
+
5
+ function databaseLocation({ dbPath, neonUrl }: DatabaseConfig): string {
6
+ if (!neonUrl) return `SQLite ${dbPath === ':memory:' ? dbPath : resolve(dbPath)}`
7
+ // Report the destination, never URL credentials or query-string secrets.
8
+ try {
9
+ const url = new URL(neonUrl)
10
+ return `Neon Postgres ${url.host}${url.pathname}`
11
+ } catch { return 'Neon Postgres' }
12
+ }
13
+
14
+ export function e2eStorageLines(config: DatabaseConfig & { objstoreDir: string }): string[] {
15
+ return [
16
+ ` E2E database: ${databaseLocation(config)}`,
17
+ ` E2E objects: ${config.neonUrl ? 'Vercel Blob (private), {workspaceTag}/' : resolve(config.objstoreDir)}`,
18
+ ` E2E staging: ${config.neonUrl ? 'Vercel Blob (private), {workspaceTag}/.staging/' : resolve(config.objstoreDir, '{workspaceTag}', '.staging')}`,
19
+ ]
20
+ }
21
+
22
+ export function managedStorageLines(config: DatabaseConfig & { storageEncryptionKey?: string | null }): string[] {
23
+ const dir = dirname(config.dbPath)
24
+ const location = (path: string) => config.neonUrl ? `Vercel Blob (private), .managed/${path}/` : resolve(dir, path)
25
+ return [
26
+ ` Managed database: ${databaseLocation(config)}`,
27
+ ...(config.storageEncryptionKey ? [
28
+ ' Managed storage encryption: ChaCha20-Poly1305',
29
+ ` Managed encrypted caches: ${location('cache-encrypted-v1')}`,
30
+ ] : []),
31
+ ` Managed reports: ${location('reports')}`,
32
+ ` Managed bundles: ${location('bundles')}`,
33
+ ` Managed avatars: ${location('avatars')}`,
34
+ ` Managed bundle cache: ${location('cache/bundles')}`,
35
+ ` Managed report sources cache: ${location('cache/report-sources')}`,
36
+ ...(config.neonUrl ? [` Managed upload parts: ${location('uploads')}`] : []),
37
+ ]
38
+ }
@@ -0,0 +1,103 @@
1
+ // Shared optional Vercel Blob SDK boundary for both server modes.
2
+ import type { Readable } from 'node:stream'
3
+ import type { Buffer } from 'node:buffer'
4
+
5
+ // Minimal structural shape of the bits of `@vercel/blob` we use.
6
+ // Shared so the optional peer dep doesn't have to type-resolve
7
+ // for SQLite-only deployments. Real type details live in the
8
+ // installed package; the fields/parameters we touch here are stable
9
+ // per the SDK v2 public surface.
10
+ //
11
+ // `put` accepts the SDK's full `PutBody` union (string | Readable |
12
+ // Buffer | Blob | ArrayBuffer | ReadableStream | File). We only ever
13
+ // pass a Node `PassThrough` (a Readable), but the wider type lets
14
+ // callers reuse this signature for future buffer/blob bodies without
15
+ // type gymnastics. `copy` accepts `allowOverwrite` — REQUIRED for
16
+ // version bumps since the live pathname is reused on re-upload.
17
+
18
+ type VercelBlobBody = Readable | Buffer | string | Blob | ArrayBuffer | ReadableStream<Uint8Array>
19
+ export type VercelBlobSdk = {
20
+ BlobNotFoundError: new () => Error
21
+ BlobPreconditionFailedError?: new () => Error
22
+ put: (
23
+ pathname: string,
24
+ body: VercelBlobBody,
25
+ options: {
26
+ access: 'private' | 'public'
27
+ addRandomSuffix?: boolean
28
+ allowOverwrite?: boolean
29
+ contentType?: string
30
+ token?: string
31
+ ifMatch?: string
32
+ multipart?: boolean
33
+ abortSignal?: AbortSignal
34
+ cacheControlMaxAge?: number
35
+ },
36
+ ) => Promise<{ url: string; pathname: string }>
37
+ head: (
38
+ pathname: string,
39
+ options?: { token?: string; abortSignal?: AbortSignal },
40
+ ) => Promise<{ size: number; pathname: string; url: string; etag?: string; uploadedAt?: Date | string | number }>
41
+ get: (
42
+ pathname: string,
43
+ options: {
44
+ access: 'private' | 'public'
45
+ token?: string
46
+ useCache?: boolean
47
+ headers?: Record<string, string>
48
+ abortSignal?: AbortSignal
49
+ },
50
+ ) => Promise<{
51
+ statusCode: 200 | 304
52
+ stream: ReadableStream<Uint8Array> | null
53
+ blob: { size: number | null; etag?: string; uploadedAt?: Date | string | number }
54
+ } | null>
55
+ copy: (
56
+ fromPathname: string,
57
+ toPathname: string,
58
+ options: {
59
+ access: 'private' | 'public'
60
+ addRandomSuffix?: boolean
61
+ allowOverwrite?: boolean
62
+ token?: string
63
+ contentType?: string
64
+ cacheControlMaxAge?: number
65
+ },
66
+ ) => Promise<{ url: string; pathname: string }>
67
+ del: (
68
+ urlOrPathname: string | string[],
69
+ options?: { token?: string; abortSignal?: AbortSignal; ifMatch?: string },
70
+ ) => Promise<void>
71
+ list: (options: {
72
+ prefix?: string
73
+ cursor?: string
74
+ limit?: number
75
+ mode?: 'expanded' | 'folded'
76
+ token?: string
77
+ abortSignal?: AbortSignal
78
+ }) => Promise<{
79
+ // `uploadedAt` (a Date per the SDK v2 surface) is the blob's
80
+ // creation time — used by the reaper's GC grace window. Optional
81
+ // in the type because the `folded` listing path ignores it (only
82
+ // `listLiveBlobs` reads it, and it uses the default expanded mode).
83
+ blobs: Array<{ pathname: string; size: number; uploadedAt?: Date | string | number }>
84
+ folders?: string[]
85
+ cursor?: string
86
+ hasMore: boolean
87
+ }>
88
+ }
89
+
90
+ // Recognise "blob is gone" errors uniformly across read/write/delete
91
+ // paths so callers can treat them as success (delete) or not-found (read).
92
+ // SDK error subclasses retain `.name === 'Error'`; identify the missing-blob
93
+ // class from the same lazily loaded SDK instance that performs the request.
94
+ // Store/configuration failures must propagate, never masquerade as cache misses.
95
+ export function isNotFound(err: unknown, sdk: Pick<VercelBlobSdk, 'BlobNotFoundError'>): boolean {
96
+ return err instanceof sdk.BlobNotFoundError
97
+ }
98
+
99
+ export async function loadVercelBlobSdk(): Promise<VercelBlobSdk> {
100
+ // @ts-ignore optional peer dep: '@vercel/blob'
101
+ const mod = (await import('@vercel/blob')) as VercelBlobSdk
102
+ return mod
103
+ }