Skip to content

Encode bookmark id before calling DELETE /bookmarks/{id} #74

Closed

Description

@capJavert

What happened?

Hi, we spotted this while reviewing the marketplace submission. Nice plugin!

src/lib/daily.ts → deleteBookmark() puts the id from src/pages/api/bookmarks/[id].ts straight into the URL:

fetch(`${BASE}/bookmarks/${id}`, { method: "DELETE", ... })

If the route param decodes %2F, an id like lists%2F<id> or ..%2Ffeeds%2Fcustom%2F<id> would send the DELETE to a different Public API endpoint (bookmark folders, custom feeds, profile stack/experiences).

It can't be exploited by anyone else: the session cookie is SameSite=Lax and the call only uses the signed-in user's own token. Still, it's worth hardening.

Steps to reproduce

  1. Sign in (real mode)
  2. Send DELETE /api/bookmarks/lists%2F<listId> from the same browser
  3. The server calls DELETE https://api.daily.dev/public/v1/bookmarks/lists/<listId> instead of a bookmark delete

Expected behavior

Only bookmark deletes should be possible through this route:

fetch(`${BASE}/bookmarks/${encodeURIComponent(id)}`, ...)

and/or reject ids that don't match /^[\w-]+$/ in the route.

Which mode?

Real mode (signed in)

Environment

No response

Anything else?

Two smaller suggestions while you're there:

  • Loading bookmarks fetches up to 1000, which is 20 API requests per load. Free accounts get 200 requests a month, so it's worth mentioning in the About that playing uses API quota, or lowering the cap.
  • Now that "Sign in with daily.dev" works, consider removing the "paste an API token" fallback so personal tokens don't pass through a hosted server.

Activity

  1. finallyjay commented on Oct 9, 2026

    @finallyjay
    Owner

    Thanks for the careful review, and for the kind words! All three points are addressed and deployed:

    • Bookmark id (fix(api): only allow bookmark ids in DELETE /api/bookmarks/:id #76): DELETE /api/bookmarks/:id now rejects any id that doesn't match ^[\w-]+$ with a 400 before calling daily.dev, and deleteBookmark() also uses encodeURIComponent. Covered by e2e tests (lists%2F…, ..%2Ffeeds%2F…); on production, DELETE /api/bookmarks/lists%2F<id> now returns 400 Invalid bookmark id.
    • API quota (feat(roulette): play real bookmarks one page at a time, oldest-ward #77, docs(marketplace): quota-aware skill and an updated listing #79): the roulette loads one page of 50 (one request) instead of up to 1000. Clearing a batch moves on to the next, older page, and a later visit resumes there, so a visit costs 2 requests (profile + one page) plus 1 per delete. The agent skill does the same: one page per batch, more only on request, and it treats a 429 as a possible monthly quota (honours retry-after, never loops). The marketplace About now has an "API quota" section explaining this.
    • Token fallback (feat(auth): make the deployed app OAuth-only #78): the deployed app is OAuth-only. The "paste an API token" form and POST /api/auth only exist under pnpm dev, since OAuth can't redirect to localhost; in production builds the code is gone and the endpoint returns 404.

    I'll update the marketplace listing with the new About and SKILL.md shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions