Simple single-user bookmark service running on Cloudflare Workers + D1.
This app requires both of the following at runtime:
- D1 binding named
DB - Worker secret named
WRITE_SECRET
If either is missing, the app returns 500 server misconfigured.
- Install dependencies.
npm install-
Ensure D1 binding is configured in
wrangler.tomlas[[d1_databases]]withbinding = "DB". -
Set a local development secret in
.dev.vars(do not commit this file):
WRITE_SECRET=replace-with-a-long-random-secretWith WRITE_SECRET declared as a required secret in wrangler.toml, both WRITE_SECRET=test npm run dev and .dev.vars work for local testing.
- Run local development server:
npm run devThis applies the local D1 migrations automatically before starting Wrangler, so a fresh database no longer needs manual setup.
Set the write secret in Cloudflare before deployment:
wrangler secret put WRITE_SECRETApply migrations as needed for the configured D1 database.
npm test
npm run test:e2enpm test remains the default automated suite. npm run test:e2e verifies MVP critical paths (GET /, POST /bookmarks with valid WRITE_SECRET, GET /rss.xml) against a local Wrangler runtime.
GET /: render bookmark list and formsGET /rss.xml: render RSS feed from current bookmarksPOST /bookmarks/metadata: fetch page metadata (url) and return{ title, thumbnailUrl }without DB writesPOST /bookmarks: create a bookmark (url,title,thumbnailUrl,comment,secret)POST /bookmarks/:id/update: update an existing bookmark (url,title,thumbnailUrl,comment,secret)POST /bookmarks/:id/delete: delete an existing bookmark (secret)
All write routes require a valid WRITE_SECRET and return 403 when authentication fails.
POST /bookmarks/metadata is read-only and does not require WRITE_SECRET. The form calls this endpoint on URL blur, auto-filling only empty Title and Thumbnail URL fields. Metadata failures are non-fatal and manual form submission still works.
For SSRF hardening, metadata fetch performs host validation on both the requested URL and every redirect target. Before each upstream request, it resolves A/AAAA records via DNS-over-HTTPS and rejects requests if any resolved address is loopback/private/link-local/unspecified/multicast/documentation/reserved.
Cloudflare Workers cannot pin the outbound TCP connection to a specific resolved IP, so perfect DNS rebinding prevention is not possible in-process. As compensating controls, this app uses manual redirect handling with per-hop re-validation, strict scheme/host checks, and short upstream timeouts.