Skip to content

feat(bookmarks): captured-article images → encrypted offline assets (9.18.9)#88

Merged
th3-br41n merged 1 commit into
mainfrom
feat/9.18.9-bookmark-images
Jul 3, 2026
Merged

feat(bookmarks): captured-article images → encrypted offline assets (9.18.9)#88
th3-br41n merged 1 commit into
mainfrom
feat/9.18.9-bookmark-images

Conversation

@th3-br41n

Copy link
Copy Markdown
Contributor

What

Readable capture (network.readable) emits image blocks pointing at the article's remote http URLs. The sandboxed-app CSP (img-src 'self' data: brainstorm:) blocks those, so captured articles rendered image-less and didn't work offline.

This sub-fetches each remote article image through the same guard chain as favicon/cover (fetchAndStoreImage → SSRF / privacy / size / MIME), stores it in the encrypted asset store, and rewrites the block src to brainstorm://asset/<id> — which renders, works offline, and auto-binds an asset_refs row (the bind writer derives refs from brainstorm://asset/ URLs in the entity body).

Safety / bounds

Article image URLs are attacker-controlled, so the fetch is capped:

  • MAX_ARTICLE_IMAGES = 40 distinct URLs (walk stops early)
  • ARTICLE_IMG_SIZE_CAP_BYTES = 5 MiB per image
  • ARTICLE_IMG_CONCURRENCY = 4 (bounded batches)
  • per-image privacy re-check on the image host (a CDN differs from the page host; Allowlist-mode vault won't egress to a non-listed host)
  • an image failing any guard keeps its remote src and is dropped at render — never blocks the capture

Changes

  • new readable/enrich-blocks.ts — pure, network-free walk/rewrite helpers (collectRemoteImageSrcs, rewriteImageSrcs, isRemoteHttpImageSrc). 6 unit tests.
  • handler enrichBlocksWithAssets wired into handleReadable; reuses existing fetchAndStoreImage. 2 end-to-end handler tests (happy rewrite + guard rejection).
  • render bookmark-detail.tsx — keep brainstorm://asset/ images, drop only the remaining remote ones.

Tests

bun run test → 14195 passed, 0 test-case failures (the 10 failed suites are the known worktree Electron failed to install correctly collection artifact — pass in CI). typecheck (packages + apps) + lint clean.

🤖 Generated with Claude Code

…9.18.9)

Readable capture emits image blocks pointing at the article's REMOTE http
URLs, which the app CSP (`img-src 'self' data: brainstorm:`) blocks — so
captured articles rendered image-less and broke offline. Sub-fetch each
remote image through the SAME guard chain as favicon/cover
(`fetchAndStoreImage` → SSRF / privacy / size / MIME), store it encrypted,
and rewrite the block `src` to `brainstorm://asset/<id>` (auto-binds an
`asset_refs` row). Bounded: MAX_ARTICLE_IMAGES distinct URLs, 5 MiB each,
concurrency 4 — a hostile page can't drive unbounded egress. An image that
fails any guard keeps its remote src and is dropped at render.

- new pure walk/rewrite helpers `readable/enrich-blocks.ts` (6 tests)
- handler `enrichBlocksWithAssets` wired into `handleReadable` (2 tests)
- render: keep `brainstorm://asset/` images, drop only remaining remote ones

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@th3-br41n th3-br41n merged commit 7200a33 into main Jul 3, 2026
3 checks passed
@th3-br41n th3-br41n deleted the feat/9.18.9-bookmark-images branch July 3, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant