Skip to content

fix(share-worker): harden sweep, uploads, and content CSP - #71

Merged
wolfgang-aura merged 1 commit into
mainfrom
fix/share-worker-hardening
Oct 7, 2026
Merged

wolfgang-aura merged 1 commit into
mainfrom
fix/share-worker-hardening

Conversation

@wolfgang-aura

Copy link
Copy Markdown
Owner

Fixes the confirmed defects behind GHSA-j65f-2wm8-5r8g and GHSA-cfx9-rjc2-c87c, plus the non-object create body from #68 (refs #68, does not close it).

Changes

  • Sweep: only capsules/<32 hex>/_meta.json counts as a record, and the id comes from the key, never the object body. Upload names under media/ starting with _ are refused.
  • Upload state: PUT answers 409 unless the capsule is uploading. A published capsule is immutable; a leaked token can still delete it.
  • Upload names: media/ accepts only jpg|jpeg|png|gif|webp|avif. The client uploads nothing else for a share (images and video poster stills). content.html, content.md and manifest.json are unchanged.
  • CSP: content.html uses a sha256 script-src for the lightbox script instead of 'unsafe-inline', and adds form-action 'none'. Across the whole history of sourcecapsule.user.js (69 revisions, all since v0.5.0) the script text has exactly one variant, so one hash covers every capsule made by an older client. A test recomputes the hash from the current userscript's emitted HTML, so a future lightbox edit fails CI.
  • Body cap: upload bodies are counted while streaming and cancelled at the capsule's remaining budget instead of buffered whole.
  • Rate limits: new per-capsule UPLOAD_LIMITER (120 per 60 s, keyed on capsule id, checked after the bearer token so strangers cannot spend a capsule's budget). The create limiter now keys IPv6 on the /64 prefix.
  • Create body: null or any non-object JSON returns a JSON 400 instead of throwing.

Deploy note

share-worker/wrangler.toml adds a UPLOAD_LIMITER rate-limit binding (namespace_id = "1002"). It is picked up on wrangler deploy; the code is a no-op when the binding is absent.

Behaviour changes

  • Two existing tests uploaded to an already published capsule; they now seed objects directly or expect 409.
  • Uploads of .svg or unknown-type (.bin) media now get 400. mimeToExt emits these only for SVG or non-image data, which X media does not produce.

Tests

One focused block per item in test/share-worker.test.mjs. npm run lint, format:check, test:share and npm test pass locally.

🤖 Generated with Claude Code

Addresses GHSA-j65f-2wm8-5r8g and GHSA-cfx9-rjc2-c87c, plus the non-object
create body from #68.

- Sweep only treats capsules/<32 hex>/_meta.json as a record and takes the
  id from the key, not the object body. Names under media/ starting with "_"
  are refused.
- Uploads answer 409 unless the capsule is still in the uploading state.
- media/ accepts only image names (jpg, jpeg, png, gif, webp, avif).
- content.html allows its lightbox script by sha256 instead of
  'unsafe-inline', and sets form-action 'none'. A test recomputes the hash
  from the userscript's emitted HTML.
- Upload bodies are counted while streaming and cut off at the remaining
  capsule budget.
- New per-capsule UPLOAD_LIMITER binding; the create limiter keys IPv6
  clients on their /64.
- A JSON body that is not an object returns 400 instead of throwing.

Refs #68

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wolfgang-aura
wolfgang-aura merged commit 8372457 into main Oct 7, 2026
1 check passed
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